Ajax made Simple

From NSB App Studio
Revision as of 10:40, 10 November 2012 by Ghenne (talk | contribs)
Jump to navigation Jump to search

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 App Studio.

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 NS Basic 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.

A Simple Exchange of Data with the server

Let's look at some code. We will have our NS Basic 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 Ajaxfunction 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:

Dim req
Function btnAjax_onclick()
  req=Ajax("ajax.php/?myText=" & txtSend.value)
  If req.status=200 Then 'success
    txtResponse.value=req.responseText
  Else 'failure
    txtResponse.value="Error: " & req.err.message
  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 NS Basic 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.

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:

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); 
<pre>
The second part involves a bit more PHP, but is straightforward. It writes the data was received to a file.
<pre> 
$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);
<pre>
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:
<pre>
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.

Fortunately, they left an easy workaround. 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 App Studio part that calls the PHP script. It looks very much like the code in Ajax Made Simple. The main difference is that we're calling a different PHP script.

Function btnGetFile_onclick()
  req=Ajax("ajaxGetURL.php/?urlToGet=" & 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
echo file_get_contents($_GET['urlToGet']); 
?>

Let's take apart this one line:

$_GET('urlToGet') gets the parameter string that was attached to the URL in our App Studio 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 App Studio 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.)

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 Until SysInfo(10) > limit
    req=Ajax("ajaxLog.php/?myText=" + 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);
?>
<pre>
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:
<pre>
req=Ajax("ajax.php/?myText=" + 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 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.

Check out the AjaxAsync app in the Samples to see this in action.

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.

Using JSONP to get a Stock Quote

Working with Ajax, we have run into the Same Origin Policy over and over again. In this post, we'll show you another way to get around it. The Same Origin Policy restricts you from loading files from a server not your own. There is an exception: JavaScript (.js) files are allowed. There are sites on the web where you can request information and have it passed back in JSONP format. The "P" stands for padding: JSONP is JSON with padding. The results are returned as a function call, with the results as parameters. That function can then be called in your program.

Let's use this to get a stock quote from Yahoo. There are 3 parts to this: first, we have to make the request. Next, we handle the return. Finally, we execute the function.

Make the request

 Dim YQI, URL, callback
 YQI = escape("select * from yahoo.finance.quotes where symbol in ('AAPL','GOOG','MSFT')")
 callback="requestComplete"
 URL = "http://query.yahooapis.com/v1/public/yql?q=" & YQI & _
       "&format=json&env=http://datatables.org/alltables.env&callback=" & callback
 loadScript(URL)

Yahoo! has a very powerful API to look up all kinds of data. To make it easier, they have set up an interface using SQL like statements called YQI. In our code above, we start by creating our YQI request. The escape() function translates special characters and spaces so they can be sent in our Ajax call.

URL is then composed of our YQI request with the rest of the boilerplate that is needed. The most interesting part is the &callback part. It names the function that the return values will be wrapped in.

Having prepared everything, we can inject the script.

Inject the script

Yahoo! is going to return some code: it will be a call to the function we specified in callback, with the parameters all filled in. The following code will insert the code returned by Yahoo! into our program and execute it:

Function loadScript(URL)
  Dim head, script
  head=document.getElementsByTagName("head")[0]
  script=document.createElement("script")
  script.src=URL
  script.async=True
  head.appendChild(script)
End Function

We do the usual checking to see if the call is complete. If it is, we will have gotten a text string back that looks something like this:

requestComplete({"query":
  {"count":3,"created":"2012-03-20T11:34:00Z","lang":"en-US","results":
     {"quote":...);

This is a valid function call, so when it is run, the function is executed.

Execute the function

The Eval() function causes requestComplete() to be called.

Function requestComplete(data)
  Dim quotes, i
  quotes=data.query.results.quote
  TextArea1.value=""
  For i=0 To Len(quotes)-1
    TextArea1.value=TextArea1.value+quotes[i].symbol & _
                    ": " & quotes[i].Ask & vbCRLF
  Next
End Function

The data that comes back is a fairly complex structure with a lot of data in it. You can copy it to the NotePad to see everything that is there. For now, we are just interested in the asking price of the stock. The results are returned in an array, with one element per stock.

Closing points

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:

  • Lots of stuff: Yahoo!
  • Weather: Wunderground
  • Elevation: Geonames
  • Geographic Info: Geonames

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 App Studio 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 MySQL or SQLite on the server

PHP has built in support for MySQL 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);
}
?>

You'll want to address security with this. 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.