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?
- How to get a
clientId
using backing beans - How to create a generic class for component binding
- How to get a
clientId
from anid
using a TLD function - How to cache a
clientId
from anid
using a TLD function - Sample Code
- End Notes
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 NamingContainer
s to control the state of
their children and affect how the clientId
is generated.
NamingContainer
s 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.
/**
|
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". |
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 { |
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}" />
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 clientId
s 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 theclientId
can be1:N
. The return value fromgetClientId
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 aUIComponent
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.
how would this work with dataTable rows where ClientId also cosist of row index? This would always return clientId of first row.
ReplyDeleteIf 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).
ReplyDeleteI 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?
ReplyDeleteGoing 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.
ReplyDelete2 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?
ReplyDeleterich: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
Yes.
ReplyDeleteAs the first line of the post says...
...this post is obsolete; go read this one instead.
Click the link.
Hi McDowell,
ReplyDeleteI 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
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.
ReplyDeleteIf you use jQuery, the closest() method would be one way to do this.
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?
ReplyDeleteI'm actually using Oracle ADF. Do you have any idea how to do it in this framework?
Thanks!
Sa
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