Blog

Creating an Ajax Component:Handling Errors and Loading Notifications with Publish and Subscribe

This is the second post in a multi-part series on creating a JavaScript component for handling your Ajax requests in front-end development across your enterprise. You can find the first post here:

In the last post we covered some introductory topics for creating your own JavaScript utility library, which wraps functionality for Ajax. We’ll start where we left off. Our first step will be to add a few tweaks to our library to make it more usable:

var mySiteAjax = ( function( $ ) {
  return (
     function( params ){
      var settings = $.extend({
        url: "",
        spinner:  undefined,
        dataType: "html",
        data: "",
        type: "GET",
        cache:  false,
        success:  function() { }
      }, params );

<pre><code>  $.ajax({
      beforeSend: function() {
        $( settings.spinner ).show();
      },
      url: settings.url,
      dataType: settings.dataType,
      type: settings.type,
      data: setting.data,
      success: settings.success,
      complete: function() {
          $( settings.spinner ).hide();
        }
    });
}
</code></pre>

);
})(jQuery);

We’ve done two things to our old example. We’ve added new parameters for type and data that could be passed in. Additionally, we’ve removed a named function and returned an object literal. This enables easier usage:

mySiteAjax({
  url: "myUrl",
  success: function( data ) {
    //do something with the data here
  }
});

Although a good start, this solution is not yet satisfactory as we are not yet handling errors in any capacity. How do we handle errors and display messages to the user? Additionally showing and hiding of loading notification images is present but not customizable. What if a page doesn’t want to do a simple showing and hiding of load images? How can we decouple the notifications from our Ajax component library?

Handling and Displaying Errors

When dealing with Ajax errors there are typically two concerns – handling the error in code and displaying an error message to the user.

Handling the Ajax Error

A popular technique for “handling” Ajax errors is to either never add an error callback for the Ajax request, or to end up with code like this (including the todo comment):

$.ajax({
  //...
  error: function() {
    // todo: handle error here.
  },
  //...
});

Most of us have been there. It is often a struggle to determine what reasonable actions can be done with an Ajax error. Yet there are some options. A few pragmatic actions we could take upon Ajax error:

  • Try again (especially for GETs).
  • Have debug code for non-production code which logs the error details to the console.
  • Have an error specific Ajax request URL (either global for all sites or specific to your application). This activity is typically a request in which data is sent to the server and the client does not care about the response. This is typically used to log the issue to a server-side resource.
  • Automatically take action based on HTTP Status Code. We can detect certain error codes from the server response and take action. For example a 401 or 403 error may be an indication of expired client credentials. In this case we could redirect the user to a login page to establish new credentials.
  • Suggest to the user next steps as part of the error message.

It would be prudent for our utility library to take some actions based upon HTTP status code. If the user request comes in as unauthorized we will redirect to a new page. If there is a server timeout we will try the Ajax request a second time.

To accomplish this we need to add an error callback function to .ajax(). We are also going to add another programming technique to repeatedly call .ajax(). First the code:

var mySiteAjax = ( function( $ ) {
  return (
    function( params ) {
      //...
      var retries = 0;
      function ajaxRequest ( ) {
        $.ajax({
          beforeSend: function(){
            $(settings.spinner).show();
          },
          url: settings.url,
          dataType: settings.dataType,
          success: settings.success,
          complete: function(){
            $( settings.spinner ).hide();
          },
          error: function( xhr, tStatus, err ) {
            if( xhr.status === 401 || xhr.status === 403 ) {
              //redirect action here
            } else if ( xhr.status === 504 && !retries++ ) {
              ajaxRequest();
            }
          } // end error handler
        }); // end $.ajax()
      }; // end ajaxRequest()
      ajaxRequest();
    } // end getViaAjax()
  ); // end return statement
})(jQuery);

We’re using the status property of the XmlHttpRequest object to determine the type of error encountered. With our authorization issues we can detect an authorization error status code and redirect to our authorization page. Handling retries requires us to call the original Ajax call again. To do that we can use recursion.

In our case of recursion we are setting up a retries variable to determine the number of Ajax requests we setup. retries is declared outside of the recursive function. We can use and increment the retries variable inside of our function because of lexical scoping. If retries is 0 and a timeout error code occurs, we increment retries and recursively call ajaxRequest. The ajaxRequest function then sets up a another ajax request. If another timeout occurs retries is now set to 1 which will keep another recursive call from happening.

After we’ve created our ajaxRequest function we then execute it to kick off an initial Ajax request.

We’re not using the global event handler, .ajaxError(). This is because we only want our error handling to affect those calls which come through our component. If we attach a handler globally using .ajaxError() then all ajax errors will result in the execution of our callback. While you may want that behavior in your application, it’s generally considered bad practice to introduce code with side effects.

This example gets us started down the path of retries, but isn’t a complete solution. Other good additions would be to allow the number of retries to be specified as a parameters, and to add timers to throttle retries.

Displaying Error Messages to the User

Notifying the user of the error involves interaction with another component on the page (you can find an example of creating such a component with our message center plugin blog posts 1 and 2). We could set up another parameter to be passed as an error callback function. However, it seems like we’re quickly going parameter-happy.

We’re already expecting the calling code of our utility function to know about the load notifications (spinners) that may be on the page. We’d be adding yet another responsibility of the callee to know which and how many messaging components may be on the page and would need to be notified of an Ajax error. This puts pressure on the code calling our Ajax library to be edited every time any messaging or loading notification widgets are added, edited, or removed from the page.

Paradigm Shift – Communicating Between Components with Publish/Subscribe

In our case we have three separate components that are trying to interact on the page:

  • An Ajax system
  • A ‘loading’ notification system
  • A flash message or message center

We need an effective way to communicate between components. In the case of our Ajax library, we need to notify any components that an error message is available to be displayed or that a load notification needs to start or end.

Introducing Publish/Subscribe

A great solution would be to broadcast over a channel that an error occurred or that loading has started. Consumers could register a callback to run when a message was broadcast over a specific channel.

One way to accomplish this is the publish/subscribe pattern. With this pattern a publish function typically exists which broadcasts using a message topic. A subscribe function is used to bind a callback function which will be executed when a message topic is broadcast. Data can be passed to the subscriber through an additional parameter.

If we compare a typical publish/subscribe system to a radio broadcast, the “message topic” would be the selected AM, FM, or Satellite station. The data being passed to the subscriber would be analogous to the audio coming through the radio.

Using Custom jQuery Events for Publish and Subscribe

We can illustrate publishing and subscribing by using jQuery custom events names. We can use the event name as our message topic and pass data using the additional parameters.

Lets add a mechanism to publish error messages as well a standard error message:

var mySiteAjax = ( function( $ ) {
  var standardError = &quot;Oops. Sorry about that.&quot;;
  return (
    function( params ) {
      var settings = $.extend({
        url:      '',
        spinner:  undefined,
        dataType: 'html',
        cache:    false,
        success:  function(){}
      }, params);

<pre><code>  var retries = 0;
  function ajaxRequest ( ) {
    $.ajax({
      beforeSend: function(){
        $(settings.spinner).show();
      },
      url: settings.url,
      dataType: settings.dataType,
      success: settings.success,
      complete: function(){
        $( settings.spinner ).hide();
      },
      error: function( xhr, tStatus, err ) {
        if( xhr.status === 401 || xhr.status === 403 ) {
          //redirect action here
        } else if ( xhr.status === 504 &amp;amp;&amp;amp; !retries++ ) {
          ajaxRequest();
        }
        $(document).trigger( &amp;quot;ui-flash-message&amp;quot;,
          [{ message: standardError }] );
      } // end error handler
    }); // end $.ajax()
  }; // end ajaxRequest()
  ajaxRequest();
} // end getViaAjax()
</code></pre>

); // end return statement
})(jQuery);

We’re publishing with a custom event named “ui-flash-message”. The .trigger() method takes in an array of parameters as a second argument. We chose to pass in a single hash (object literal) with our message attached.

Now that we’ve covered publishing, lets take a look at how a subscribe might be set up:

  // ... somewhere in a component that displays messages
  $(document).bind( "ui-flash-message",
    function(evt, data) {
      if(data.message) {
        showMessage(data.message);
      }
    };
   // ...

Our subscriber must be set up before a message is published. In this case as long as our message component has already been loaded and the above code has been executed, then the component is waiting for a message to be published.

Load Notifications

Our old implementation of load notification was for a selector to be passed in and our Ajax component would handle the showing and hiding of a ‘spinner’. This technique is not ideal. The Ajax component has no business implementing specific behavior for spinners (such as using .show() and .hide()). It would be much better to use our publish/subscribe methodology to allow other components to handle the behavior.

// we 'define' undefined to alleviate concerns
// that someone may have done something stupid
// like this in code before this code executes:
// undefined = &quot;Im now defined.&quot;;
// credit: Ben Alman (see comments)
var mySiteAjax = ( function( $, undefined ) {
  return (
    function( params ) {

<pre><code>  // use extend to merge our defaults with parameters
  // passed by function caller
  var settings = $.extend({
    url: &amp;quot;&amp;quot;,
    spinner:  {},
      // use empty object if version 1.3.2-
      // credit: Ben Alman (see comments)
    dataType: &amp;quot;html&amp;quot;,
    cache:    false,
    success:  function(){},
    errorMsg: &amp;quot;Oops. Sorry about that.&amp;quot;
      // credit: rmurphey (see comment below)
  }, params),
      retries = 0; // setting up retries variable
 // setting up a function that we can call recursively
 // to retry ajax calls
 function ajaxRequest ( ) {
    alert(&amp;quot;here&amp;quot;);
    $.ajax({
      beforeSend: function() {
        $( settings.spinner ).show();
      },
      url: settings.url,
      dataType: settings.dataType,
      success: settings.success,
      complete: function() {
        $( settings.spinner ).hide();
      },
      error: function( xhr, tStatus, err ) {
        if( xhr.status === 401 || xhr.status === 403 ) {
          //redirect action here
        } else if ( xhr.status === 504 &amp;amp;&amp;amp; !retries++ ) {
          //make our recursive request
          ajaxRequest();
        } else {
          $(document).trigger( &amp;quot;ui-flash-message&amp;quot;,
            [{ message: settings.errorMsg }] );
        }
      } // end error handler
    }); // end $.ajax()
  }; // end ajaxRequest()

  // call our ajax request function. notice above
  // that we only define the function. here we make
  // the original call.
  ajaxRequest();

} // end getViaAjax()
</code></pre>

); // end return statement
})(jQuery);

Now we can add a load notification component to our application, where we can handle showing and hiding spinners:

  // ... somewhere in a load component
  $(document).bind( "ui-load-start-message",
    function(evt, data) {
      $(data.spinner).show();
    });

$(document).bind( "ui-load-end-message",
    function(evt, data) {
      $(data.spinner).hide();
    });
   // ...

We now have much better separation of concerns. The Ajax component only publishes messages and isn’t concerned with implementation of a loading notification system.

I’ve set an example of this solution on a jsFiddle with a few use cases. Take a look at the example and feel free to play with the functionality on your own. Post in the comments if you find anything interesting!

Although you can create a message bus with jQuery custom events, there’s overhead and DOM interaction that isn’t necessary. Components exist today that allow for publish/subscribe to occur using only JavaScript. I encourage you to look for those components and use them for publish and subscribe interactions.

PostScript / Errata :
There have been some good comments below, so we’ve made a few changes:

  • The last full Ajax solution has been updated with suggestions and appropriate credit given.
  • The jsFiddle has been updated to reflect those changes.
  • Rebecca Murphey correctly pointed out that we are adding a variable to the global scope and that is something that should be minimized. A good approach would be to create a simple namespace (object) and add this utility library to that namespace. So the assignment at the top statement wouldn’t be mySiteAjax, but rather myCompanyNS.siteAjax or something similar. Note that you’ll need to ensure myCompanyNS exists and created an empty object if it does not already. We are still adding to the global scope, but one is better than many possible editions.
Tweet about this on TwitterShare on FacebookShare on RedditShare on Google+Share on LinkedIn

  • Mikestu

    I like the idea of a custom event in order to remove the spinner logic out of there. Can you explain a little more about the last paragraph? I have used custom events in a few places recently, so I'm curious as to what overhead they bring with them. I have not looked through the jQuery bind and trigger code, but I'm now curious. You mention DOM interaction, but is it a cost even worth worrying about? I mean, I'm using jQuery quite a bit already anyway, so a tiny bit more DOM interaction is probably no big deal to me. Thanks!

  • http://twitter.com/Poetro Peter Galiba

    Instead of passing an empty function like function() { }, you could write $.noop instead. It is just the same empty function, but it is shorter and still easy to read.

  • http://twitter.com/ltackmann Lars Tackmann

    JQuery fires the global ajaxError/ajaxComplete events on errors and completion, I think a more clean (decoupled) solution would be to handle those (especially by placing event handlers higher in the DOM tree to take advantage of event bubbling). Alternatively, as such error handling code is properly fairly general, one could just use $.ajaxSetup to set it up globally.

  • amWirick

    Hello Lars,

    We chose local events to ensure no side effects on code which does not use the utility library. Consider the case where you are adding a utility library to an already existing site. Global handlers could affect all of the Ajax calls on the site – even those that aren't ready to go through the utility library.

    For .ajaxSetup(), again we didn't want to cause global side effects. Also we will be adding settings in future posts beyond the normal ajax settings – so we will need a different way to pass in settings.

  • amWirick

    Peter,

    Good point. I prefer the empty function in this specific scenario (especially when illustrating code). I agree $.noop would be shorter, but I don't think using an empty function makes the code less readable. Even if someone doesn't understand the empty function outright, I like that someone seeing that code might question what is going on – they might learn some JS in the process!

  • http://twitter.com/rmurphey Rebecca Murphey

    I'm a big fan of pubsub, as I think it really helps keep separate components separate and independent, while allowing communication among them, and any components that get added to the application later.

    Custom events are one way to accomplish pubsub, but they come with a lot of event-related overhead that isn't strictly necessary for pubsub to work. Peter Higgins has a nice, super-lightweight, and more performant solution that provides pubsub functionality without that overhead. People may want to consider it as an alternative to custom events if they're interested in using this pattern: http://j.mp/9xCBFc.

  • amWirick

    Hello Mikestu,

    The cost really depends on the amount of Ajax calls. jQuery is a mature library that enables publish/subscribe (albeit indirectly). The problem is that we must attach an event to the DOM (document in our case) for subscription, and interact with the DOM for the publish. A more explicit approach would be to have a JavaScript library with no DOM interaction for pub/sub. Core Dojo has pub/sub that operates in this way (although it has the overhead of unnecessary code if you are using mostly jQuery). There are some other pub/sub standalone solutions out there as well. JavaScript MVC also has pub/sub.

    Stay tuned as more on this front may be happening in the future.

    Cheers,
    Andrew

  • amWirick

    Hi Rebecca,

    Thanks for pointing out Peter's solution. Agreed that jQuery custom events come with unnecessary overhead for pub/sub. We used it for brevity. Hopefully folks catch the final paragraph and look for explicit pub/sub libraries.

    Cheers,
    Andrew

  • http://twitter.com/coldfuser Drew Wells

    Could you leave some hints at useful pub/sub libraries. I keep hearing about pub/sub and finding that only obscure libraries provide them ie YUI, dojo

  • http://twitter.com/rwaldron rick waldron

    To be clear, I was not “Guest”. But you deleted this comment and that is censorship – just so happens that I had the window open in another tab.
    http://gyazo.com/4d29fa04092769a7233c9703c7358a01.png

  • Guest

    There are some serious, fundamental errors regarding how jQuery (and XMLHttpRequest) work in this article. I am honestly beside myself that something like this would be published by a group that claims to be “the jQuery company”.

    “We're publishing with a custom event named 'ui.flash.message'.” — No you aren't. You're using a custom event named “ui-flash” in the “message” namespace. Why does the word namespace not come up once in this article?

    “xhr.Status === “401″ || xhr.Status === “403″ — First the property is “status”, all lowercase, not “Status”. Second, the “status” property is a number, not a string, so performing string equality to a string would be wrong even if the property name wasn't.

    If you guys are going to continue publishing articles, you really, really need to fact check before posting.

  • amWirick

    Thanks Rick, post updated. Showed up as a temporary email acct, so we removed.

    We'll do a better job in the future of 100% unit testing all pieces of the code snippets before posting.

    Yes, we are using a namespace with “.message”. I've removed it for now to simplify the posts. I was prepping pieces for possible future posts which I admint can cause confusion in the interim.

  • amWirick

    Hello 'Guest',

    Addressed above with Rick's comment. When you wrote this, all fixes were already in place. thanks for checking.

  • http://twitter.com/rwaldron rick waldron

    yeah dude, just so you know – I really didn't write that. I just don't like censorship.

  • http://twitter.com/rwaldron rick waldron

    Also, it should be noted that in addition to not liking censorship, I equally do not like anonymous posts.

    Its not my style.

  • http://twitter.com/pomeh Pomeh

    Nice article. I like the way you implement the separation of concerns.

    I have a question about the snippet. Let's say you have two kind of ajax request: the one your user have raised by using the application, and one other, a sort of javascript task that makes ajax calls every X minutes and update a part of you page, silently. When the “update request” is raised, I don't want to show the spinner, and I don't want to notify the user if the request fails. Obviously, when it's a “user request”, the spinner should be shown and the error handling enabled. The events are related to the document DOM node, so when I subscribe to the “ui-load-start-message” event, I don't know which request is executing. So how can I make that stuff works as I want ? In this case, should the callbacks system be a solution ?

    “Although you can create a message bus with jQuery custom events, there’s overhead and DOM interaction that isn’t necessary”

    This is something true in this example. The events you're publishing are “global” and should not be related to a DOM node (which is not possible natively with jQuery custom events). But I'd just say that, in some other case, this relation between DOM and events is very useful, as you can bind an event to this particular element and only this one (or this collection of elements). I just want to mention that this is a problem here which can be a solution elsewhere.

    Here are two links about pubsub system, the first presents how events can save the universe (yes, really !) and the second shows what's wrong with the built-in jQuery custom event system and how to fix it. Have a look: http://slideshp:a.re/9TOn0M & htt//bit.ly/FyFHz

    Waiting for the next article ! :)

    Pomeh

  • amWirick

    Hello Pomeh,

    Great responses!

    For the first question – the jerk reaction would be to simply add a parameter to turn off load notifications on a per request basis.

    There is a better approach, which is one we haven't got to with Ajax requests. What if instead we separate out the definition of our server-side requests from actually making the requests? For example we could define a “normal Ajax” request type and also a “polling Ajax” request type? Then the responsibility of our site would be to wire up the appropriate request type with the requests being made. It would take an entire blog post to explain it – so more on this later.

    Also agreed that there are some DOM centric situations that could appear and use custom events that are triggered in code. This is not one of them.

    Thanks for the links, I will check them out!

    Cheers,
    amWirick

  • http://twitter.com/bjarkih Bjarki

    There is a small naming problem with your custom events you register the hide event “hui-load-local-message” but invoking the “ui-load-end-message” event

    $(document).trigger( “ui-load-end-message”,…..
    $(document).bind( “ui-load-local-message”,…..

  • amWirick

    Hello Bjarki,

    Great catch. Post updated.

    –Andrew

  • http://twitter.com/rmurphey Rebecca Murphey

    I just got a chance to read this in a bit more depth, and had a few questions/thoughts. Some of these may be personal preference, but I think they're all important to discuss in regards to code such as this that's being used in an “enterprise” environment:

    - Re $(settings.spinner).hide(): the value of settings.spinner could very likely be undefined, as that's what it is by default. While .hide() doesn't have a huge cost, it's generally not a good practice to call methods on empty selections (see Paul Irish's presentation at 2009 jQuery Conference, I think), and it's easy enough to test whether settings.spinner is defined: settings.spinner && $(settings.spinner).hide()
    - Re scope: it could be useful for readers to understand that the mySiteAjax function will exist in the global namespace as you've defined it, which may not be desirable; perhaps it should it be “namespaced” instead, i.e. as myApp.ajax or similar?
    - Re $.ajax config: why not build up a settings object that can be passed directly to $.ajax, rather than building up a separate object and then using its properties in another object passed to $.ajax? Also, wouldn't it make sense for the error message to be configurable rather than hard-coded into the function?
    - Code org nitpick: the success and error functions are probably large enough that they should be defined as their own named functions inside the closure, rather than passed as anonymous functions via the config object.

  • http://benalman.com/ "Cowboy" Ben Alman

    I've noticde a few issues with this article.

    The example's code is somewhat hard to read. It's hard to tell, just by looking at the code, what it does. It's clear that AJAX is involved, but how? More useful in-line comments would be supremely helpful, for starters. A better syntax highlighter would also help.

    I'd like to see a globally accessible “options” object, with a per-invocation optional “options” object as the function argument. This way, the default options object can have sensible default values that don't have to be hard-coded into the plugin, but can be used for every subsequent mySiteAjax call.

    Don't use undefined in your code without defining it explicitly, as its value can be changed. Considering the number of people of varying technical abilities often writing code on the same “enterprise” project, it's critical to both minimize “leaks” in your code and succeptibility to “leaks” from others' code.

    A warning here: since settings.spinner is undefined by default, calling $(data.spinner).hide(); is very bad practice, considering this website is “Enterprise jQuery” and many enterprises are still using jQuery 1.3.2 or earlier. While $(undefined) in jQuery 1.4.2 returns an empty set, $(undefined) in jQuery 1.3.2 returns a set with the document selected. Performing an arbitrary method on document is a very bad idea, in general, and should be avoided. Acceptable: passing a selector to $() that will return 0 elements. Unacceptable: passing undefined to $().

  • amWirick

    Hi Ben,

    Thanks for the comments. I'd agree that more comments would be helpful – and the current highlighting the comments seem to fall into the background.

    We actually have a globally accessibly options object slated for the next post (one of two concepts we hoped to cover). I look forward to your responses to how we organize the global setting object.

    Good points on undefined. An empty object would have been a better choice in the first place as defaults. We'll update (and give you credit).

  • amWirick

    Hi Rebecca,

    - Good point on testing for undefined – I'll add it into the code and give the appropriate credit.
    - I'll add a postscript section – a good addition worth noting.
    - We heavily considered using a setting object that can be passed directly to Ajax instead of this methodology – http://jsfiddle.net/paGJR/5/ . We didn't go with a direct pass to Ajax as future posts will include a lot of additional parameters. It brings up a good design pro/con. Do you pass in an object that have a superset of properties to .ajax()? What happens if you use a property that is added to .ajax() in the future and causes a side effect?
    - I think you are referring to 'complete' and 'error' callbacks. I'll move error and credit you appropriately. I think that is a worthy technique as well.

    Thanks for all the feedback!

  • amWirick

    So when actually putting all the changes in code I have two opinions that I left out.

    - I understand the undefined check in one sense, but what kind of performance bottleneck are we really looking at here? I've seen good coders leave out the undefined check often when there is an acknowledgement that a tiny spec of performance may be lost.
    - Adding a named function and using it for the error callback seemed more verbose than necessary. It's a valid technique – but the function is only 5 lines. I don't think that warrants pulling it out and naming it. If jQuery core used that methodology, there would be a lot of named functions.

  • amWirick

    Comments only go 3 deep – so this is in response to Drew Wells.

    - I wouldn't consider YUI or Dojo as obscure – they are fully functional libraries with high quality code and worth some of your time to learn how they function on at least a basic level.
    - Check out library referenced by Rebecca and written by Peter Higgins. It doesn't take a lot of code to implement pubsub in its most basic form. Heck, you could fork it and write a few unit tests to ensure basic functionality – I'm sure he'd appreciate the help.

  • Garrett

    Why “Enterprise” Business”? Is there a special technique here that makes this inappropriate for other businesses?

    I see two extraneous nested functions, which result in an augmented scope chain.

    In spending < 1 minute scanning this article, I see the use of the jQuery script. I see that “ajaxRequest” being called “XmlHttpRequest” litterally, and given that you're not using XMLHttpRequest (note the capitalization), it is fair for the reader to presume that you're so unfamiliar with using it driectly that you don't know the proper capitalization. I see a non localized error message string that, even in English, is uninformative.

    Your tutorial is not suitable for any business; enterprise or otherwise.

    I realize that this comment may not be the type of response that you wish the elicit and so if. you decide to censor this comment, I'll post it on c.l.js.

  • Rrodriguez

    Hi Andrew,

    Great post, IMO, and great discussions. Any idea on when we can see upcoming parts? Very interested on see how the final solution looks like.

  • Rrodriguez
  • Rrodriguez

    Taking a deeper look into pubsub stuff, I also found that the Rx team from MS has it RxJs which delivers a LOT of power when it comes to event handling and composition.
    http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

    I'm not sure this is something everybody wants to use, especially for common problems that can be addressed really easy with the tools out there, but when your requirements get dirty in terms of GUI and event handling, I think this can be pretty handy.
    Just thought it worth mentioning.

  • http://sindilevich.livejournal.com/ Matanel Sindilevich

    Isn't it that the final example actually omits triggering the “ui-start-message” and “ui-end-message” events?