Search code examples
jsfjsf-2.2composite-componentomnifaces

#{cc} is resolving wrong in omnifaces tree if used in nested composite components


See the following simplified code:

test.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:o="http://omnifaces.org/ui"
      xmlns:test="http://xmlns.jcp.org/jsf/composite/test">
    <body>
        <h:form>
            <h1>Nested - not working</h1>
            <test:parentComp />

            <h1>Not nested - working</h1>
            <o:tree value="#{testModel.treeModel}">
                <o:treeNode>
                    <ul>
                        <o:treeNodeItem>
                            <test:childComp />
                            <o:treeInsertChildren />
                        </o:treeNodeItem>
                    </ul>
                </o:treeNode>
            </o:tree>
        </h:form>
    </body>
</html>

parentComp.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:o="http://omnifaces.org/ui"
      xmlns:cc="http://xmlns.jcp.org/jsf/composite"
      xmlns:test="http://xmlns.jcp.org/jsf/composite/test">
    <cc:interface componentType="parentComp">
    </cc:interface>

    <cc:implementation>
        <div id="#{cc.clientId}">
            <o:tree value="#{cc.treeModel}">
                <o:treeNode>
                    <ul>
                        <o:treeNodeItem>
                            <test:childComp />
                            <o:treeInsertChildren />
                        </o:treeNodeItem>
                    </ul>
                </o:treeNode>
            </o:tree>
        </div>
    </cc:implementation>
</html>

childComp.xhtml

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://xmlns.jcp.org/jsf/composite"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <cc:interface componentType="childComp">
    </cc:interface>

    <cc:implementation>
        <div id="#{cc.clientId}">
            <h:commandLink action="#{cc.childAction}" value="Click" />
        </div>
    </cc:implementation>
</html>

ParentComponent.java

@FacesComponent("parentComp")
public class ParentComponent extends UINamingContainer {

    private TreeModel treeModel;

    public ParentComponent() {
        this.treeModel = new ListTreeModel();
        this.treeModel.addChild("1").addChild("1.1");
        this.treeModel.addChild("2").addChild("2.2");
    }

    public TreeModel getTreeModel() {
        return treeModel;
    }
}

ChildComponent.java

@FacesComponent("childComp")
public class ChildComponent extends UINamingContainer {

    public void childAction() {

    }
}

TestModel.java

@RequestScoped
@Named
public class TestModel {

    private TreeModel treeModel;

    public TestModel() {
        this.treeModel = new ListTreeModel();
        this.treeModel.addChild("1").addChild("1.1");
        this.treeModel.addChild("2").addChild("2.2");
    }

    public TreeModel getTreeModel() {
        return treeModel;
    }
}

In the example there the o:tree is used in the composite component, the #{cc} of the child component is resolved wrongly, if you click on the link you will get the an exception like this:

Caused by: javax.el.MethodNotFoundException: /resources/test/childComp.xhtml @10,71 action="#{cc.childAction}": Method not found: [email protected]()

If the o:tree is not nested inside a composite component like in the second example, the commandlink is working as intended.

Is this a bug in Omnifaces? Am I doing something wrong here?


Solution

  • This is indeed a bug in OmniFaces. I fixed it in this commit and the fix is available in today's 2.3 snapshot (the 2.3 final is expected end this week, so you're pretty on time).

    Reason is, the queued action event is basically caught and wrapped by <o:tree> so that it can remember the currently iterated tree node on which it was fired. However, when broadcasting the action event, it's being invoked on <o:tree> itself and not on the source component (your command link). Thus, only the #{cc} in the context of <o:tree> itself is considered as composite component parent.

    I fixed it by pushing the source component's own composite component parent, if any, into EL context.

    Original Tree#broadcast() method:

    wrapped.getComponent().broadcast(wrapped);
    

    Fixed Tree#broadcast() method:

    UIComponent source = wrapped.getComponent();
    pushComponentToEL(context, getCompositeComponentParent(source));
    
    try {
        source.broadcast(wrapped);
    }
    finally {
        popComponentFromEL(context);
    }
    

    Thank you for reporting!