Search code examples
jsfjsf-2mojarra

Understanding ui:repeat client id and component finding


Mojarra 2.1.

The question is actually about the component finding algorithm.

I have the following UIRepeat:

<h:form id="frm">

    <ui:repeat id="repeater" value="#{bean.values}" var="v">
        <h:panelGroup id="pg">
            <!-- content -->
            <h:selectBooleanCheckbox id="cb" value="#{bean.v}">
                <f:ajax event="change" listener="#{bean.listener()}" render=":frm:repeater:pg" />
           </h:selectBooleanCheckbox>
           <!-- another content -->
        </h:panelGroup>
    </ui:repeat>

</h:form>

Now consider the method AjaxBehaviorRenderer#getResolvedId:

private static String getResolvedId(UIComponent component, String id) {

        UIComponent resolvedComponent = component.findComponent(id);
        if (resolvedComponent == null) {
            // RELEASE_PENDING  i18n
            throw new FacesException(
                "<f:ajax> contains an unknown id '"
                + id
                + "' - cannot locate it in the context of the component "+component.getId());
        }

        return resolvedComponent.getClientId();
    }

This method is intended to resolve the render attribute value to a component. Now, in the debugger I found that the method is being called with the following parameters in my case:

component = the instance of the HtmlSelectBooleanCheckbox
id = :frm:repeater:pg

Now, the method component.findComponent(id) returns the component which has clientId - frm:repeater:0:pg. But invoking the method as component.findComponent(":frm:repeater:0:pg") returns null.

Why? I expected the result would be the same. What did I miss?


Solution

  • This was as per impl issue 2958 "fixed" in Mojarra 2.2.5 where they simply removed that nullcheck altogether instead of improving it by stripping the iteration index. Since then, one can specify an arbitrary client ID and not ever trigger an exception. I've created API issue 1372 and proposed a fix to address this.

    This thus means, that since Mojarra 2.2.5 you can ajax-update a specific <ui:repeat> or <h:dataTable> iteration round, provided that the model is properly preserved (i.e. it is view scoped).

    <h:form id="form">
        <ui:repeat id="list" value="#{['one','two','three']}" var="item">
            <h:outputText id="item" value="#{item}" /><br/>
        </ui:repeat>
    
        <h:commandButton value="Update second item">
            <f:ajax render=":form:list:1:item" />
        </h:commandButton>
    </h:form>
    

    But this also means, that since Mojarra 2.2.5, you will never face the well known "<f:ajax> contains an unknown id 'foo' cannot locate it in the context of the component 'bar'" error, which is confusing and bad for starters.

    See also:


    Unrelated to the concrete problem, in your specific case, you can also just do render="pg" instead of render=":frm:repeater:pg", as the render target component is in the very same naming container parent component as the execute source component.