11

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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
rdcrng
  • 3,415
  • 2
  • 18
  • 36

1 Answers1

17

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:


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.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks! I had some suspicions this would be the case. You wouldn't recommend the workaround if I could do without it, would you? The view will then probably become overly big. – rdcrng Apr 16 '13 at 18:27
  • I'm working with ADF 10g (JSF 1.1) and this solution doesn't work for me. I don't know if I'm doing something wrong or if it is something related to JSF 1.1. Any idea? I'm refering to generate dynamically the id component with an expression like id="table_#{loop.index}" – Eduardo Oct 17 '13 at 10:09
  • Thanks @BalusC! When you say legacy JSP, you mean that I'm using a old JSF implementation version, don't you? – Eduardo Oct 17 '13 at 10:31
  • @Eduardo: Since JSF 2.0, JSP is as view technology deprecated and succeeded/replaced by Facelets (XHTML). Facelets is also available for JSF 1.2 by manual installation/reconfiguration, but not for 1.1 or older. – BalusC Oct 17 '13 at 10:40