Most developers have heard of Ajax by now. Unless you've worked with it, you probably think it's another complicated form of alphabet soup. It actually turns out to be quite easy to use from AppStudio.
First, let's explain what it is (and isn't). It stands for Asynchronous JavaScript And XML. The good news is that there's no need to be asynchronous or to use XML - and that AppStudio takes care of the JavaScript.
Ajax makes it easy to exchange data with your server. Think of it as a function you can call that is running on another system. You can pass it parameters and it can return values. Since it's running on your server, it can do things that you cannot do on your device. It can access files and databases on the server, or get data from other web sites without worrying about Same Origin Policies.
You will need to use a server with PHP enabled to run these samples. The AppStudio Server does not have PHP enabled.
A Simple Exchange of Data with the server
Let's look at some code. We will have our AppStudio app send the server a string, which the server will return reversed. Here is what the app looks like:
The only code in this app is the "Reverse" button. The Ajax function sends the contents of the top textarea to a PHP script that is running on the server. If it returns successfully, we display what came back. Otherwise, we show the error:
Example
var req;
btnSendData.onclick = function() {
req=Ajax("ajax.php/?myText=" + txtSend.value, done);
};
function done() {
if(req.status == 200) { //success
txtResponse.value=req.responseText;
} else { //failure
msg = "Error: Status = " + req.status;
if(TypeName(req.statusText)=="string" ) { msg = msg + " " + req.statusText; }
if(TypeName(req.err)=="string" ) { msg = msg + " " + req.error; }
NSB.MsgBox(msg);
}
}
Dim req
Function btnSendData_onclick()
req=Ajax("https://www.nsbasic.com/i/Ajax/ajax.php/?myText=" + txtSend.value, done)
End Function
Function done()
If req.status = 200 Then 'success
txtResponse.value=req.responseText
Else 'failure
msg = "Error: Status = " & req.status
If TypeName(req.statusText)="string" Then msg = msg & " " & req.statusText
If TypeName(req.err)="string" Then msg = msg & " " & req.error
MsgBox msg
End If
End Function
The PHP script is even easier. It gets the input that is named myText, reverses it, and uses the echo statement to send it back.
<?php
// Get the data from the client.
$myText = $_GET['myText'];
// Send back the text, reversed
echo "Data received from Device (reversed): " . strrev($myText);
?>
Here is the result:
A few notes:
- The php script needs to be on the same folder on the server as the AppStudio app.
- Ajax is based on XMLHttpPost.
- A handy way to return data from your PHP script is in JSON format. PHP even has a handy json_encode() function.
- PHP is not enabled on the AppStudio Server.
Using POST to Send more Data to the Server
Let's extend what we are doing a bit and talk about using the POST method.
We discussed the simplest Ajax call, which sends data to the server by adding it to the end of the URL, a method called, rather misleadingly, GET. But what if we want to send more data? GET has a limit, some say, of around 2000 characters. For more than that, we'll use the also badly named POST method. POST has an upload limit on most servers of 8 megabytes. If you need more, server settings can be adjusted.
Let's look at some code. You'll see it looks very much like our last post:
Example
btnAjax.onclick = function() {
req=Ajax("ajaxPost.php" ,"POST" ,txtSend.value);
if(req.status==200) { //success
htmResponse.innerHTML=req.responseText;
} else { //failure
htmResponse.innerHTML="Error: " + req.err;
}
};
Function btnAjax_onclick()
req=Ajax("ajaxPost.php","POST",txtSend.value)
If req.status=200 Then 'success
htmResponse.innerHTML=req.responseText
Else 'failure
htmResponse.innerHTML="Error: " & req.err
End If
End Function
The difference is a couple of extra parameters. We add "POST" and the data we want to send as a separate parameter. Before we talk about what is going to happen at the server end, let's see what the app looks like:
OK, here is the PHP code on the server which we are calling:
<?php
// uncomment the next line to see runtime error messages from this script
// ini_set('display_errors','1');
// Get the data from the client.
$myText = file_get_contents('php://input');
// Send back the text, reversed
echo "Data received from Device (reversed):<br>" . strrev($myText);
//write the data out to a file on the server
//make sure permissions are all OK!
$myFile = "AjaxPost/ajaxPost.txt";
echo "<p>Writing data to " . getcwd() . $myFile;
$f = fopen($myFile, 'w') or die("can't open file");
fwrite($f, $myText);
fclose($f);
?>
The file_get_contents('php://input') call gets the data, exactly as it was sent. We'll do two things with it: echo it back reversed and we will write it to a file on the server. The first part is easy: anything in an echo statement is sent back to our app:
echo "Data received from Device (reversed):" . strrev($myText);
The second part involves a bit more PHP, but is straightforward. It writes the data was received to a file.
$myFile = "AjaxPost/ajaxPost.txt";
echo "<p>Writing data to " . getcwd() . $myFile;
$f = fopen($myFile, 'w') or die("can't open file");
fwrite($f, $myText);
fclose($f);
What would you use this feature for?
- Upload a JSON copy of your database.
- Upload some pictures in Base64 format.
- Create a .htm file which can be accessed as a web page.
- Lots more ideas!
A tip on debugging PHP scripts Since PHP scripts run on the server and not on your screen, you don't get to see the error messages. This can make debugging difficult. If you put this statement at the top of your PHP script, the error messages will be echoed to your app:
ini_set('display_errors','1');
The error messages come back as HTML formatted text. The best way to look at them is to assign them to the innerText property of an HTMLview control.
Getting Data from Another Site
One of the basic security features of the modern web is the Same Origin Policy. It states that a web app cannot pull up just any data from another site. This becomes a problem if you need data on someone else's site.
First, check to see if the site you want to access has an API. If it does, it's the best solution. If not, you may need to create your own:
While a web app isn't allowed to access the files on another site, a PHP script is. We'll get our PHP script to do the dirty work for us and return the results to our app. The technique we will use is often called scraping.
Time to look at some code again. Here's the AppStudio part that calls the PHP script. It looks very much like the code in A Simple Exchange of Data with the server above. The main difference is that we're calling a different PHP script.
btnGetFile.onclick = function() {
req=Ajax("ajaxGetURL.php/?urlToGet=" + encodeURIComponent(txtURL.value));
if(req.status==200) { //success
htmResponse.innerHTML=req.responseText;
} else { //failure
htmResponse.innerHTML=req.err;
}
htmResponse.refresh();
};
Function btnGetFile_onclick()
req=Ajax("ajaxGetURL.php/?urlToGet=" & encodeURIComponent(txtURL.value))
If req.status=200 Then 'success
htmResponse.innerHTML=req.responseText
Else 'failure
htmResponse.innerHTML=req.err
End If
htmResponse.refresh()
End Function
Before we talk about what is going to happen at the server end, let's see what the app looks like:
And here is what the PHP script looks like:
<?php
$ch = curl_init($_GET['urlToGet']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($ch);
curl_close($ch);
echo $data
?>
Let's take apart this one line:
- $_GET('urlToGet') gets the parameter string that was attached to the URL in our AppStudio code.
- _file_get_contents get the contents of the file, in this case, the URL that was passed.
- echo sends the result back to our AppStudio program.
Pretty simple, isn't it?
(In this example, we're grabbing the robots.txt file from Microsoft's site, just to show we can get files from anywhere. Nearly all sites have a robots.txt file: it is used to guide search engines.)
There is a simpler way to do this, but it can run into security problems.
<?php
echo file_get_contents($_GET['urlToGet']);
?>
How fast is it?
If your app is going to communicate with your server while it is running, you've probably wondered how much your app will slow down when it has to wait for the server.
In this blog post, we're going to make a program to test this. We'll construct a little program which sends data to the server as many times as it can. The server will record the information in a log file. Here is what our app will do:
do{ if(SysInfo(10) > limit) break;
req=Ajax("ajaxLog.php/?myText=" + encodeURIComponent(DateAdd("s",0,new Date())));
counter=counter+1;
} while(0<1);
Do Until SysInfo(10) > limit
req=Ajax("ajaxLog.php/?myText=" + encodeURIComponent(Now))
counter=counter+1
Loop
SysInfo(10) is the current time in milliseconds. Limit is the time we want run to. You can run this test for up to about 5 seconds: after that, the system will think you are in a endless loop and shut you down.
On the server side, we need a PHP script to get the messages and write them out to disk. Here's the code:
<?php
// read and append to file
$myFile = "AjaxLog/log.txt";
$f = fopen($myFile, 'a') or die("can't open file");
fwrite($f, $_GET['myText'] . Chr(13) . Chr(10));
fclose($f);
?>
Now, let's run it on a few different devices and see how many accesses we can do per second:
Desktop (Ethernet) |
2745.0
|
iPhone 4 (WiFi) |
10.5
|
iPhone (3G) |
2.5
|
Nexus S (WiFi) |
5.0
|
Kindle Fire (WiFi) |
9.0
|
It's obvious that the speed that really depends on what kind of connection you have to the web. A hard wire to your router gets the best time. WiFi is much slower, and 3G is slower again. No real surprise here.
The speeds shown here probably are not what you will see if you run the same test. There are a number of factors that will affect the speed:
- Type of data connection
- Load on the network
- Number of hops the data has to make to get to the server
- Load on the server
(You can try this sample yourself - it is called AjaxLog in the Samples folder.)
Asynchronous Calls
In our last Ajax discussion, we discovered that it could take half a second for an Ajax call to complete (actually, due to network latency, it could even be a lot more than that.) How do we do Ajax calls and not make our app look slow?
Easy. We send the Ajax message off, then go do other stuff. When the response comes back, we respond to it. This is called Asynchronous operation.
We do this by adding the name of the function to be called on completion as the 4th parameter of our Ajax call:
req=Ajax("ajax.php/?myText=" + encodeURIComponent(txtSend.value), "", "", done)
Our callback function is called 'done'. After we execute the Ajax call, the program goes on to the next statement in the program immediately. It doesn't wait for any response. We can update the screen, or wait for the user to do something else. When the reply comes back, our 'done' function will be called:
function done() {
if(req.readyState!=4 ) { return; } //ignore progress reports
if(req.status==200) { //success
txtResponse.value=req.responseText;
} else { //failure
txtResponse.value="Error: " + req.err.message;
}
}
Function done()
If req.readyState<>4 Then Exit Function 'ignore progress reports
If req.status=200 Then 'success
txtResponse.value=req.responseText
Else 'failure
txtResponse.value="Error: " & req.err.message
End If
End Function
'done' will actually be called several times with progress reports on the call. We only care if it completes, so we wait for req.readyState to be 4. The rest of our function works just like when we were not asynchronous.
One of the uses of this technique that we see every day is to return partial results on a search field (as in Google). Each time a character is entered, it is sent to the server for a list of possible matches. As the results come back, the window is updated. Entering characters in the field is unaffected by the server communications that are going on at the same time.
To abort a pending Ajax call (it took too long, the user wants to cancel, you no longer want the results, etc.):
req.abort()
Check out the AjaxAsync app in the Samples to see this in action.
Caching
Sometimes when you make the same request repeatedly, you'll get the same result, even through the data on the server has changed. The browser is "helping" you by delivering the same result from its cache instead of going out to the server again. To fix this, add a time stamp to the query string. This example puts the number of milliseconds into the string:
req=Ajax("ajax.php/?myText=" + "sdfsfd" + "&time=" + SysInfo(10), "", "", done)
Where to put your PHP files
For development, put the PHP files for your project right in your project folder. You can then use any text editor that you have installed on your system to edit them.
Next, add the file names to the manifest in Project Properties. When you do a Full Deploy, they will be copied to the server with the rest of your project.
The result is that your PHP files will be placed on the server, in the same folder as your app. Your app can call a PHP script by just giving the file name - no path name will be needed.
Cool things you can get on the web
There are many web services that work in a similar fashion. One of the tip offs is if the calling string has an &callback argument. If it does, it probably can be used with this method.
Some of these services are free or limited, while others require an API key that you must obtain from the service.
Some places to go for services:
Got more? Let us know at support@nsbasic.com.
Zip Code Lookup
Ever realize, when getting a user's address, that knowing the zip code makes asking for the city and state unnecessary? Here's some simple code to look up the city and state from a zip code:
zip="90210"
req=Ajax("http://zip.elevenbasetwo.com?zip=" & zip)
If req.status=200 Then 'success
MsgBox req.responseText
Else 'failure
MsgBox "Error: " & req.err.message
End If
The result is
{"country": "US", "state": "CA", "city": "BEVERLY HILLS"}
Cross Origin Resource Sharing
A lot of the discussion in this Tech Note has been ways to get around Same Origin Policies. Can we get rid of this restriction? We can, but there's a catch.
To allow a PHP script on your server to give its results to apps which were not loaded from your server, add this line as the first line of your script.
header('Access-Control-Allow-Origin: *');
The catch is that now anyone with an internet connection can run your PHP script.
Using AJAX to send an email
Contributed by Graham Barlow, New Zealand
The AppStudio Code:
EmailText = EmailText & vbCRLF & "Date: " & FormatDateTime(Date,"dd/mm/yy")
Url = "/ajaxEmailRegister.php/?EmailTo=gotnomates@farmmates.com"
req=Ajax(Url,"POST",EmailText)
If req.status=200 Then 'success
MsgBox("Confirmation email sent.")
Else 'failure
MsgBox("Error sending email " & req.err)
End If
...and the PHP script that gets called ("ajaxEmailRegister.php"):
<?php
// Get the data from the client.
$myText = file_get_contents('php://input');
$to = $_GET['EmailTo'];
$subject = "New Mate registration confirmation";
$body = $myText;
$headers = "From: gotnomates@farmmates.com";
mail($to, $subject, $body, $headers);
?>
Using AJAX to get info from a database on your server.
Since AppStudio runs on your device, it cannot directly access files or databases on another computer, such as a server. It need to communicate with an app running on the server, which gets the information from the database and returns it.
PHP has built in support for MS SQL, MySQL, Oracle and SQLite. If you have a database on your server, it's easy to query it and return the result to your app. Here's a very simple example:
<?php
$host = 'localhost';
$databasename = 'myDatabaseName';
$username = 'myUsername';
$password = 'myPassword';
$con = mysql_connect($host, $username, $password) or die(mysql_error());
mysql_select_db($databasename) or die(mysql_error());
$query = file_get_contents('php://input');
$results = mysql_query($query);
if (mysql_errno()) {
header('HTTP/1.1 500 Internal Server Error');
echo $query . "\n";
echo mysql_error();
}
else
{
$rows = array();
while($r = mysql_fetch_assoc($results)) {
$rows[] = $r;
}
echo json_encode($rows);
}
?>
Here is a link to an article showing how to do the same thing to Access:
http://phpmaster.com/using-an-access-database-with-php/
and Oracle
http://www.php.net/manual/en/book.oci8.php
and MS SQL
http://php.net/manual/en/function.mssql-connect.php
"I use IHttpHandler with VB.net. Basically asp.net without the user interface. I find it is very fast and works extremely well with SQL Server. You can write custom HTTP handlers to process specific, predefined types of HTTP requests in any Common Language Specification (CLS) compliant language. Executable code defined in the HttpHandler classes, rather than conventional ASP or ASP.NET Web pages, responds to these specific requests. HTTP handlers give you a means of interacting with the low-level request and response services of the IIS Web server and provide functionality much like ISAPI extensions but with a simpler programming model.
https://msdn.microsoft.com/en-us/library/system.web.ihttphandler(v=vs.100).aspx " - JamesF
Security
There is no parsing of the incoming query string, so someone could conceivably put some malicious code in there ("SQL Injection") and you may want to determine that the person requesting the info is authorized to do so.
For a more detailed description, see this article: http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app.
Here's XKCD's comment on this:
Using AJAX to get save a picture to your server
See the sample "CameraToServer", contributed by Mike Miller jdmdude2009@gmail.com
This sample lets you get a picture from the camera or the Photo Library
and display it on your screen and:
- Push it to a web server in base64 or PNG format using an AJAX POST
- Receive a response from the server and do something with it.
This is a mod of the original Camera Sample file.
You must have the supplied PHP files in the same location
as the AppStudio production files.
To control when a pic may be sent or that a pic is not sent
multiple times by click happy users, btnSubmitImages is placed
in different states of visiblility and/or usability by way of the
SentFlag.
Note: PictureBox1 is in the negative left of the form.
This is a trick that developers use to hide controls
from the users but still have availability to them.
Image1 is used for preview ONLY because viewing PictureBox1
isn't very realistic for viewing because it exceeds the
mobile screen size. (You can size the PictureBox to any size
a camera is capable of, but beware, size does matter! The lag
to insert an image into a db gets longer as the pics get larger.
I also noticed phpmyadmin is not very responsive to large images AND
I have had timeouts when loading pages with lots of images. I suspect
some sort of slide presentation tool that controls the load of
the images will be in order to view large quantities of photos
(lot's of opensource for slide views)
(the source code of the program)
Dim e
Dim SentFlag
reader = new FileReader()
img=new Image()
SentFlag=0
Sub Main()
pb=PictureBox1.getContext("2d")
SetTimeout(window.scrollTo(0,window.innerHeight),100)
btnSubmitImage.value=""
End Sub
Function txtGetPicture_onchange()
'This gets called after the picture is chosen.
'Next, let's read start reading it in.
'The _onload function will be called when that completes.
reader.readAsDataURL(txtGetPicture.files[0])
e=event
End Function
Function reader_onload(e)
'read of the file is complete.
'Now, let's load it into an image.
'The _onload function for the image will be called on completion.
img.src=e.target.result
Image1.firstChild.src=e.target.result 'Phone Viewing ONLY
SentFlag=0
btnSubmitImage.hidden=False
btnSubmitImage.value="Send Image to Web"
lblPicMsg.textContent="Now is the time"
End Function
Function img_onload()
'image is now loaded. Let's add it to the PictureBox2 and scale
'PictureBox1 is very large here and takes a while for the image
'to transfer to the web server (approx 1MB)
'still no solution it the iOS distortion.
pb.drawImage(img,0,0,PictureBox1.width,PictureBox1.Height)
End Function
Function PostToWebServer()
Dim devInfo
Dim postData
SentFlag=1 'Don't allow multiple submissions because the button
'is pushed more than once.
'Get divice information - not available
devInfo="No device selected"
'Build POST
postData = "filename=" & txtGetPicture.files[0].name
postData = postData & "&deviceInfo=" & devInfo
postData = postData & "&imgBase64=" & PictureBox1.toDataURL()
'Send POST to server
req=Ajax("insertimage.php","POST",postData)
If req.status=200 Then 'success
btnSubmitImage.hidden=True 'Picture was pushed to server so hide button
lblPicMsg.textContent = "Success!!!"
htmResponse.innerHTML=req.responseText
Else 'failure
htmResponse.innerHTML="Error: " & req.err
btnSubmitImage.hidden=False
lblPicMsg.textContent = "Failure"
SentFlag=0
End If
End Function
Function btnSubmitImage_onclick()
If Len(PictureBox1.toDataURL())>0 And SentFlag=0 Then
btnSubmitImage.value = "Sending"
lblPicMsg.textContent = "Please Wait"
'Use the SetTimout function to call a procedure OUTSIDE
'this onclick event so that any events that need to
'occur on submission are not held up by the size of the
'image or the speed of the internet connection.
'(i.e. the btn value change and the label caption change.
SetTimeout(PostToWebServer,3000) 'POST TO WEB
Else
lblPicMsg.textContent = "Need to take a pic first"
End If
End Function
Using AJAX to get all kinds of stuff on the web
Many websites have information that can be retrieved using AJAX. The best list we have found is on Programmable Web.
Using Ajax with VoltBuilder
When running as a native app, your program is no longer tied to the server it was downloaded from. As a result, it doesn't know where to go on the web. You need to make two changes to any of the above samples to make them work as VoltBuilder apps.
1. Declare the website you want to access in the configxml property (in Project Properties), by adding this line:
<access origin="https://www.nsbasic.com" />
Substitute the URL to your site which has the script on it. To allow everything (not really a good idea), do this:
<access origin="*" />
2. Use the full path to the script in your Ajax call:
req=Ajax("https://www.nsbasic.com/i/Ajax/ajax.php/?myText=" + encodeURIComponent(txtSend.value))
(Again, substitute the path to your script - don't use this one!)
3. Set jQuery's options to allow cross origin requests:
Sub Main()
'the next two statements should be added to your Sub Main()
$.support.cors = True
$.mobile.allowCrossDomainPages = True
End Sub
Using Ajax with ASP.NET
Everything is pretty much the same on the AppStudio side. Here's how it looks on the server side. (Thanks to John Cody!)
1) Make sure this entry is in your asp.net webservice's web.config:
<webServices>
<protocols>
<add name="HttpPost"/>
</protocols>
</webServices>
2) ASP.NET VB code (i.e Service.asmx file):
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> <ToolboxItem(False)> _
Public Class WebService
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function HelloWorld(Token as string) As String
'the var Token has the string that was send from the AppStudio app
Return "Hello World"
End Function
End Class
3) AppStudio code:
Dim req
Function cmdSend_onclick()
req = Ajax("http://mywebsite.com/Service.asmx/HelloWorld","POST","token=&Test123", send_return)
End Function
Function send_return()
Dim Res
If req.readyState <> 4 Then Exit Function 'ignore progress reports
If req.status = 200 Then 'success
Res = req.responseText
'Res has "Hello World" in it, but it is in XML/UTF-8 format and needs to be decoded
Else
'some error
End If
End Function