Monday, December 13, 2010

Accessing ADF components using client side script

Occasionally there is a need to access a client side component using javascript.   One technique is to send script  down to the page from the backing bean that looks up the component using the ADF javascript method
AdfPage.PAGE.findComponent('clientIdOfComponent');
That technique works well but requires that you bind the component to the backing bean or look up the component using the full naming container path.  The technique I am going to describe allows you to declaratively access a UI component in script without having to add java code in the backing bean.  Because the getClientId(FacesContext)   method of the UIComponent requires a parameter it cannot be called directly within a EL expression on the page. The only parameter that can be passed in a EL Expression is the key to a Map object.  Because of this I created a Application scope helper bean  (ClientIdMap) that implements the Map interface.   It's not a real map and it's sole purpose is to evaluate the Key as a EL expression and return the Client ID of the component it finds. Just add this as an Application scope bean to the adfc-config.xml file.  (unused methods are not shown but must be implemented). 

public class ClientIdMap implements Map {
    /**
     * @param key el expression where UIComponent is stored
     * @return client side component ID
     */
    public Object get(Object key) {
       
        String returnValue = "";
        Object val = JSFUtils.resolveExpression("#{"+key+"}");
        if(val instanceof UIComponent){
            returnValue = ((UIComponent)val).getClientId(FacesContext.getCurrentInstance());
        }
           
        return returnValue;
    }

}

Then add this line to the top of your page which sets the client ID into a javascript variable that can then be used by your script.  Don't ask how the double nested single quotes work but they do.

<trh:script text="var myPopup = '#{ClientIdMap['requestScope.myComponent']}';"/>

Notice that for this to work, an EL expression (without the surrounding #{}) must be passed that is the same as the binding for the component.  Note that you can bind a component to a backing bean or directly to requestScope as shown. 

<af:popup binding="#{requestScope.myComponent}" id="myPopup">


Binding to requestScope is easiest because it can be added at runtime without restarting the server.

Then to retrieve the UIComponent do something like this (shows the popup without making an ajax call to the server)


var popup = AdfPage.PAGE.findComponent(myPopup);
var hints = {autodismissNever:true};
popup.show(hints);

The advantage of retrieving the ClientId from the binding instead of adding the fully qualified path in your javascript is that ADF may add indexes to your client ID that may change.  (see this link) Also, if someone adds a naming container around your page, region or component the Client ID will change so it's never a good idea to hard code the client ID.

3 comments:

  1. The map trick.
    http://www.theserverside.com/news/1363683/JSF-Anti-Patterns-and-Pitfalls

    ReplyDelete
    Replies
    1. Here's a link to Duncan Mills blog where he is using the same Map trip so I'm in good company.
      http://blogs.oracle.com/groundside/entry/break_group_formatting_in_a

      Delete
  2. I agree that this can be misused but in this case does not add any coupling between the view and model as the article suggests. The client side view is just retrieving the ID for one of it's own client side components. This makes it simple to add javascript behavior at runtime without having to cycle the server. Just refresh the page.

    ReplyDelete