jsfcomposite-componentclientidnaming-containers

How to access a composite component's sibling via clientId


I have a composite component that bundles some input fields. The component will be used multiple times on a page and contains a button to copy the values of another of these components. For this I would need to access one of those siblings via its clientId as a target for an

<f:ajax execute=":XXX:siblingId" render="...">

My problem lies in constructing this ID. I have the name of the sibling and I can make sure that it is located in the same naming container as the component that contains the copy button, but I can't control the complete nesting hierarchy, so it might be :form:foo:bar:parent:child or just form:parent:child. So essentially I would want to get the prefix of the current composite component, but without the component's own ID and then attach the ID of the component from which to copy.

This is similar to these questions:

However, both answers make use of PrimeFaces-sepcific features like @parent and widgetVar, which does not apply to my project.

When experimenting with EL's implicit objects I basically tried the same things as the poster of the second question - with the same results: cc.parent.clientId is always empty. I also tried cc.namingContainer.clientId and some combinations of the two, alas - no success. Especially the fact that parent does not work as expected confuses me...

So: Is there a component-library-agnostic way to access the "path" of containing naming containers for a composite component? How is the parent object supposed to work, especially: when can we use it and when not?

PS: I was thinking about using the composite's full clientId and then trimming its actual ID with fn:split, however, if there was a more direct way I'd be happy to use it.


Solution

  • The #{cc.parent} resolves to UIComponent#getCompositeComponentParent() which returns the closest parent composite component. In other words, it returns only non-null when the composite component is by itself nested in another composite component.

    The #{cc.namingContainer} simply refers to #{cc} itself, fully conform as specified in UIComponent#getNamingContainer():

    Starting with "this", return the closest component in the ancestry that is a NamingContainer or null if none can be found.

    Composite components namely implicitly implement NamingContainer themselves.

    So your attempts unfortunately won't work. I also do not see any "standard API" ways to achieve the concrete functional requirement. The CompositeComponentAttributesELResolver causes that the #{cc.parent} doesn't resolve to UIComponent#getParent() which is what you ultimately want.

    You can however provide a custom UIComponent implementation for the composite which adds an extra getter with an unique name which in turn properly delegates to UIComponent#getParent().

    Here's a kickoff example:

    @FacesComponent("myComposite")
    public class MyComposite extends UINamingContainer {
    
        public UIComponent getParentComponent() {
            return super.getParent();
        }
    
    }
    

    If you register it as follows in the composite interface:

    <cc:interface componentType="myComposite">
    

    then you'll be able to use

    #{cc.parentComponent.clientId}
    

    to get the client ID of the real parent UIComponent.

    Ultimately you should be able to use the following construct to refer the sibling:

    process=":#{cc.parentComponent.clientId}:siblingId"