ADF Faces – How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages?

While at the Oracle booth in the Java Pavilion during JavaOne, I took the opportunity to pose a question to one of the JDeveloper product managers about an issue that I think will be important to embed rich UI widgets from Java Script libraries such as Scriptaculous, Yahoo and DoJo in ADF Faces application and have them benefit from the AJAX capabilities in ADF Faces (the Partial Page Refresh or PPR for short): when the PPR cycle is complete and all updates from the server have been pasted into the DOM, I may want to use some of the updated information to refresh/update the rich widgets – all on the client side. Fresh data loaded into the ADF Faces table component could be used on the client side for example to refresh a rich charting widget.

What I need for this is a way to have my JavaScript function called by the ADF Faces framework whenever it has just completed the client side stage of PPR. Note: ADF Faces 11g – the rich faces component framework – has built in support for this hook, but not the 10.1.3 release we are currently using.

I could not get an immediate answer at the booth and then all of a sudden I stumbled upon a pretty simple solution myself. When Partial Page Rendering or Refresh occurs, one of the components that is refreshed could be an afh:script component that contains a call to my own JavaScript function! It is that simple.

Let’s take a look at what that means:

I create a new JSF page and add the following components inside the body:

      <h:form id="myform">
        <af:commandButton id="but" text="Execute PPR-request" partialSubmit="true"/>
        <af:panelBox id="box">
          <af:inputText id="ppr-counter" label="Number of PPR-requests fulfilled" value="-1"/>
        </af:panelBox>
        <afh:script id="ppr-hook" partialTriggers="but"
                    text="document.getElementById('myform:ppr-counter').value++;" />
      </h:form>

Notice how the button has partialSubmit set to true. That means that pressing the button starts a PPR cycle. Also notice that the inputText, nor its parent and ancestors, have a partialTriggers attribute set. So this inputText is not part of the PPR cycle started by pressing the button. The inputText has a static value by the way, not tied to any managed bean.

The afh:script component writes a simple Javascript statement, that gets executed immediately when rendered in the browser. What it does is get hold of the element with id myform:ppr-counter, which is the inputText element rendered as an HTML input element on the client, and increase its value by one. The script component does have a partialTriggers attribute set, associated with the commandButton through the but reference.

When we run the page, it looks like this:

ADF Faces - How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages? pprhook001

Now when we press the button, a PPR cycle is started that does nothing really on the server. When the PPR cycle is concluded on the client by updating the DOM, all that is refreshed is the ppr-hook component that has its partialTriggers attribute refer to the button that started the PPR cycle. Since refreshing that component basically means rerendering the same Javascript statement, what effectively happens is that the Javascript is reexecuted, increasing the value in the inputText component. Note that instead of pretty pointlessly increasing that input element, we could also have invoked the page specific hook into the PPR cycle, the thing we were after in this article.

After pressing the button, the page looks like this:

ADF Faces - How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages? pprhook002

With every button press, we start a new PPR request that concludes with executing my JavaScript snippet.

Using Server Side results in the Client Side after-PPR processing

Of course this hook into the PPR cycle is only of real interest if we are able to pick up some of the results produced on the server during the PPR cycle and use them to update the rich client widgets we manage in Javascript. Let’s add a little additional complexity to our page.

First of all, the button will be associated with an ActionListener – a method on a managed bean that will update some server value.

        <af:commandButton id="but" text="Execute PPR-request"
                          actionListener="#{bean.actionListener}"
                          partialSubmit="true"/>

Then we add an inputHidden component, that is rendered as a hidden form item in the browser. This component has its value attribute tied to a property on the managed bean. Since inputHidden does not have a partialTriggers attribute, we need to embed this component in a container that can be partially refreshed. We use a panelGroup for this, and embed the afh:script component in it while we are at it.

        <af:panelGroup id="hiddenContainer" partialTriggers="but">
          <af:inputHidden id="secretValueFromServer" value="#{bean.value}"/>
          <afh:script id="ppr-hook"
                      text="document.getElementById('myform:ppr-counter').value++;
                            document.getElementById('secretRevealed').value=
                                    document.getElementById('myform:secretValueFromServer').value "/>
        </af:panelGroup>

For this example we have not used an incredibly rich widget, but please pretend that the <input> we use here is in effect the most wonderfully animated information visualizer you have ever seen. The mechanics are the same.

        <f:verbatim>
          <input id="secretRevealed" type="text"/>
        </f:verbatim>

The verbatim component renders raw html, resulting in a very plain INPUT element in our page. The Javascript that gets executed at the end of the PPR request will read the value from the hidden input in the hiddenContainer panelGroup, that is updated from the server side during PPR. This value is then used to refresh the visible rich input text element.

When we start the page, it looks like this:

ADF Faces - How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages? pprhook003

Then when we press the button, PPR processing results in:

ADF Faces - How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages? pprhook004

And when we press the button again, here is what we get:

ADF Faces - How to get a Client Side hook into the Partial Page Render cycle in order to embed rich UI widgets from Dojo, Yahoo and other into ADF Faces pages? pprhook005

So we managed to get PPR going by pressing the button, resulting in a call to the actionListener method in the bean bean that updated the value property in the bean bean that is then used the PPR engine to update the value in the hidden INPUT element secretValueFromTheServer after which the Javascript element ppr-hook is re-executed that reads this new value from the server from the hidden element and uses it to update the rich visual widget called secretRevealed.

By the way the bean I used here is based on the amis.Bean class:

package amis;

import javax.faces.event.ActionEvent;

public class Bean {

    private String value;

    public Bean() {
    }

    public void actionListener(ActionEvent event) {
       if (value==null) {
           value = "hushhush";
       }
       else { value=value.concat("=> even more secrect!");
       }
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

 

Not having to add the Javascript to every page and tying it to all elements that can start a PPR cycle

While we have gotten the PPR hook to work, we have to add the Javascript to every page and we have to hook the afh:script component to every element in the page that may initiate the PPR request. That is not ideal. It would be much better we have a generic way of adding this script component to pages and even more importantly, to have it execute whenever PPR has happened.

Let’s see how JSF allows us to do just that through two mechanisms: phase listener and programmatic View manipulation.

Let’s create a JSF PhaseListener class that will kick in just before the page is rendered. The PhaseListener is injected into the JSF lifecycle and is called back by the lifecycle at the right moment. Configuration of the PhaseListener is done in the faces-config.xml file:

  <lifecycle>
    <phase-listener>amis.PPRHookManager</phase-listener>
  </lifecycle>

The class I have created here, PPRHookManager, is implemented as follows:

 

package amis;

import java.util.List;

import javax.faces.application.Application;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import oracle.adf.view.faces.component.html.HtmlScript;
import oracle.adf.view.faces.context.AdfFacesContext;

public class PPRHookManager implements PhaseListener {

    public PPRHookManager() {
    }


    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }


    public void beforePhase(PhaseEvent e) {
        if ("true".equals(getHTTPServletRequest().getParameter("partial"))) {
            Application app = e.getFacesContext().getApplication();
            UIComponent body =
                ((UIComponent)((UIComponent)e.getFacesContext().getViewRoot().getChildren().get(0)).getChildren().get(1));

            HtmlScript pprHook = (HtmlScript)findPPRHook(body);
            if (pprHook == null) {
                pprHook =
                        (HtmlScript)app.createComponent(HtmlScript.COMPONENT_TYPE);
                pprHook.setText("postPPR();");
                pprHook.setId("pprHook");
                body.getChildren().add(pprHook);
            }
            AdfFacesContext.getCurrentInstance().addPartialTarget(pprHook);
        }

    }

    private UIComponent findPPRHook(UIComponent component) {
        // traverse all children of component to locate the pprHook component
        // for some reason, I could not get findComponent to work properly
        for (UIComponent node: (List<UIComponent>)component.getChildren()) {
            if (node instanceof HtmlScript)
                if ("pprHook".equalsIgnoreCase(node.getId()))
                    return node;
        } //loop
        return null;
    }

    public void afterPhase(PhaseEvent e) {


    }

    public static HttpServletRequest getHTTPServletRequest() {
        return (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
    }
}

The beforePhase() method is where the action is. This method first establishes whether or not we are in a Partial Page Refresh cycle. If not, we are done since we only want to add and activate the PPRHook inside a PPR cycle. When the current request is a PPR one, the class will locate the body (HtmlBody) component, since that is the one that will contain the PPRHook.

In the Body component, we try to locate the component with id pprHook. When not found, it will create the component right there and then (for the first PPR request from a certain page, that will be the case). The pprHook component is an HtmlScript element, that has an id and a text attribute. The text attribute contains the actual JavaScript that is rendered into the page. This component is added to the collection of children of the Body component.

The last step is where we programmatically instruct the ADF Faces framework to include the (new) pprHook component in the Partial Page refresh that will be done in the client.

The pprHook script element contains a single Javascript call to the postPPR() function.

This function is created in a generic JavaScript library that is added to the template (region) used for every one of our pages, and does absolutely nothing (useful):

 

function postPPR() {
  alert("Generic PostPPR hook");
}

This library is included in the ADF Faces page like this:

  <afh:script source="/pprMgr.js"/>

The idea here is that pages that have specific Post PPR processing to implement, have their own version of the postPPR() function, that overrides the function from the library. In our example, the page contains the following component:

        <afh:script id="postppr-trigger"
                    text="function postPPR() {
                            alert('Specific PostPPR hook');
                            document.getElementById('myform:ppr-counter').value++;
                            document.getElementById('secretRevealed').value=
                                    document.getElementById('myform:secretValueFromServer').value;
                          }"/>

However, we can remove this component if we like and the page continues to function correctly – however, it does no longer performs any post PPR operations.

Next steps

I expect I will take use of the mechanism described in this article to embed client side widgets from Rich UI (Javascript) libraries and integrate them with ADF Faces and the ADF Data Binding. In a next piece I hope to report on my findings in that direction.

One other area of the PPR cycle I have not yet gone into is the initiating of such a cycle. ADF Faces PPR can only be initiated by ADF Faces Components (With the autoSubmit property). Or: by non-ADF Faces Components using an onchange event trigger and the submitForm() function:

for example:

onChange=”window.submitForm(‘customerForm’, 1, {source:’customerForm:firstName’});”

where the source property on the JSON-ed object indicates the element that initiated this PPR request and customerForm obviously is the id of the form that is being submitted. I am not sure what the parameter 1 is for.

 

Alternatively, we can create a commandlink or button in the page that has partialSubmit set to true, make it invisible (display:none) and activate it in JavaScript (see for example https://technology.amis.nl/blog/?p=1314 ).

2 Comments

  1. Lucas Jellema November 13, 2011
  2. Francisco November 1, 2011