Section 15.1. Handling Asynchronous EventsIncluding Ajax Requests


15.1. Handling Asynchronous EventsIncluding Ajax Requests

In the earliest days of the web, all we had were simple files that displayed in our web browsers. The next step was to dynamically generate on the server static information that displayed in the browser. The final step to making browser-based applications is to make it so that the information in the browser is no longer unchanging. Ajax is the name applied for using JavaScript to retrieve new information and update the page without a page refresh.

The MochiKit.Async package provides functions to help you do Ajax easily. In this section, you'll see how these work and take a look at a function that installs a timer on your page. How does a timer relate to Ajax? Timers and Ajax requests both represent functions that run asynchronously with results that show up at some point in the future.

15.1.1. Dealing with Results That Arrive Later

Web browsers provide an object called an XMLHttpRequest that is used for requesting additional information from the server. This object has two different ways of working: synchronous and asynchronous. Synchronous is how we'd like to be able to program this type of thing:

mypage = XMLHttpRequest(...); doSomethingWithThatPage(mypage);


This style of programming is just like any other programming you do. Call a function, get a result back, and do something with it. It's very pleasant.

Unfortunately, the way XMLHttpRequest is implemented, doing things as in the preceding example will result in your user's browser becoming entirely unresponsive until the result comes back. If there's one sure-fire way to frustrate people, it's to give them a program that doesn't respond.

To get around this, XMLHttpRequest is generally used asynchronously. You create the request, and a function of yours gets called when the data is ready. The user's browser never locks up.

Support for Ajax in MochiKit is styled after the Twisted Network Programming Toolkit (Twisted) that's written in Python (www.twistedmatrix.com). This was a good choice, given that Twisted is all about asynchronous programming.

The way MochiKit handles Ajax requests is to hand you an object called a Deferred. A Deferred is like an IOU. It's a promise that later on you're either going to get a value back or an error. You can do some advanced things by creating your own Deferred functions, but most of the time you're just going to use the Deferred that is given to you by one of MochiKit.Async's functions.

15.1.2. Making a Request

Let's say that you're not out to do anything too fancy. You just want to run a simple XMLHttpRequest to get some more data from the server. Here's how you do that:

function showText(xmlhttp) {     alert(xmlhttp.responseText); } d = doSimpleXMLHttpRequest("http://localhost:8080/yourcode"); d.addCallback(showText);


That's some easy Ajax.

doSimpleXMLHttpRequest returns a Deferred. By making the call, we know that the XMLHttpRequest was made, but we have no idea when it will return. We just have the promise that we will either get a result or an error condition after the browser has finished with the asynchronous request.

By calling addCallback on the Deferred, we tell MochiKit where in our JavaScript code the result of that Ajax call needs to go.

When that result arrives, our callback function is called with the XMLHttpRequest object as a parameter. In the preceding example, we put the exact text returned by the server into an alert box. The XMLHttpRequest has a few useful properties that we can look at, and these are enumerated in Table 15.1.

Table 15.1. Useful Properties of XMLHttpRequest

Property Name

Value

responseText

String data that came from the server

responseXML

A Document Object Model (DOM) representation of the data

status

The numeric status code returned from the server (for example, 200)

statusText

The string version of the status, as returned by the server (for example, "OK")


Most often, you won't need to refer to the status field because MochiKit automatically handles the values there for you. Statuses of 200 (OK) or 304 (Not Modified) are considered "success codes" and will result in your callback being called. If any other status code comes up, it's considered an error, which leads us to …

15.1.3. Handling Errors

If your Ajax call has an incorrect URL or some kind of error occurs on the server, your client-side code on the browser needs to be able to handle those cases. MochiKit Deferred objects differentiate between successful calls and failures. The showText function in the preceding example is only going to be called if the Ajax call was a success. What if it fails?

In our example, it would fail silently. The user wouldn't get a false indication of success, but he's probably not going to be happy about being given the silent treatment.

We can add to the example to provide a useful error message:

function showText(xmlhttp) {     alert(xmlhttp.responseText); } function showError(err) {     alert("An error occurred! The status was: " + err.number); } d = doSimpleXMLHttpRequest("http://localhost:8080/yourcode"); d.addCallbacks(showText, showError);


The Deferred addCallbacks method gives you one call to handle a common pattern: setting up callbacks for both success and error conditions. In this revised example, if the server yielded a 500 response, the error alert box would pop up telling you that. An alternative to addCallbacks is to call addCallback with your success callback and addErrback with the call that should be made in the event of an error. Another alternative that you can use if your function correctly handles both success and error cases is addBoth. In summary:

  • addCallback(callback) Adds a function to call on success

  • addErrback(errback) Adds a function to call on failure

  • addCallbacks(callback, errback) Adds functions to be called for success or failure

  • addBoth(callback) Adds a single function that will be called for both success or failure

The object passed to your error callback is an XMLHttpRequestError object. You can get at the original XMLHttpRequest object via the req property, and you can also get at the status via the number property, as seen previously.

15.1.4. Passing Parameters

Quite often, you want the server to return some specific data, so you need to pass parameters to tell the server what you're looking for. You could build up a string of the URL, properly encoding each part along the way. But, given that this is a common need, MochiKit provides a clean and neat way to do this:

d = doSimpleXMLHttpRequest("http://localhost:8080/order",     {"size": size, "pepperoni" : pepp});


This ensures that the size and pepp variables are fit for passing in a URL. It's also much easier to build up a hash programmatically than it is to build up a properly formatted string.

15.1.5. Limitations of Ajax

The JavaScript security model prevents JavaScript code from making requests to servers other than the one the page originated on. A common desire today is to aggregate data that comes from different services. For example, if you have a business management application, you might want to pull census data for a specific city from another website.

One solution to this problem is to proxy the request through your application. The user's browser will make an Ajax call to your TurboGears application, which will then make its own HTTP request or requests to retrieve the data from the other server. This solution works, but you end up paying the bandwidth bill to pull the data down to your server and then pass it along to the user. In addition, this is likely to be slower for your users.

Some web services have started offering the ability to include a callback function name with your request. The script tag in HTML enables you to request a script from another domain. Combined with that callback parameter, you can retrieve and process data from services on other servers. For example, suppose your application needs to display the phone number of the nearest pizza delivery place, and pizzasforyou.com knows how to find that restaurant for you. You can write a function on your page that looks like this:

function showphone(result) {     alert(result["phone"]); }


Then, in your page, you include a script tag that looks like this:

    <script src="/books/4/370/1/html/2/http://www.pizzasforyou.com/search?phone=734-555-1212&format=json &callback=showphone"></script>


That tag will make the call out to PizzasForYou, and the result would look something like:

showphone({"name":"Big Ed's Pizza Bistro","phone":"810-555-1212"});


This convenient workaround will result in your showphone function getting called when the result from PizzasForYou is ready. This approach is easy to implement on both the browser side (in your code) and on the server side (the code of the service provider). Unfortunately, it does require implementation on both sides, so you must check the documentation for any web service you connect to in order to determine whether this is supported.

15.1.6. Using JSON

In the preceding section, our request to PizzasForYou included a parameter format=json, and the result that came back included this:

{"name":"Big Ed's Pizza Bistro","phone":"810-555-1212"}


That looks suspiciously familiar to both JavaScript and Python programmers because that snippet is legal in both languages! This data is encoded in JavaScript Object Notation (JSON), a simple format that is also remarkably flexible. JSON allows you to nest JavaScript hashes and arrays that contain other hashes, arrays, and native types, letting you create a hierarchy of data with ease. In JavaScript, converting from JSON to native objects is as simple as eval(json_value). MochiKit also provides functions for encoding JavaScript values into JSON. On the Python side, TurboGears uses simplejson to perform conversions to and from JSON.

Despite the fact that the x in Ajax stands for XML, JSON is an excellent format to use for your Ajax requests. It's fast, compatible with every browser, and made super simple by built-in features of TurboGears. When you know you're getting JSON back in MochiKit, you can use loadJSONDoc rather than doSimpleXMLHttpRequest. If you do, your result comes into your callback function directly as JavaScript objects rather than an XMLHttpRequest object that you need to inspect. loadJSONDoc is not a huge feature, but it's another one of those conveniences that makes MochiKit fun to use.

15.1.7. Working with Timers

Although this chapter is mostly about Ajax, it turns out that some of the same concepts apply to timers. The section on Deferreds talked about how a Deferred object is a promise for something that will happen later. By definition, when you put a timer on a web page, you're saying that you want something to happen later.

If PizzasForYou offers a 30-minute delivery guarantee, the order confirmation page can pop up an alert when the pizza should have arrived. Here's how to do that:

function pizzahere(pizza_description) {     alert("Your " + pizza_description + " pizza should have arrived!"); } // The following function would likely have been called from a button // press that confirms the order function orderpizza() {   // Look up the element that's displaying the description   // Which would look something like:   // <div >Large Pepperoni</div>   pizza_description = $("description");   halfhour = 60 * 30; // seconds * minutes   d = wait(halfhour, pizza_description);   d.addCallback(pizzahere); }


The wait() function takes the length of time to delay (in seconds) and the result to pass in when the wait is over. It returns a Deferred, so it works just like the Ajax calls we've made previously. After 30 minutes, our pizzahere function will be called, displaying the description of the hot and tasty pizza that should have arrived. Woe unto the poor pizza delivery guy who didn't make it there before that alert pops up!

MochiKit also offers a callLater convenience function that can eliminate a line of code in the preceding example. Instead of separate calls to wait and addCallback, you can call callLater(halfhour, pizzahere, pizza_description). callLater takes the length of the delay, the function to call, and the arguments to pass into that function. callLater is also convenient, because you can pass multiple arguments into your callback function.

15.1.8. Canceling Deferreds

What if that large pepperoni pizza ordered in the preceding section has arrived? Do you still want the 30-minute timer alert box to pop up? Probably not.

Deferreds have a cancel method that allows you to cancel the Deferred before it has received a value. Both wait and callLater return a cancelable Deferred. If you hang on to the Deferred in a variable that's available to scripts on the whole page, you can create a "My Pizza Has Arrived" button that calls d.cancel() when the button is pushed.

15.1.9. Ajax, Timers, and Cancellation Combined

What if you want to apply a timeout to an Ajax call to gracefully handle the case where the user loses Internet connectivity or your server becomes otherwise unavailable? It becomes trivial if you combine the tools we've discussed in these last few segments:

d = loadJSONDoc("your_url"); callLater(30, d.cancel);


This adds a silent timeout. If you want an alert error message, you can just use JavaScript's closures to create a function that does the right thing:

d = loadJSONDoc("your_url"); callLater(30, function() {     if (d.fired == -1) {         alert("Unable to talk to the server!");         d.cancel();     } });


This example checks the value of the fired attribute of the Deferred. fired starts at -1, which reflects that it has not yet called its callbacks or errbacks. Zero means that it has completed successfully, and 1 means that there was an error. If the JSON data has not been loaded in 30 seconds, this will display an alert message and cancel the JSON request.

15.1.10. More about Callbacks

Early in this chapter, you saw how easy it is to add a callback and an errback (a callback for errors) to your Deferreds. What we didn't talk about is what happens when you have more than one callback. Callbacks and errbacks are appended to a list, each element of which is called in order when the result arrives or the pending event occurs. Each callback gets the result of the previous callback as its parameter. This allows you to do useful transformations of the data that comes in.

For an example of this in action, we need look no further than MochiKit itself. We talked about using doSimpleXMLHttpRequest to perform an Ajax request. That function returns a Deferred that is later called back with the XMLHttpRequest object. loadJSONDoc effectively uses the chained callback feature of Deferred to return the JavaScript object rather than an XMLHttpRequest. Here's an approximation of what loadJSONDoc does:

var loadJSONDoc = function(url) {     d = doSimpleXMLHttpRequest(url);     d.addCallback(evalJSONRequest);     return d; }


The initial Ajax result arrives and gets post-processed by evalJSONRequest before your own callback sees the result, so you only need to worry about the JavaScript object that comes back.




Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net