Friday 9 October 2009

JSF: working with component identifiers (id/clientId)

This is a post about how to work with IDs in JavaServer Faces. You may find this useful if you want to use JavaScript with JSF.

Example ID developer sets on a JSF component:  foo1
What JSF might render in the resultant HTML:   j_id_jsp_115874224_691:table1:0:foo1

This is a revised version of JSF: working with component IDs (id vs clientId), though you don't need to read that. This post fixes some of the issues with the code, provides a more robust version of the library and details how to use it to make simpler, more reliable applications. The target JSF version is 1.2.

Topics

Component identifiers versus client identifiers

It is important to understand how IDs work if you're going to use them. Failure to do so may result in unexpected behaviour and make your application difficult to maintain.

So, why doesn't JSF just emit the ID you set on it?

Consider this dataTable:

      <h:dataTable id="d1" value="#{tableBean.rows}" var="row">
        <h:column>
          <h:outputText value="#{row.name}" />
        </h:column>
        <h:column>
          <h:inputText id="x1" value="#{row.quantity}" />
        </h:column>
      </h:dataTable>
      <h:commandButton value="save" action="#{tableBean.save}" />

The table contains multiple, editable rows. In HTML, each of the resultant input controls requires a unique name attribute. If you want to refer to the element by id, each row will need a unique ID. However, we only specify the control once in JSF.

How the table might be rendered:

<table id="f1:d1">
 <tbody>
  <tr>
   <td>beans</td>
   <td><input id="f1:d1:0:x1" type="text" name="f1:d1:0:x1" value="0" /></td>
  </tr>
  <tr>
   <td>carrots</td>
   <td><input id="f1:d1:1:x1" type="text" name="f1:d1:1:x1" value="0" /></td>
  </tr>
  <tr>
   <td>corns</td>
   <td><input id="f1:d1:2:x1" type="text" name="f1:d1:2:x1" value="0" /></td>
  </tr>
 </tbody>
</table>

The component identifier is managed by the getId()/setId(String) methods and is automatically generated if the user does not set a value. The client identifier that appears in the browser is returned by getClientId(FacesContext) which may return different values for the same component depending on state managed by parent controls.

Any naming container (such as a data table component, or a form) is responsible for managing the clientIds of its children. The clientId may also be namespaced by the container (this is done for portlet views where multiple views may be rendered to a single HTML page).

The (lack of) uniqueness of component identifiers

The JSF specification says this about component identifiers:

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

This means that this view is legal even though two components have an ID called x:

  <f:view>
    <h:outputText id="x" />
    <h:form id="form1">
      <h:outputText id="x" />
    </h:form>
  </f:view>

UIForm implements NamingContainer and is responsible for namespacing its children. It would not be legal to have both components with id="x" inside or outside the form.

The separator character

As we can see from examples like f1:d1:0:x1, a colon is used as a separator when building the clientId. In JSF 1.2, this value comes from NamingContainer.SEPARATOR_CHAR, but the exact value should be treated as an implementation detail. It may be unwise to hard-code this value into your view code. (JSF 2 deprecates this field and allows developers to set the value via an initialisation parameter keyed to javax.faces.SEPARATOR_CHAR.)

Getting the clientId using component binding

One of the better ways to get the clientId is to leverage component bindings with a custom tag library function.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:id="http://illegalargumentexception.googlecode.com/clientId">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>clientIdTest</title>
</head>
<body>
<h:form>
  <h:commandButton id="t1" binding="#{foo}" value="Hello, World!"
    onclick="alert('I am #{id:cid(foo)}'); return false;" />
</h:form>
</body>
</html>

The above Facelets page renders a single button that displays the clientId of the button when it is clicked. The component is bound to the request scope map by the attribute binding="#{foo}". In the JavaScript, the expression #{id:cid(foo)} resolves to the component's clientId. This is how the button is rendered:

<input id="j_id2:t1" name="j_id2:t1" type="submit" value="Hello, World!"
        onclick="alert('I am j_id2:t1'); return false;;" />

The custom tag library function resolves to this simple method:

  public static String cid(UIComponent component) {
    FacesContext context = FacesContext.getCurrentInstance();
    return component.getClientId(context);
  }

This function can be found in the sample code.

An alternate way to get the clientId is to bind the component to a request scope backing bean of some kind and then expose a method that will return the clientId from the bound component. You swap writing a single custom function to managing beans, though.

Sample code

A sample library implementing the functions described in this post can be downloaded here: clientId_2.0.1.zip

All the sources are available in a public Subversion repository.

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

Recipe: reducing request map pollution with a utility resolver

If you need to get the clientId (or other properties) of multiple controls, you're faced with the prospect of binding many controls to some backing source. This increases either the amount of code you have to write or the likelihood that there will be a naming collision with non-view artifacts. The Resolver class allows you to find controls relative to each other.

    <h:form binding="#{form1}">
      <h:inputTextarea id="text1" cols="80" readonly="true" />
      <br />
      <h:inputTextarea id="text2" cols="80" readonly="false" />
      <p>text1: <h:outputText
        value="#{id:resolver(form1).find['text1'].component.readonly}" /><br />
      text2: <h:outputText
        value="#{id:resolver(form1).find['text2'].component.readonly}" /></p>
    </h:form>

In the above JSP code, only form1 is bound to a backing resource. The resolver is used to locate the text controls and read their readonly attributes.

The resolver is just a wrapper class round component instances. It provides other methods for finding parent containers, peers and emitting clientIds. All this can be done with the equivalent static functions (and they are provided), but using the resolver makes expressions less verbose. See the documentation for more details.

Recipe: avoiding binding collisions

Binding to the request scope, as in the examples above, might be convenient, but care needs to be taken to avoid duplicate bindings. For example, if navigating between two pages, there is a danger that a component in one view would end up in the other if they each had a binding to #{requestScope.foo}. That would be bad! If you're making use of the component template features in Facelets, that also increases the number of places you need to watch for collisions.

One way to get round this is to bind components to managed backing beans with a strict naming scheme. Another is to use a custom component to help, as in this Facelets code:

<id:resolver var="resolver1">
  <h:outputText id="text1" value="Hello" />
  <h:form id="form1">
    <h:inputText id="text1" value="Goodbye" />
  </h:form>
  <p>#{resolver1.find.text1.clientId} <br />
  #{resolver1.find.form1.find.text1.clientId}</p>
</id:resolver>

The resolver component binds a Resolver class instance (wrapping itself) to the request scope var value much like a dataTable binds its rows. If it detects a collision, it will throw an exception. This code also demonstrates how to cross a NamingContainer boundary.

Recipe: editing table rows with JavaScript

Here's a HTML table with some buttons for setting quantities using JavaScript:

HTML table with JavaScript increment/decrement buttons that change a text field

Here is the JSP page:

<?xml version="1.0" encoding="UTF-8" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:id="http://illegalargumentexception.googlecode.com/clientId"
  version="2.0">
  <jsp:directive.page language="java"
    contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />
  <jsp:text>
    <![CDATA[<?xml version="1.0" encoding="UTF-8" ?>
<!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>
  <title>JSF TABLE DEMO</title>
  <link href="style.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
  <f:view>
    <h:form id="f1">
      <h:dataTable id="d1" value="#{tableBean.rows}" var="row">
        <h:column>
          <h:outputText value="#{row.name}" />
        </h:column>
        <h:column>
          <h:inputText id="x1" binding="#{foo}" value="#{row.quantity}" />
          <h:commandButton value="+"
            onclick="setQuantity('#{id:cid(foo)}', 1); return false;" />
          <h:commandButton value="-"
            onclick="setQuantity('#{id:cid(foo)}', -1); return false;" />
        </h:column>
      </h:dataTable>
      <h:commandButton value="save" action="#{tableBean.save}" />
    </h:form>
  </f:view>
  <script type="text/javascript">
<![CDATA[
  function setQuantity(elementId, n) {
    try {
      var textField = document.getElementById(elementId);
      var value = parseInt(textField.value);
      textField.value = n + value;
    } catch(err) {
      alert(err);
    }
  }
]]>
    </script>
  </body>
  </html>
</jsp:root>

The client identifier in JSF 2.0

JSF 2.0 adds the zero-argument getClientId() method to the UIComponent class. A JSF 2.0 application could cut out the function altogether; that is, #{id:cid(foo)} could be replaced with #{foo.clientId}. Hey, no custom code required!

JSF 2.0 also adds a few implicit variables to make resolving components easier.

<h:outputText id="foo" value="I am ${component.clientId}" />

This control will render something like I am foo. The implicit objects component and cc (composite control) are detailed in the spec (JSR 314).

JSF 2.0 will become part of Java Enterprise Edition in version 6, but it's open source, so you can go get it now if you want to.

5 comments:

  1. Thanks for the useful article!

    I would like to get clientId of multiple controls in JSF2.0.
    Is there a simpler way in jsf 2.0 than using the resolver?

    ReplyDelete
  2. Does the technique you referred to at the end for JSF 2.0 actually work? I tried it and it doesn't work for me.

    Can you elaborate with a working example or something?


    Suggesting that the attribute `id` on the `inputText` component
    creates an object that can be accessed with EL using `#{myInptTxtId}`,
    in the above example. The article goes on to state that JSF 2.0 adds
    the zero-argument `getClientId()` method to the `UIComponent` class.
    Thereby allowing the `#{myInptTxtId.clientId}` construct suggested
    above to get the actual generated id of the component.

    Though in my tests this doesn't work. Can anyone else confirm/deny.

    ReplyDelete
  3. @Anonymous

    The last section on JSF 2.0 assumes you've followed the section on component binding.

    Assuming #{myInptTxtId} resolves to a component binding, then #{myInptTxtId.clientId} (getClientId()) will resolve like any other getter as per the rules for EL.

    ReplyDelete
  4. Hi,

    you suggest in this section.

    "An alternate way to get the clientId is to bind the component to a request scope backing bean of some kind and then expose a method that will return the clientId from the bound component. You swap writing a single custom function to managing beans, though."

    can you give some example. thanks.

    ReplyDelete
  5. @Anonymous - see the IndexPageBean example here: http://illegalargumentexception.blogspot.co.uk/2009/02/jsf-working-with-component-ids.html#id_backingbean

    I do not recommend this approach.

    ReplyDelete

All comments are moderated