|
|
(81 intermediate revisions by 3 users not shown) |
Line 1: |
Line 1: |
| 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. | | 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 NS Basic takes care of the JavaScript. | | First, let's explain what it is (and isn't). It stands for [http://en.wikipedia.org/wiki/Ajax_(programming) 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. | | [[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 [http://en.wikipedia.org/wiki/Same_origin_policy 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 === | | === 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: | | 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: |
| | |
| | [[File:Ajax1.png]]<br /> |
| | |
| | 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 == |
| | |
| | <tabber> |
| | JavaScript= |
| | <syntaxhighlight lang="JavaScript"> |
| | var req; |
|
| |
|
| | btnSendData.onclick = function() { |
| | req=Ajax("ajax.php/?myText=" + txtSend.value, done); |
| | }; |
|
| |
|
| 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:
| |
|
| |
|
| | 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); |
| | } |
| | } |
| | </syntaxhighlight> |
| | |-| |
| | BASIC= |
| | <syntaxhighlight lang="vb.net"> |
| Dim req | | Dim req |
| Function btnAjax_onclick() | | |
| req=Ajax("ajax.php/?myText=" & txtSend.value) | | Function btnSendData_onclick() |
| If req.status=200 Then 'success | | 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 | | txtResponse.value=req.responseText |
| Else 'failure | | Else 'failure |
| txtResponse.value="Error: " & req.err.message | | 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 If |
| End Function | | End Function |
| | </syntaxhighlight> |
| | </tabber> |
| | |
| 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. | | 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. |
| | | <pre> |
| <?php | | <?php |
| // Get the data from the client. | | // Get the data from the client. |
Line 29: |
Line 68: |
| echo "Data received from Device (reversed): " . strrev($myText); | | echo "Data received from Device (reversed): " . strrev($myText); |
| ?> | | ?> |
| | </pre> |
| Here is the result: | | Here is the result: |
|
| |
|
| | [[File:Ajax2.png]] |
|
| |
|
| A few notes: | | A few notes: |
|
| |
|
| The php script needs to be on the same folder on the server as the NS Basic app. | | * The php script needs to be on the same folder on the server as the AppStudio app. |
| Ajax is based on XMLHttpPost. | | * [[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. | | * A handy way to return data from your PHP script is in [http://blog.nsbasic.com/?p=425 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 === | | === Using POST to Send more Data to the Server === |
Line 42: |
Line 84: |
| Let's extend what we are doing a bit and talk about using the POST method. | | 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. | | 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: | | Let's look at some code. You'll see it looks very much like our last post: |
| | == Example == |
|
| |
|
| | <tabber> |
| | JavaScript= |
| | <syntaxhighlight lang="JavaScript"> |
| | 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; |
| | } |
| | }; |
| | </syntaxhighlight> |
| | |-| |
| | BASIC= |
| | <syntaxhighlight lang="vb.net"> |
| Function btnAjax_onclick() | | Function btnAjax_onclick() |
| req=Ajax("ajaxPost.php","POST",txtSend.value) | | req=Ajax("ajaxPost.php","POST",txtSend.value) |
Line 54: |
Line 112: |
| End If | | End If |
| End Function | | End Function |
| | </syntaxhighlight> |
| | </tabber> |
| | |
| 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: | | 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: |
|
| |
|
| | [[File:Ajaxpost.png]]<br /> |
|
| |
|
| OK, here is the PHP code on the server which we are calling: | | OK, here is the PHP code on the server which we are calling: |
| | | <pre> |
| <?php | | <?php |
| // uncomment the next line to see runtime error messages from this script | | // uncomment the next line to see runtime error messages from this script |
Line 75: |
Line 137: |
| fwrite($f, $myText); | | fwrite($f, $myText); |
| fclose($f); | | fclose($f); |
|
| |
| ?> | | ?> |
| | </pre> |
| 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: | | 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): | | <pre> |
| " . strrev($myText); | | 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. | | The second part involves a bit more PHP, but is straightforward. It writes the data was received to a file. |
| $myFile = "AjaxPost/ajaxPost.txt";
| | <pre> |
| | $myFile = "AjaxPost/ajaxPost.txt"; |
| echo "<p>Writing data to " . getcwd() . $myFile; | | echo "<p>Writing data to " . getcwd() . $myFile; |
| $f = fopen($myFile, 'w') or die("can't open file"); | | $f = fopen($myFile, 'w') or die("can't open file"); |
| fwrite($f, $myText); | | fwrite($f, $myText); |
| fclose($f); | | fclose($f); |
| | </pre> |
| What would you use this feature for? | | What would you use this feature for? |
|
| |
|
| Upload a JSON copy of your database. | | * Upload a JSON copy of your database. |
| Upload some pictures in Base64 format. | | * Upload some pictures in Base64 format. |
| Create a .htm file which can be accessed as a web page. | | * Create a .htm file which can be accessed as a web page. |
| Lots more ideas! | | * 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: | | 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'); | | ini_set('display_errors','1'); |
| | </pre> |
| 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. | | 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 === | | === 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. | | One of the basic security features of the modern web is the [http://en.wikipedia.org/wiki/Same_origin_policy 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.
| | 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: |
|
| |
|
| 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.
| | 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 [http://en.wikipedia.org/wiki/Web_scraping 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. |
| | <tabber> |
| | JavaScript= |
| | <syntaxhighlight lang="JavaScript"> |
| | 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(); |
| | }; |
| | </syntaxhighlight> |
| | |-| |
| | BASIC= |
| | <syntaxhighlight lang="vb.net"> |
| Function btnGetFile_onclick() | | Function btnGetFile_onclick() |
| req=Ajax("ajaxGetURL.php/?urlToGet=" & txtURL.value) | | req=Ajax("ajaxGetURL.php/?urlToGet=" & encodeURIComponent(txtURL.value)) |
| If req.status=200 Then 'success | | If req.status=200 Then 'success |
| htmResponse.innerHTML=req.responseText | | htmResponse.innerHTML=req.responseText |
Line 114: |
Line 198: |
| htmResponse.refresh() | | htmResponse.refresh() |
| End Function | | End Function |
| | </syntaxhighlight> |
| | </tabber> |
| | |
| Before we talk about what is going to happen at the server end, let's see what the app looks like: | | Before we talk about what is going to happen at the server end, let's see what the app looks like: |
|
| |
|
| | [[File:Ajaxgeturl.png]]<br /> |
|
| |
|
| And here is what the PHP script looks like: | | And here is what the PHP script looks like: |
| | | <pre> |
| <?php | | <?php |
| echo file_get_contents($_GET['urlToGet']);
| | $ch = curl_init($_GET['urlToGet']); |
| | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
| | $data = curl_exec($ch); |
| | curl_close($ch); |
| | echo $data |
| ?> | | ?> |
| | </pre> |
| Let's take apart this one line: | | Let's take apart this one line: |
|
| |
|
| $_GET('urlToGet') gets the parameter string that was attached to the URL in our App Studio code. | | * $_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. | | * _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. | | * echo sends the result back to our AppStudio program. |
| | |
| Pretty simple, isn't it? | | 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.) | | (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. |
| | <pre> |
| | <?php |
| | echo file_get_contents($_GET['urlToGet']); |
| | ?> |
| | </pre> |
|
| |
|
| === How fast is it? === | | === How fast is it? === |
Line 136: |
Line 237: |
|
| |
|
| 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: | | 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: |
| | | <tabber> |
| | JavaScript= |
| | <syntaxhighlight lang="JavaScript"> |
| | do{ if(SysInfo(10) > limit) break; |
| | req=Ajax("ajaxLog.php/?myText=" + encodeURIComponent(DateAdd("s",0,new Date()))); |
| | counter=counter+1; |
| | } while(0<1); |
| | </syntaxhighlight> |
| | |-| |
| | BASIC= |
| | <syntaxhighlight lang="vb.net"> |
| Do Until SysInfo(10) > limit | | Do Until SysInfo(10) > limit |
| req=Ajax("ajaxLog.php/?myText=" + Now) | | req=Ajax("ajaxLog.php/?myText=" + encodeURIComponent(Now)) |
| counter=counter+1 | | counter=counter+1 |
| Loop | | Loop |
| | </syntaxhighlight> |
| | </tabber> |
| | |
| 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. | | 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: | | On the server side, we need a PHP script to get the messages and write them out to disk. Here's the code: |
| | | <pre> |
| <?php | | <?php |
| // read and append to file | | // read and append to file |
Line 152: |
Line 266: |
| fclose($f); | | fclose($f); |
| ?> | | ?> |
| | </pre> |
| Now, let's run it on a few different devices and see how many accesses we can do per second: | | Now, let's run it on a few different devices and see how many accesses we can do per second: |
|
| |
|
| Desktop (Ethernet) 2745.0 | | {| class="wikitable" |
| iPhone 4 (WiFi) 10.5 | | |- |
| iPhone (3G) 2.5 | | | Desktop (Ethernet) || 2745.0 |
| Nexus S (WiFi) 5.0 | | |- |
| Kindle Fire (WiFi) 9.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. | | 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: | | 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 | | * Type of data connection |
| Load on the network | | * Load on the network |
| Number of hops the data has to make to get to the server | | * Number of hops the data has to make to get to the server |
| Load on the server | | * Load on the server |
| | |
| (You can try this sample yourself - it is called AjaxLog in the Samples folder.) | | (You can try this sample yourself - it is called AjaxLog in the Samples folder.) |
|
| |
|
| === Asynchronous Calls === | | === 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? | | 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. | | 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: | | 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) | | req=Ajax("ajax.php/?myText=" + encodeURIComponent(txtSend.value), "", "", done) |
| | </pre> |
| 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: | | 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: |
| | <tabber> |
| | JavaScript= |
| | <syntaxhighlight lang="JavaScript"> |
| | 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; |
| | } |
| | } |
| | </syntaxhighlight> |
| | |-| |
| | BASIC= |
| | <syntaxhighlight lang="vb.net"> |
| Function done() | | Function done() |
| If req.readyState<>4 Then Exit Function 'ignore progress reports | | If req.readyState<>4 Then Exit Function 'ignore progress reports |
Line 187: |
Line 328: |
| End If | | End If |
| End Function | | End Function |
| | </syntaxhighlight> |
| | </tabber> |
| | |
| '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. | | '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. | | 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.): |
| | <pre> |
| | req.abort() |
| | </pre> |
|
| |
|
| Check out the AjaxAsync app in the Samples to see this in action. | | 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: |
| | <pre> |
| | req=Ajax("ajax.php/?myText=" + "sdfsfd" + "&time=" + SysInfo(10), "", "", done) |
| | </pre> |
|
| |
|
| === Where to put your PHP files === | | === Where to put your PHP files === |
Line 200: |
Line 357: |
| 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. | | 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 === | | === Cool things you can get on the web === |
| | |
| 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. | | 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. |
Line 262: |
Line 365: |
| Some places to go for services: | | Some places to go for services: |
|
| |
|
| Lots of stuff: Yahoo! | | * The master list is here: [http://www.programmableweb.com Programmable Web] |
| Weather: Wunderground | | * Lots of stuff: [http://developer.yahoo.com/javascript/ Yahoo]! |
| Elevation: Geonames | | * Weather: [http://www.wunderground.com/weather/api/d/docs Wunderground] |
| Geographic Info: Geonames | | * Elevation: [http://www.geonames.org/export/JSON-webservices.html Geonames] |
| | * Geographic Info: [http://www.geonames.org/export/JSON-webservices.html Geonames] |
| | |
| Got more? Let us know at support@nsbasic.com. | | Got more? Let us know at support@nsbasic.com. |
|
| |
|
Line 271: |
Line 376: |
|
| |
|
| 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: | | 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: |
| | <pre> |
| zip="90210" | | zip="90210" |
|
| |
|
Line 279: |
Line 385: |
| MsgBox "Error: " & req.err.message | | MsgBox "Error: " & req.err.message |
| End If | | End If |
| | </pre> |
| The result is | | The result is |
| | <pre> |
| {"country": "US", "state": "CA", "city": "BEVERLY HILLS"} | | {"country": "US", "state": "CA", "city": "BEVERLY HILLS"} |
| | </pre> |
|
| |
|
| === Cross Origin Resource Sharing === | | === Cross Origin Resource Sharing === |
Line 286: |
Line 395: |
| 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. | | 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. | | 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. |
| | | <pre> |
| header('Access-Control-Allow-Origin: *'); | | header('Access-Control-Allow-Origin: *'); |
| | </pre> |
| The catch is that now anyone with an internet connection can run your PHP script. | | The catch is that now anyone with an internet connection can run your PHP script. |
|
| |
|
Line 294: |
Line 404: |
| ''Contributed by Graham Barlow, New Zealand'' | | ''Contributed by Graham Barlow, New Zealand'' |
|
| |
|
| The App Studio Code: | | The AppStudio Code: |
| | | <pre> |
| EmailText = EmailText & vbCRLF & "Date: " & FormatDateTime(Date,"dd/mm/yy") | | EmailText = EmailText & vbCRLF & "Date: " & FormatDateTime(Date,"dd/mm/yy") |
| Url = "/ajaxEmailRegister.php/?EmailTo=gotnomates@farmmates.com" | | Url = "/ajaxEmailRegister.php/?EmailTo=gotnomates@farmmates.com" |
Line 304: |
Line 414: |
| MsgBox("Error sending email " & req.err) | | MsgBox("Error sending email " & req.err) |
| End If | | End If |
| | </pre> |
| ...and the PHP script that gets called ("ajaxEmailRegister.php"): | | ...and the PHP script that gets called ("ajaxEmailRegister.php"): |
| | <pre> |
| <?php | | <?php |
| // Get the data from the client. | | // Get the data from the client. |
Line 314: |
Line 426: |
| mail($to, $subject, $body, $headers); | | mail($to, $subject, $body, $headers); |
| ?> | | ?> |
| | </pre> |
| | |
| | === Using AJAX to get info from a database on your server. === |
|
| |
|
| === Using AJAX to get info from MySQL or SQLite on the 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 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 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: |
| | <pre> |
| <?php | | <?php |
|
| |
|
| $host = "localhost"; | | $host = 'localhost'; |
| $databasename = "myDatabaseName"; | | $databasename = 'myDatabaseName'; |
| $username ="myUsername"; | | $username = 'myUsername'; |
| $password = "myPassword"; | | $password = 'myPassword'; |
|
| |
|
| $con = mysql_connect($host,$username,$password) or die(mysql_error()); | | $con = mysql_connect($host, $username, $password) or die(mysql_error()); |
| mysql_select_db($databasename) or die(mysql_error()); | | mysql_select_db($databasename) or die(mysql_error()); |
| $query = file_get_contents("php://input"); | | $query = file_get_contents('php://input'); |
| $results = mysql_query($query); | | $results = mysql_query($query); |
|
| |
|
| if (mysql_errno()) { | | if (mysql_errno()) { |
| header("HTTP/1.1 500 Internal Server Error"); | | header('HTTP/1.1 500 Internal Server Error'); |
| echo $query.'\n'; | | echo $query . "\n"; |
| echo mysql_error(); | | echo mysql_error(); |
| } | | } |
Line 344: |
Line 460: |
| } | | } |
| ?> | | ?> |
| 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. | | </pre> |
| | |
| | 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 ("[http://en.wikipedia.org/wiki/SQL_injection 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: |
| | |
| | [[File:Exploits of a mom.png]] |
| | |
| | === 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: |
| | <ul> |
| | <li>Push it to a web server in base64 or PNG format using an AJAX POST |
| | <li>Receive a response from the server and do something with it. |
| | </ul> |
| | 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) |
| | |
| | <div class="toccolours mw-collapsible mw-collapsed" > |
| | (the source code of the program) |
| | <div class="mw-collapsible-content"> |
| | <pre> |
| | 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 |
| | </pre> |
| | </div> |
| | </div> |
| | |
| | === 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 [http://www.programmableweb.com 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: |
| | <pre> |
| | <access origin="https://www.nsbasic.com" /> |
| | </pre> |
| | Substitute the URL to your site which has the script on it. To allow everything (not really a good idea), do this: |
| | <pre> |
| | <access origin="*" /> |
| | </pre> |
| | |
| | 2. Use the full path to the script in your Ajax call: |
| | <pre> |
| | req=Ajax("https://www.nsbasic.com/i/Ajax/ajax.php/?myText=" + encodeURIComponent(txtSend.value)) |
| | </pre> |
| | (Again, substitute the path to your script - don't use this one!) |
| | |
| | 3. Set jQuery's options to allow cross origin requests: |
| | <pre> |
| | Sub Main() |
| | 'the next two statements should be added to your Sub Main() |
| | $.support.cors = True |
| | $.mobile.allowCrossDomainPages = True |
| | End Sub |
| | </pre> |
| | |
| | === 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: |
| | <pre> |
| | <webServices> |
| | <protocols> |
| | <add name="HttpPost"/> |
| | </protocols> |
| | </webServices> |
| | </pre> |
| | |
| | 2) ASP.NET VB code (i.e Service.asmx file): |
| | <pre> |
| | 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 |
| | </pre> |
| | |
| | 3) AppStudio code: |
| | <pre> |
| | 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 |
| | <pre> |
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