Skip to main content

Busy status indicator with JSF 2

Posted by driscoll on September 2, 2009 at 10:58 AM PDT

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 <html xmlns="http://www.w3.org/1999/xhtml"
   2       xmlns:ui="http://java.sun.com/jsf/facelets"
   3       xmlns:h="http://java.sun.com/jsf/html"
   4       xmlns:f="http://java.sun.com/jsf/core"
   5       xmlns:ez="http://java.sun.com/jsf/composite/busystatus">
   6 <h:head>
   7     <title>Busy Busy</title>
   8 </h:head>
   9 <h:body>
  10     <h:form id="busyForm">
  11         <h:inputText id="in" value="#{string.string}">
  12             <f:ajax render="out"/>
  13         </h:inputText><ez:busystatus id="busy" for="busyForm:in" /><br/>
  14         <h:outputText id="out" value="#{string.string}"/><br/>
  15         <h:commandButton type="button" value="Click Me"/>
  16     </h:form>
  17 </h:body>
  18 </html>

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 <h:outputScript name="jsf.js" library="javax.faces" target="head"/>
   2 <h:outputScript name="busystatus/busystatus.js" target="head"/>
   3 <script type="text/javascript">
   4     busystatusdemo.init("#{cc.clientId}", "#{cc.attrs.for}");
   5 </script>
   6 <span id="#{cc.clientId}" style="display:none;">
   7    <h:graphicImage id="busyindicator" height="15" width="15" name="busystatus/spinner3-greenie.gif"/>
   8 </span>

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.

UPDATE: Ed Burns, in the comments, recommends http://www.ajaxload.info as a great place for all your spinning gif needs.

Related Topics >>

Comments

Not Sure how to Execute

Hi I am new to JAVA and JSF i have downloaded the source code, but iam not sure how to execute the project and see the example working, i will really appricate if you could tell me how run it eclipse.

Global Spinner?

Hey Jim, How would you adapt this to apply to all ajax events on a page -- like a global ajax status indicator? --Lincoln

I'm guessing you would

I'm guessing you would probably remove the lookup for the componentId, and just display a status or not... that should work. I'll try it out and let you know.

Only one problem... it

Only one problem... it doesn't seem to work for ajax events triggered with instead of jsf.ajax... if (!window["busystatus"]) { var busystatus = {}; } busystatus.onStatusChange = function onStatusChange(data) { var status = data.status; if (status === "begin") { // turn on busy indicator document.body.style.cursor = 'wait'; } else { // turn off busy indicator, on either "complete" or "success" document.body.style.cursor = 'default'; } }; jsf.ajax.addOnEvent(busystatus.onStatusChange);

Sorry, it works for me

Not sure what problem you may be having - it does work for me. And the f:ajax tag is how I'm doing it... Have you tried doing a trace with Firebug and seeing what exactly is going wrong in your code? Is it that the onStatusChange function is never called? That seems unlikely.

Small Correction

The site to check for the Load Animation Gif generator is http://www.ajaxload.info/ not ".com"

Thanks

The post's been updated. Thanks for pointing that out.

get more spinners here

Very useful component! I learned of a spinner gif generator from
following Leah Culver on twitter. [1]

JD> I had to use the rendered ID (busyForm:in) rather than the JSF id (in)

We have a long standing issue about this [2]. I'd love to see it.
Please vote for it.

[1] http://twitter.com/leahculver/status/3455840933

[2] https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=49

Not really a solution

Unfortunately, issue 49 seems to address a different issue, server side resolution of component ids - that won't help for the client side. But I'll have to think about how to do this in the js api, and file a spec issue if I come up with an idea.