How to address a component inside a looping naming

2019-01-27 00:26发布

I have the following structure (contents and attributes omitted):

<ui:repeat id="outerlist">
    <my:compositeComponent id="myCC">
        <h:panelgroup id="container">
            Some content here (outputText, etc.)
            <ui:repeat id="innerlist">
               <h:commandButton>
                   <f:ajax render=":#{cc.clientId}:container" />

<!-- all closing tags accordingly -->

As the content inside the container depends on the action of the innerlist's button, I need to update it. The approach as shown above works, when there is no outer ui:repeat. However, it fails with a component not found error when there is one.

This is seems due to the fact that the cc.clientId then itself contains the row index of the outer ui:repeat, e.g. outerlist:0:myCC:container. As a comment to this answer indicates, this indexed ID is not available in the server-side representation of the view tree. Instead "the row index only exist at client side". I must admit I do not quite understand how this indexing is done and what is available at the server side.

So my question is: How does JSF do this indexing, how does it (on the server) separate different "instances" inside a ui:repeat and is there a solution for what I am trying to achieve with the above code?

3条回答
smile是对你的礼貌
2楼-- · 2019-01-27 00:45

As an update: In this particular case, using c:foreach was not an option, as both lists need to be dynamic (although this route saved me a lot of trouble in some other cases) Instead I added an attribute to the composite component to pass an optional "update scope" like so:

<cc:attribute name="updateScope" required="false" type="java.lang.String"  
              default=":#{cc.clientId}:container" />

By setting a default, I do not need to alter any uses of the components in a non-looping context. However, if I want to use it inside a ui:repeat I can pass an ID to the attribute that is wide enough to enclose the outer loop. Often, this would be an h:panelGroup id="wrapper" just around the ui:repeat.

Obviously, this has quite some drawbacks: it updates all children of the loop, which could possibly result in a lot of unnecessary content being re-rendered. One nasty side-effect of this is that error messages and local values of these siblings are reset. In one of our pages it additionally causes all expanded panels to be reset into closed state.

However, if these problems are of no importance (for example: my original example referred to a component that has no input fields or panels, but only feedback text and the action), the additional attribute can be a simple approach to work around the described issue.

查看更多
做自己的国王
3楼-- · 2019-01-27 00:58

The client ID as specified in <f:ajax> must be available in both the server side by JSF's

facesContext.getViewRoot().findComponent(clientId);

(so that it could be found in order to render its new HTML representation for the ajax response)

and in the client side by JavaScript's

document.getElementById(clientId);

(so that it could be updated/replaced by JS once the ajax response with new HTML content has arrived)

As the <ui:repeat> runs during view render time only, the client ID with the row index does not represent a valid component in server side ("Cannot find component..." error from findComponent()), but it does represent a valid HTML element in the client side. Basically, you'd need the client ID without the row index for the server side and the one with the row index for the client side. But that just won't work for <ui:repeat> as it's (unfortunately) not possible to select the component tree state of a specific iteration round by alone findComponent().

It should work fine when using JSTL <c:forEach> and dynamically assigning component ID as it runs during view build time and also actually generates multiple fullworthy JSF components in the view tree instead of only one which is re-used multiple times during render.

<c:forEach varStatus="loop">
    <my:compositeComponent id="myCC">
        <h:panelGroup id="container_#{loop.index}">
            Some content here (outputText, etc.)
            <ui:repeat id="innerlist_#{loop.index}">
               <h:commandButton>
                   <f:ajax render=":#{cc.clientId}:container_#{loop.index}" />

This has however its own implications, certainly when used with composite components and also when used in nested loops. Your code is not complete enough to give insight and advice about that. It would for example break when this piece of code is placed in a composite component which is by itself also reused multiple times in a render time loop.

See also:

查看更多
看我几分像从前
4楼-- · 2019-01-27 01:12

I just solved this on my own like so:

update=":#{cc.clientId.replaceAll(':[0-9]+:', ':')}"
查看更多
登录 后发表回答