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.
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
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:
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
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