I've had a few requests on how to write a busy status indicator - you know, the little spinning ball that's there while an Ajax call is active, and which goes away once the request is complete. So, I spent about two hours today, and did just that - including putting it into a component so it's reusable. As usual, it involved no Java, and only a minimal amount of JavaScript.

First, I needed an animated gif for a spinning image - there were a number at http://mentalized.net/activity-indicators - I just picked one. They're all in the public domain, and there are other sites which offer similar animated gif spinners.

After that, I tried to imagine what it would look like in the using page. Something like this seemed appropriate:

1 6 7 Busy Busy 8 9 10 11 12 13
14
15 16 17 18 On line 13, you see a component, busystatus, with a single attribute, "for", which is pointing at the rendered ID of the component I want to monitor. Otherwise, it's a straightforward JSF Ajax app - Ajaxify the "in" component, write to the the "out" component. I had to use the rendered ID (busyForm:in) rather than the JSF id (in), because there was no easy way to do ID resolution inside the component, but we've had to deal with that often enough at this point that the difference shouldn't be too confusing.

We'll also have make sure that the Ajax request lasts long enough to visibly trigger the indicator - that's as simple as adding a Thread.sleep(2000); to the setString method of the bean referenced by #{string}.

With that out of the way, let's write the component. Here's the composite component implementation section (the interface section just refers to the "for" attribute, so there's nothing to see there):

1 2 3 6 7 8
Line 1 loads the jsf.js library, if necessary. We'll need it in the next file for listening to events - note that it'll get loaded anyway, from any f:ajax tag we use, but it's good practice to make sure that it's loaded before we try to reference it. Line 2 will load the JavaScript we've written for this component. We could have just put the script inline in the composite component itself, but then we'd bloat the size of the page unnecessarily if we used this component more than once in the page. What works best for performance is going to vary on case by case basis, but since we're trying to create a generally reusable component, this is probably the best way to do it. Lines 3 thru 5 call the init function for this component, which we'll use to associate the component ID with the for attribute: this is the same trick we use for almost every Ajax component on this blog, so again, this shouldn't be surprising.Lines 6 thru 8 define a span wrapping an image. The span is initially set to be invisible with a style attribute, and we'll make it visible via JavaScript calls once the ajax request is active. The image itself is loading the spinning animated gif as a resource - and it's in the same resource library as the component itself.So, to recap what's happening in this file: We load the required scripts, run an initialization function, and set up an invisible span holding the image we'll display later. Now, let's examine the last file for this component, the busystatus.js file that holds the functions that'll be doing all the work on the page:

1 if (!window["busystatusdemo"]) { 2 var busystatusdemo = {}; 3 } 4 busystatusdemo.onStatusChange = function onStatusChange(data) { 5 var status = data.status; 6 var componentID = busystatusdemo[data.source.id]; 7 if (!componentID) { // if there's no request to listen for this one component, then leave 8 return; 9 } 10 var element = document.getElementById(componentID); 11 if (status === "begin") { // turn on busy indicator 12 element.style.display = "inline"; 13 } else { // turn off busy indicator, on either "complete" or "success" 14 element.style.display = "none"; 15 } 16 }; 17 18 jsf.ajax.addOnEvent(busystatusdemo.onStatusChange) ; 19 20 busystatusdemo.init = function init(componentID, forValue) { 21 busystatusdemo[forValue] = componentID; 22 };Three sections here: Lines 1 thru 3 set up the namespace. Lines 20 thru 22 are the initialization function that creates a map between the component and the for attribute. Let's go over lines 4 thru 18, though, since that's doing the interesting bit...

On line 18, we're adding an event listener - after this call, whenever an event occurs, the onStatusChange function will be called with a single parameter. When that function is called, on lines 6 thru 10, we retrieve the id of the component that generated the event, and use it to look up the associated "for" value, and exit the function if there's no associated "for" value. Then, lines 11 thru 15, we check whether we're beginning the Ajax call, or ending it. If beginning, we display the gif - if ending, we hide the gif.

So, that's our very simple busy component. But please note that this isn't really done. For instance, by revealing and hiding the gif, we're actually altering the layout of the page - there's traditionally two different ways to deal with this: you can either swap between the animated gif and a blank, transparent gif of the same size, or use CSS to hardcode in the size of a span, which wraps the component that's having it's display set to none. Either would work, and both are really out of scope for this blog - my only goal for this blog was to just show you how to use the event to trigger changes that updated a status indicator. As usual, you can find the code for this blog in the Project Mojarra codebase, under the jsf-demo/ajax-components directory.


Questions? Please ask in the comments section, and I'll do my best to answer them.

More...