Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refer to JSF dynamically generated ids based on iteration index

Tags:

jsf-2

In JSF, <ui:repeat/> and similar components such as PrimeFaces <p:dataTable/> generate dynamic ids for sub-components based on the iteration index, i.e.:

<p:dataTable id="table" var="item" value="#{itemList}">
    <h:outputText id="name" value="#{item.name}"/>
</p:dataTable>

will generate something like this:

<table id="table">
    <span id="table:0:name">name0</span>
    <span id="table:1:name">name1</span>
    <span id="table:2:name">name2</span>
    ...
    <span id="table:n:name">nameN</span>
</table>

so all the elements clearly have a distinct client id. I intentionally skipped the <tr/>, <td/>, etc.

So, <p:ajax ... update=":table:name"/> refers to all the names in the table and it works fine, <p:ajax ... update=":table:#{someDesiredIndex}:name"/> fails with a message similar to SEVERE: javax.faces.FacesException: Cannot find component with identifier ":table:0:name" in view. event though I can confirm that the component exists in the markup. Is it not possible to do this?

I'm running on GlassFish 3.1.2 and Mojarra 2.1.6 in case it is relevant.

like image 830
rdcrng Avatar asked Apr 16 '13 17:04

rdcrng


1 Answers

It does indeed not exist in the JSF component tree as traversable by UIViewRoot#findComponent(). It exists only in the generated HTML output. There's only one <h:outputText id="name"> in the JSF component tree, not multiple as you seemed to expect. It's just been reused multiple times when producing the HTML output. At best, you can get the physical component by table:name, but this does in turn not exist in the HTML DOM tree, so the document.getElementById() would fail on that during performing the ajax update.

In order to achieve the concrete functional requirement anyway, you basically need to have a physical existing component representing the row in the JSF component tree. You can create them in a loop if you use a view build time tag, such as JSTL <c:forEach>, instead of a view render time tag.

<table id="table">
    <c:forEach items="#{itemList}" var="item" varStatus="loop">
        <tr><td><h:outputText id="table_#{loop.index}_name" value="#{item.name}" /></td></tr>
    </c:forEach>
</table>

This will create physically multiple components in the JSF component tree and this get rendered as:

<table id="table">
    <span id="table_0_name">name0</span>
    <span id="table_1_name">name1</span>
    <span id="table_2_name">name2</span>
    ...
    <span id="table_n_name">nameN</span>
</table>

And you can reference them via e.g. update=":table_#{someDesiredIndex}_name".

See also:

  • How to find out client ID of component for ajax update/render? Cannot find component with expression "foo" referenced from "bar"
  • JSTL in JSF2 Facelets... makes sense?

Update: since Mojarra 2.2.5, the <f:ajax> doesn't validate the client ID anymore and the renderer is capable of walking through iterating components in order to find the right iteration round to render. So referencing the iteration index in <f:ajax> this way should just work fine. It only doesn't work yet in current MyFaces 2.2.7 / PrimeFaces 5.1 versions, but it's expected that they will catch up it in a future version.

like image 50
BalusC Avatar answered Nov 07 '22 03:11

BalusC