Monday 16 February 2009

JSF: working with component IDs (id vs clientId)

Updated 2009/10/17

WARNING: this post is obsolete; go read this one instead.


JavaServer Faces components generate their own HTML element id attributes. How to work with these generated IDs is a source of much confusion among JSF neophytes. This post aims to explain what is going on and provides sample code that should make working with IDs a little bit easier.

Example JSF control in a JSP:

<h:inputText id="foo1" />

How the control might be rendered in the markup:

<input id="j_id_jsp_115874224_691:table1:0:foo1"
       name="j_id_jsp_115874224_691:table1:0:foo1" />

The id attribute rendered in the browser is obviously not the same as the value set in the JSP. This new id is the clientId and is produced by the UIComponent.getClientId(FacesContext) method.

If you want to skip all the explanations and just get something running, read How to get a clientId from an id using a TLD function and then go download the Sample Code.

Topics:

Why doesn't JSF just emit the ID that is set on the control?

The problem is that the HTML ID must be unique, but the JSF component might render itself multiple times in a given request.

As a child of a data table, foo2 may render a text input field to the page any number of times:

<h:dataTable id="table1" value="#{myBean.list}" var="row">
    <h:column id="col1">
        <h:inputText id="foo2" value="#{row.firstName}" />
    </h:column>
</h:dataTable>

The underlying control for data tables (UIData) is a NamingContainer. It is possible for NamingContainers to control the state of their children and affect how the clientId is generated.

NamingContainers are not the only things that affect the clientId. In a portlet container, it is possible for a JSP to be rendered more than once in the same HTML page. Therefore, the clientId of the view root will be namespaced to the portlet instance.

Ultimately, the exact form of a clientId is an implementation detail. Hard-coding a clientId (e.g. in JavaScript) is a potential source of bugs.

The nature of the clientId mechanism imposes restrictions on when and where you can invoke the UIComponent.getClientId(String) method. For example, for the data table above, it would be an error to invoke the method on foo2 outside col1.

How to get a clientId using backing beans

One way to get a component's clientId is via a property on a managed bean. A managed bean containing the code below could return the clientId via the EL expression #{someBean.fooClientId}.

WARNING: this component finding method findComponent (which is also used in the functions) was implemented based on the incorrect assumption that IDs must be unique in the view. What the specification says is this:

If a component has been given an identifier, it must be unique in the namespace of the closest ancestor to that component that is a NamingContainer (if any).

If you use the code, ensure that your component IDs are unique to the view. An alternative approach can be found here.

  /**
   * Returns the clientId for a component with id="foo"
   */
  public String getFooClientId() {
    FacesContext context = FacesContext.getCurrentInstance();
    UIViewRoot root = context.getViewRoot();

    final String componentId = "foo";
    UIComponent c = findComponent(root, componentId);
    return c.getClientId(context);
  }

  /**
   * Finds component with the given id
   */
  private UIComponent findComponent(UIComponent c, String id) {
    if (id.equals(c.getId())) {
      return c;
    }
    Iterator<UIComponent> kids = c.getFacetsAndChildren();
    while (kids.hasNext()) {
      UIComponent found = findComponent(kids.next(), id);
      if (found != null) {
        return found;
      }
    }
    return null;
  }

Note: It isn't possible to pass arguments to methods on object instances (e.g. action="#{bean.method('id')}"). Support for this should be in JEE6 (via JSR 245).

The above code walks the component tree to find the component. It is possible to avoid this by using the binding attribute to bind the component to the managed bean, as shown in this code:

/* Backing bean for page "index.jsp".
 * Expected faces-config definition:
 *   <managed-bean>
 *   <managed-bean-name>indexPageBean</managed-bean-name>
 *   <managed-bean-class>pagebeans.IndexPageBean</managed-bean-class>
 *   <managed-bean-scope>request</managed-bean-scope>
 * </managed-bean>
 */
public class IndexPageBean {

    /* Bind as:
     * <h:commandButton binding="#{indexPageBean.button1}" value="click me" />
     */
    private UIComponent button1;

    public UIComponent getButton1() {
        return button1;
    }

    public void setButton1(UIComponent button1) {
        this.button1 = button1;
    }

    /* Get value via: #{indexPageBean.button1ClientId}
     */
    public String getButton1ClientId() {
        FacesContext context = FacesContext.getCurrentInstance();
        return button1.getClientId(context);
    }

}

The JSF lifecycle will perform the binding when the view is created or when the view is restored.

This solution works, but it means creating a bean instance and defining a new method for every component for which the clientId is required.

How to create a generic class for component binding

Rather than create a bean for each page and define methods for every component, it is possible to bind everything via a single object. This bean can be used to bind any component and provide its clientId:

public class ComponentLookup {

    private Map<String, Map<String, UIComponent>> componentMap;

    private Map<String, Map<String, UIComponent>> getPageMaps() {
        if (componentMap == null) {
            componentMap = new HashMap<String, Map<String, UIComponent>>();
        }
        return componentMap;
    }

    private Map<String, UIComponent> getViewMap(String viewId) {
        Map<String, Map<String, UIComponent>> pageMaps = getPageMaps();
        Map<String, UIComponent> viewMap = pageMaps.get(viewId);
        if (viewMap == null) {
            viewMap = new HashMap<String, UIComponent>();
            pageMaps.put(viewId, viewMap);
        }
        return viewMap;
    }

    /** Returns map for components */
    public Map<String, UIComponent> getComponents() {
        FacesContext context = FacesContext.getCurrentInstance();
        UIViewRoot view = context.getViewRoot();
        String viewId = view.getViewId();
        return getViewMap(viewId);
    }

    private Map<String, String> clientIdMap;

    /** Returns map for getting clientIds */
    public Map<String, String> getClientIds() {
        if (clientIdMap == null) {
            clientIdMap = new AbstractMap<String, String>() {
                @Override
                public String get(Object key) {
                    return getClientId(key.toString());
                }
                @Override
                public Set<java.util.Map.Entry<String, String>> entrySet() {
                    throw new UnsupportedOperationException();
                }
            };
        }
        return clientIdMap;
    }

    private String getClientId(String id) {
        FacesContext context = FacesContext.getCurrentInstance();
        Map<String, UIComponent> components = getComponents();
        UIComponent component = components.get(id);
        return component.getClientId(context);
    }
    
}

This bean should also be bound to request scope. It is unwise to keep a reference to a component beyond the scope of a request. Multiple views/pages are accommodated as the view may change during the request. Here is the faces-config.xml definition:

  <managed-bean>
    <managed-bean-name>lookup</managed-bean-name>
    <managed-bean-class>pagebeans.ComponentLookup</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>

This JSP code shows the binding of a button to the bean, and a text field displaying the button's clientId:

<h:commandButton binding="#{lookup.components.button2}"
                    value="button2" />
<h:outputText value="clientId=#{lookup.clientIds.button2}" />
Screenshot of button 2 and its clientId

The downside of this approach is that if a component is to be looked up, it must be bound to the managed bean.

How to get a clientId from an id using a TLD function

A generic way to define the lookup functionality is via custom tag library (TLD) functions. This is a mechanism that allows you to define function expressions that resolve to public static Java methods.

  <function>
    <name>clientId</name>
    <function-class>demo.faces.ClientIdUtils
    </function-class>
    <function-signature>java.lang.String
      findClientId(java.lang.String)</function-signature>
  </function>

Example JSF control calling the function in an EL expression:

<h:commandButton id="b1" value="click to display clientId"
    onclick="alert('#{id:clientId('b1')}'); return false;" />

See Sample Code for an example implementation.

Since there is no binding information, this code will walk the view to find the components. This cost may be negligible, so the benefit of scripting simplicity may outweigh the cost. There is also the benefit of not needing managed bean artifacts. See the next section for a discussion on improving performance by caching.

How to cache a clientId from an id using a TLD function

It is tempting to try to cache clientIds to avoid walking the component tree every function call. Caching the result without component binding is difficult:

  • The relationship between an instance of UIComponent and the clientId can be 1:N. The return value from getClientId can be context-sensitive.
  • The lifetime of a UIComponent instance probably won't exceed the request. Some implementations of StateManager can be configured to save the view state in the session, in which case the lifetime of the objects might be longer, but making your code rely on this makes it fragile and it would be easy to introduce memory leaks. Keeping a reference to a UIComponent instance is a bad idea.
  • Views can be dynamic. Admittedly, this is an edge case, but it is possible to add, remove and move components programmatically.

The only relatively safe caching mechanism I can think of is to cache the path to the component based on its position within the UI tree. Components can contain a map of facets and/or a list of children. The path to a component in the header of a data table column might look like this:

view.children[0]
  -> table.children[0]
    -> column.facets['header']
      -> panel.children[2]

This approach assumes that the tree structure is static.

A sample implementation (see below) is called via an EL expression like this:

<h:commandButton id="b2" value="click to get clientId"
    onclick="alert('#{id:cachedClientId('b2')}'); return false;" />

The information is cached in the application map, so it is quickly available to all callers of a JSP page. Caching has a cost. How effective it is depends on the size and structure of the JSF view. Only select the caching method after proving it is beneficial by profiling.

If performance is a problem, consider supporting different tree traversal algorithms as an alternative.

Sample Code

A pre-built library containing the TLD functions mentioned above is available as a download: clientId_2.0.1.zip. This contains a JAR that can be placed in the WEB-INF/lib directory of JSF web applications. The taglib namespace is http://illegalargumentexception.googlecode.com/clientId.

Example JSP file that imports the functions:

<?xml version="1.0" encoding="UTF-8" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:id="http://illegalargumentexception.googlecode.com/clientId">
    <jsp:directive.page language="java"
        contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />
    <jsp:text>
        <![CDATA[<?xml version="1.0" encoding="UTF-8" ?>]]>
    </jsp:text>
    <jsp:text>
        <![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ]]>
    </jsp:text>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>clientId demo</title>
    </head>
    <body>
    <f:view>
        <h:form>
            <h:commandButton id="b1" value="click for clientId"
                onclick="alert('#{id:clientId('b1')}'); return false;" />
        </h:form>
    </f:view>
    </body>
    </html>
</jsp:root>

All the sources are available in a public Subversion repository.

Repository: http://illegalargumentexception.googlecode.com/svn/trunk/code/java/
License: MIT
Project: JsfClientId

End Notes

Another way to work with the clientId is to write a custom component that emits markup via a renderer. This avoids working with the clientId in the JSP. An example of a control that does this is the output label (see the for attribute). This approach may be too heavy-weight for one-off pieces of functionality. Also, it doesn't lend itself to working with expressions in component attributes.

There are a number of places where it would be possible to plug in functionality to build a request-scope component map without explicitly binding components. See the ViewHandler class; the PhaseListener interface; etc. These can be configured with replacement or decorator classes via faces-config.xml. Whether the convenience of this versus the cost of doing it for every control on every page pays off depends on how many lookups are required. Again, this works better for static trees of components and special handling would likely be needed (probably via the Application.createComponent* methods) for controls that were added outside the normal phases.

This article assumes the use of Java EE 5. JEE5 specifies JSF version 1.2.

10 comments:

  1. how would this work with dataTable rows where ClientId also cosist of row index? This would always return clientId of first row.

    ReplyDelete
  2. If you are referring to the clientId value being cached - it is not, or you would have the problem you point out! It is the route to the component that is cached. You can see an example of using the code in a table in JSF: using component IDs in a data table (h:dataTable vs clientId).

    ReplyDelete
  3. I would really like to use a generic class for component binding. but I need to bind components inside iterative components like icefaces seriesPanel. Is a similar technique to the one described above possible?

    ReplyDelete
  4. Going purely on the documentation for panelSeries, it suggests it is possible. It will probably depend on the context (where) you end up invoking the getClientId method.

    ReplyDelete
  5. 2 tables on page and input text must have same id in both tables. Rich function clientId return wrong id for second table. Any chance for workaround?

    rich:dataTable id="a" value=...
    rich:column
    h:inputText id="input/
    #{rich:clientId('input')}
    /rich:column>
    /dataTable>

    rich:dataTable id="b" value=...
    rich:column
    h:inputText id=input/
    {#rich:clientId('input')}
    /rich:column
    /dataTable

    ReplyDelete
  6. Yes.

    As the first line of the post says...

    ...this post is obsolete; go read this one instead.

    Click the link.

    ReplyDelete
  7. Hi McDowell,

    I need to get the clientId for a cell in a table. I don't know how to do it because I cannot create a binding to the cell (or i don't know how to do that). Could you give some advice on how to do that?

    Thanks in advanced!
    Sa

    ReplyDelete
  8. Since the dataTable does not emit clientIds for UIColumn children, you cannot refer to the element directly. The best approach is probably to get a reference to a child element and walk up the parents to find the TD element.

    If you use jQuery, the closest() method would be one way to do this.

    ReplyDelete
  9. Thanks for your reply. As for the "child element" you mentioned here, do you mean the row of the table? As for the "TD element", do you mean the column? Could you elaborate a little bit?

    I'm actually using Oracle ADF. Do you have any idea how to do it in this framework?

    Thanks!
    Sa

    ReplyDelete
  10. Column children (e.g. outputText controls) can be used to emit elements with IDs. A TD element represents a table cell - I suggest familiarising yourself with the tables section of the HTML spec. A dedicated Q&A site like stackoverflow.com would be better suited for solving problems like this.

    ReplyDelete

All comments are moderated