Thursday, September 29, 2011

<af:query> loading screen / spinner pop-up v2.0

This is an update/follow up on a previous post about displaying a spinner or loading screen while a query is in progress. The original post can be found here. This post describes a much simpler method for getting the same result, and addresses a flaw in the original approach. Its a direct adaptation of the method described by Frank Nimphius in the ADF Code corner example.

The general idea here is to

  1. Execute a custom piece of JavaScript code when user hits 'Search' in af:query
  2. In our JS code, add a busy state listener to fire when the busy state changes.
  3. Show hide a popup based on the busy state.

The ADF code corner example is based on a normal command button. But in the case of af:query we don't have access to the command button that fires the search. In this case, we can use a Client Listener on the af:query that fires on the "query" event.  like below :


       



The example above, the JavaScript function 'fireLoadingScreen' is a custom JavaScript function and  is implemented as below :

          function fireLoadingScreen(evt){
            var source = evt.getSource();
            var popup = AdfPage.PAGE.findComponentByAbsoluteId('spinnerPopUp');
            if(popup != null ){
              AdfPage.PAGE.addBusyStateListener(popup,handleBusyState);        
              evt.preventUserInput();
            }
          }
          
          function handleBusyState(evt){
            var popup = AdfPage.PAGE.findComponentByAbsoluteId('spinnerPopUp');        
            if(popup!=null){
              if (evt.isBusy()){
                popup.show();   
              }
              else if (popup.isPopupVisible()) {
                popup.hide();
                AdfPage.PAGE.removeBusyStateListener(popup,handleBusyState);
              }
            }
          }



The complete example can be downloaded from here : http://myadfnotebook.googlecode.com/files/LoadingScreen2.0.zip

One word of caution. There are implications to using AdfPage.PAGE.findComponent in conjunction with JSF fragments. One way of working around the limitation described in this previous post is to use  

function fireLoadingScreen(evt){
        var source = event.getSource();
        var popup = source.findComponent('spinnerPopUp');
        if(popup != null ){
            popup.show();
        }
        

This uses the source UIComponent object to search for the popup component (not from the root of the document). So the search is relative to the af:query. But now you'd have no way to close the popup but to use the old backing bean based method though.

For those interested, lets look at what was actually wrong with the original approach ( other than being very convoluted )...
While the original post still holds good for a lot of situations (especially if you use jsff and are deploying the app as a portlet) , when specifically applied to af:query, it has one drawback that I recently came to know about. This drawback will be apparent only in a load balanced and highly available environment. If you use a load balancer or fail-over system, there is a chance that the user's session might be migrated to another server while a query is happening. When that happens, things go bad if you use the approach detailed in the older post . Lets see why.


Flow of events for the original solution


The original approach can be applied generically to normal command components or af:query (though there are other more declarative ways of doing this with command components). This method is useful if upon clicking a command component if you want to execute some logic in your backing bean to choose a specific pop-up/loading screen (our customers wanted different types of loading screens for various types of input specified by the user in the af:query ). What makes the implementation different for af:query vs. a normal command button is that in af:query you don't have direct access to the search button (unlike a normal command button). So we had to intercept the search action and hook in to it (step 1). The idea was to fire a user-input blocking pop-up by hooking in to the search action (but not start the search yet) (step 2), then fire the actual search from the pop-up once its visible (steps 3,4,5). In doing that we had to save the QueryEvent as a member variable in the backing bean. The query event would be provided to the method registered as the query listener (step 1), but since we don't actually do the query from that method(we just launch the popup from here) and fire the actual query from the pop-up using a second method(step 6), we need to make the QueryEvent object available to that second method (in step 6). And this is not a simple case of calling one method and passing a parameter to it. The second method is invoked using java script code to call back the backing bean. So we had to store the QueryEvent object and guess what, it is not Serializable ! So in the event a session is replicated, the serialization of the backing bean would fail. At this point one might think we can just make the QueryEvent transient and get around the serialization; But that door opens to a far more subtle and dangerous problem. Suppose we mark it transient, and there are a thousands of users on our load balanced system executing searches. The load balancer decides to move some sessions across to another server, and lets say it just happens to pick a session where a user just clicked the search button, and we've just saved the QueryEvent and opened our loading screen popup (step 1 complete). The entire session is serialized and replicated on the second server, and when the session is inflated on the second server, since we marked the QueryEvent as transient , the QueryEvent is set to null on the second server. Now things resume, but when we reach step 6 in our flow, instead of seeing a QueryEvent we would see null. What makes this problem really frustrating is that since the load balancer is involved, the issue will not be easily reproducible and it would be very difficult to trace.

As you can see, the new solution makes the whole process a lot simpler actually. As long as you have a single loading screen you want to display whenever the search button is hit, this solution is much better that the original one. 

Friday, September 23, 2011

Gotcha when executing Javascript from a backing bean.

Earlier I had discussed about how to inject and fire JavaScript on the browser from a backing bean. This is an invaluable tool for some corner cases and can make life easier , however there is a gotcha here when we try to do things in a sequential order after we inject the JavaScript. Its a subtlety that would go unnoticed for most people too, so that makes it noteworthy since knowing this can save us a lot of time spent on debugging.

Take a look at the code below : 
FacesContext fctx = FacesContext.getCurrentInstance();
StringBuilder script = new StringBuilder();
script = script.append("alert('Calling Service');");
Service.getRenderKitService(fctx,ExtendedRenderKitService.class)
.addScript(fctx,script.toString());


The code when set as the action for a command component will execute and display a browser dialog with the message "Calling Service". Lets say there are things you want to execute after the browser popup is launched like call a WebService or execute a query. So you make the code :
FacesContext fctx = FacesContext.getCurrentInstance();
StringBuilder script = new StringBuilder();
script = script.append("alert('Calling Service');");
Service.getRenderKitService(fctx,ExtendedRenderKitService.class)
.addScript(fctx,script.toString());
WebServiceProxy.invokeWSmethod() // assume we call a synchronous WebService.


What one might expect to happen is for the message to appear and the web service to be invoked(in that order). But what happens in reality is that the web service is invoked and then the message appears.This is because the java script is executed only after the method finishes (I'm assuming here that it executes when the call stack is empty). This order of execution is relevant when we want to use JavaScript to show a dialog or an af:popup that should be triggered first(executed on the browser) before executing an operation. The obvious solution for this would be to split up the javascript and the actual operation in to two methods on your backing bean and just run the JavaScript part first and then use a call back method in your backing bean (using a server listener) to run the actual operation. A good example of how this can be done in practice is described here.

Thursday, September 22, 2011

Gotcha when using AdfPage.PAGE to find components on your page

When using JavaScript with ADF, the AdfPage.PAGE.findComponentByAbsoluteId() and AdfPage.PAGE.findComponent() and invaluable tools. But there is a watch-out when using them in certain situations.  If you use JSF fragments , and probably you do (if you don't, then probably you should), then these functions might give you trouble. You might not be able to find the component (returns null/undefined) . In this case, you can try to look at the region that using your task flow and try to reconstruct the ID you need to be looking for. For example, if the ID for a command button is 'button1' and the jsff is in a task flow and the task flow is in a region with ID 'region1' on a jspx, then inspecting the button from firebug using Firefox, I would see 'region1:0:button1' . You could use that in your JavaScript, but  I don't really like that path though, because, if you end up portletizing your app, then you no longer have a .jspx, that would be created elsewhere, and you may not know/cannot depend on the how the actual run-time layout of the ID is going to be. The bottom line is you would need a backing bean method to find the actual run time ID of the component and then pass that to these functions. So effectively your Javascript gets moved in to the backing bean and you would be generating it at run-time (getting the real run-time IDs and setting them in to the function calls) and injecting the JavaScript and running it. Something like this would do the trick (example is from this post and it finds the actual ID for an af:popup on the page and calls popup.show() or popup.hide() ) :
public void toggleBusyPopup(boolean isShown){
    FacesContext context = FacesContext.getCurrentInstance();
    RichPopup popup = (RichPopup) JSFUtils.findComponent("busyPopup");
    ExtendedRenderKitService service =
      Service.getRenderKitService(context, ExtendedRenderKitService.class);
    if (isShown){
      service.addScript(context,
                        "var popup = AdfPage.PAGE.findComponent(\"" +
                        popup.getClientId(context) + "\"); popup.show();");
    }
    else{
      service.addScript(context,
                        "var popup = AdfPage.PAGE.findComponent(\"" +
                        popup.getClientId(context) + "\"); popup.hide();");
    }
    return;
  }