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.

Removing rows from a Query Collection

Here's a link to a blog that describes how to remove Rows from a query Collection without deleting the row from the database.  This works for entity based views but I had a need to remove rows from a Query Collection of a non-entity based read only view.     The way I did this was to add a transient attribute "Displayed" to the view.  I then create a view criteria (filterCriteria) that filters out any row where Displayed='False'.  In the "Edit View Criteria" dialog I set the Query Execution Mode to "In Memory"   After executing the query in my backing bean I loop through the rows and set the Displayed attribute to 'False" for any row I don't want to display.   Then I apply an in-memory filter by executing the following

        setApplyViewCriteriaName(filterCriteria);
        setQueryMode(QUERY_MODE_SCAN_VIEW_ROWS);                                                           
        super.executeQuery();




Normally you would just filter the original results using SQL but in my case the rules for determining which rows to remove did not translated easily to SQL so this technique was employed.  This might be useful if you were filtering out rows based on the users permissions and those permissions were stored in LDAP and couldn't be made part of the query.