Oracle ADF Code Corner: How-to build a reusble glasspane in ADF Faces RC

How-to build a reusable Glasspane in ADF Faces RC

Ever met a patient application user? If not, don't worry, they just don't exist. Users work with an application to get their job done in the shortest time possible. So clicking a button and then sit and wait for a response to show is not their kind. If an application is busy with a background process - e.g. querying the database for a complex query - then after a certain amount of waiting time, users tend to use their mouse to click somewhere in the application, just to show that they are still waiting or to force the application to come back to him. User activity like this can lead to many unwanted effects: double form submission or queued events that the user didn't want. The only way out of this is to "disable" the user interaction for the time the background process takes. While the ADF Faces RC command button exposes a blocking property that - when set to true - prevents users from double submitting an action, it doesn't prevent him from clicking anywhere else in the application. Users want entertainment while they wait, so ideally while blocking them from working in the application, they see something animated. A Glasspane is a transparent overlay that prevents users from interacting with a screen during the time of background processing. In ADF Faces RC there is no native Glasspane component available, like for example in Swing, but with a few on-board functionality and components, this component can be built as a declarative custom component. This how-to document explains the principles of building a reusable Glasspane component in ADF Faces RC.

Written by Frank Nimphius, Oracle Corporation
Original 15-July-2008
Update 05-Aug-2008 - added improvement from Anfrejus Baranowski
Update 20-Nov-2008 - a native glasspane functionality has been added to the production release that I explain at the end of this page

Introduction

The way that a Glasspane works is that it is invoked at the beginning of a process and closed when the process returns. In Swing, because all processing and visuals are on a single desktop, you could use listeners to initiate and close the Glasspane. In ADF Faces RC, there is a specific architecture needed to get the Glasspane working. A command component, like a button, initiates the action, which could be a query request, executing an action on the ADF binding. However, to make sure the Glasspane isn't blocked by the query, the action must be started when the Glasspane is visible. For this, the Glasspane invokes the query through a callback mechanism implemented using a serverListener. A declarative component can expose a method signature as its property, which allows developers to bind a method in a managed bean to it. So whenever the serverListener in the Glasspane component fires, it actually invokes a method on the application's managed bean. More later in this how to document.

Once the processing is finished, the application calls back into the Glasspane component to stop the user blocking.

The example above shows how the Glasspane overlays the main window. The implementation of the Glasspane is implemented with a modal dialog.

Building a Glasspane

In ADF Faces RC, a popup component (af:popup) can have a dialog as its child component (af:dialog). The dialog has an option that displays it as a modal dialog, which means that it blocks the user from working until the dialog closes. To prevent the user from closing the dialog, the af:dialog properties can be used to hide all closing elements.

 1. <af:popup id="busyPopup" clientComponent="true">
2. <af:dialog closeIconVisible="false" modal="true" type="none" id="mdialog">
3. <af:panelGroupLayout layout="vertical">
4. <af:outputText value="#{attrs.busytext}"/>
5. <af:facetRef facetName="image"/>
6. </af:panelGroupLayout>
7. </af:dialog>
8. <af:serverListener type="handleCommandProcessing"
9. method="#{component.handleLaunchAction}"/>
10. <af:clientListener method="onPopupOpened" type="popupOpened"/>
11. </af:popup>

In line 1 the popup dialog is defined with a component ID of "busyPopup" and the clientComponent property set to true. The Id property is required to launch the dialog, the client component is needed to find the popup.

In line2 the dialog window is specified, hiding all the close functionality, which means that the dialog must be closed programmatically.

The output text component in line 4 references an attribute of the declarative component, which is exposed in the JDeveloper IDE property palette when adding the component to a JSF page. This way application developers have the ability to customize the message string for the user.

Line5 is the "Hollywood" line and responsible for the end user entertainment. The declarative component has a facet definition that application developers can use to add an animated image or any other component they like to show to the user while waiting.

The serverListener in line 8 is invoked by JavaScript as soon as the dialog is launched. It is bound to a method attribute in the declarative component, which is exposed as a component property in Oracle JDeveloper. Application developers will use this property then to invoke a custom method in a managed bean of the application.

Line 10 notified the JavaScript function that the popup dialog is opened and that the background processing can begin. The JavaScript function it calls is shown below

  <![CDATA[
<script>
function onPopupOpened(evt){
AdfCustomEvent.queue(evt.getSource(), "handleCommandProcessing",{}, false);
}
</script> ]]>

The code above calls the serverListener, with no arguments passed as a payload. The remaining page code of the declarative component is the definition of the attributes that are referenced above

 1. <af:xmlContent>
2. <component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
3. <display-name>GlassPane</display-name>
4. <facet>
5. <description>the image to indicate that the component is busy</description>
6. <facet-name>image</facet-name>
7. </facet>
8. <attribute>
9. <attribute-name>busytext</attribute-name>
10. <attribute-class>java.lang.String</attribute-class>
11. </attribute>
12. <component-extension>
13. <component-tag-namespace>fnimphiu.sample</component-tag-namespace>
14. <component-taglib-uri>/fnimphiu/sample</component-taglib-uri>
15. <method-attribute>
16. <attribute-name>launchAction</attribute-name>
17. <method-signature>void method(oracle.adf.view.rich.render.ClientEvent)</method-signature>
18. </method-attribute>
19. </component-extension>
20. </component>
21 </af:xmlContent>

Line 9 defines an attribute of type String that will receive the text message to display while the application is busy

Line 13 defines a method attribute that expects an argument of type ClientEvent. The client event is automatically passed from the serverListener and may be used by application developers to e.g. access a payload from the client.

The codes above make the declarative component that can be deployed as an ADF library for reuse. One last class needs to be introduced here though. Its a helper class that the application developer uses to open and close the Glasspane from a managed bean

package fnimphiu.sample;

import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service;

public class GlassPaneHelper {
    public GlassPaneHelper() {
    }
    
    public static void openGlassPane(){
        ExtendedRenderKitService erks = Service.getRenderKitService(FacesContext.getCurrentInstance(),
                                        ExtendedRenderKitService.class);
        
        StringBuilder strb = new StringBuilder("AdfPage.PAGE.findComponent(\"gp:busyPopup\").show();");
        erks.addScript(FacesContext.getCurrentInstance(),strb.toString());
    }
    
    public static void closeGlassPane(){
        ExtendedRenderKitService erks = Service.getRenderKitService(FacesContext.getCurrentInstance(),
                                        ExtendedRenderKitService.class);
        
        StringBuilder strb = new StringBuilder("AdfPage.PAGE.findComponent(\"gp:busyPopup\").hide();");
        erks.addScript(FacesContext.getCurrentInstance(),strb.toString());        
    }
}

The GlassPanelHelper class exposes two static methods. Both methods use the Extended RenderKitService in ADF Faces RC to issue JavaScript commands that are executed on the client. The JavaScript code looks for a popup with the Id of "gp:busyPopup" and opens or closes the dialog. Note the "gp" in front of the popup name. We will come back to this later.

Adding the Declarative Component

After deploying the declarative component as an ADF library, the resulting JAR file can be configured in Oracle JDeveloper as a library resource. Open the resource catalog in JDeveloper by pressing ctrl+shift+O. From the menu, press the "New" folder icon on the left side of the search field. Choose New Connection --> File System. In the dialog that opens, select IDE Connections, define a name for the component library and point the Directory Path to the directory that contains the reusable ADF library jar file. In the example workspace provided with this how-to, the JAR file is in the deploy directory of the BlockingCommandHint\GlassPaneProject folder.

After importing the library, the resource palette should show the following entry:

To install the component for an application project to use, select the project's node so it is highlighted, then click on the GlassPane.jar entry and choose Add to Project.

Once the library is imported and installed, the Component palette shows a new entry as shown below

The GlassPane component is now ready to be used in a custom application project

Using the GlassPane component

The example project has a page with a button in it. The button will trigger a delay in the response, and after configuring it with the GlassPane static method, also invoke the GlassPane.The code of the JSPX file is shown below:

 1. <?xml version='1.0' encoding='windows-1252'?>
2. <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
3. xmlns:h="http://java.sun.com/jsf/html"
4. xmlns:f="http://java.sun.com/jsf/core"
5. xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
6. xmlns:fns="/fnimphiu/sample">
7. <jsp:directive.page contentType="text/html;charset=windows-1252"/>
8. <f:view>
9. <af:document>
10. <af:form>
11. <af:commandButton text="Press Me To Block" partialSubmit="true"
12. actionListener="#{testBean.onAction}">
13. </af:commandButton>
14. <fns:GlassPane busytext="I am busy Dude!"
15. launchAction="#{testBean.queryToLaunch}" id="gp">
16. <f:facet name="image">
17. <af:image shortDesc="I am busy man ..." source="/busy.gif"/>
18. </f:facet>
19. </fns:GlassPane>
20. </af:form>
21. </af:document>
22. </f:view>
23. </jsp:root>

Line 11 defines the command button that has an action listener configured to call the onAction method in the managed bean.

Line 14 defines configures the GlassPane custom component. Important is that the Id property is set to "gp". This is needed for the JavaScript code in the declarative component to find the popup. Of course, this could have been made configurable, but for this example, hard coding the "gp" strings is good enough. Also in line 14, developers can specify the message that is shown to the ser while waiting.

Line 15 defines a value for the launchAction property. The launchAction property is a method property and expects a ExpressionLanguage reference to a managed bean method that has the following signature: public void queryToLaunch(ClientEvent evt) { ... } The ClientEvent argument is passed from the declarative component, which receives it from the serverListener, to the managed bean method. Using the declarative component that comes with this example, the method signature cannot be changed.

In line 16 the application developer added an image object referencing an animated gif to the facet defined by the declarative component.

The managed bean class looks as shown below:


package fnimphiu.sample;
import javax.faces.event.ActionEvent;
import oracle.adf.view.rich.render.ClientEvent;

public class TestBean {
    public TestBean() {
    }

    public void onAction(ActionEvent actionEvent) {
        GlassPaneHelper.openGlassPane();
    }

    public void queryToLaunch(ClientEvent evt) {
        
        for (int i = 0; i < 100000; i++) {
            System.out.println("hey "+i);
        }   
        GlassPaneHelper.closeGlassPane();
    }
}

As you can see, the onAction method that is called from the commandButton calls the static method GlassPanelHelper.openGlassPane() to start the GlassPane. The queryToLaunch method is called by the declarative component in response to the serverListener callback. At the end of this method, the GlassPanelHelper.closeGlassPane() method is called to hide the GlassPane and to allow the user to continue his work in the application.

Useful Addition

Andrejus Baranovski sent me a code snippet that, when called from the glass pane's close method (closeGlassPane() in the GlassPaneHelper class), refreshes the screen to show the visual results of the long running operation

 protected void refreshCurrentPage() {
FacesContext context = FacesContext.getCurrentInstance();
String currentView = context.getViewRoot().getViewId();
ViewHandler vh = context.getApplication().getViewHandler();
UIViewRoot x = vh.createView(context, currentView);
context.setViewRoot(x);
}

Open Issues

I didn't test the GlassPane outside of the laboratory of my testcase. However, the source code is provided with this document so you can trace errors and fix issues. A functionality that would have been nice to have is a skin that hooks into the current skin used to hide the GlassPane dialog's header and footer. While I got this working in another testcase, it doesn't work when included in an ADF library for which I filed a bug.

Download

With all the usual disclaimers attached, the workspace used to write this how-to document can be downloaded from here.

Native glasspane in JDeveloper 11

A blocking call has been added in Oracle JDeveloper 11 shortly before it became production. Its a javaScript contract that you call from a af:clientListener added to the command that launches the event that cause a long delay. The feature applies to all events that are partial submits and propagates to the server. If the preventUserInput function of the AdfBaseEvent JavaScript object is called prior to queuing the client event, a glass pane will cover the entire browser window preventing further input until the event has completed a round trip to the server. This feature is offered only as a JavaScript contract.


function queueEvent(event)
{
  event.cancel(); // cancel action event
  var source = event.getSource();
  var params = {};
  var type = "customListener";
  var immediate = true;
  var isPartial = true;
  var customEvent = new AdfCustomEvent(source, type, params, immediate);
  customEvent.preventUserInput();
  customEvent.queue(isPartial);
}


Note that this feature does not allow any kind of animation to entertain the users while waiting


Related Documentation

How to build declarative components in ADF Faces RC
Accessing declarative component attributes in a managed bean.
Andrejus Baranovski' blog addition

 

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy