0

According to the JSF documentation for <composite:interface>:

Nesting of composite components

The implementation must support nesting of composite components. Specifically, it must be possible for the section of a composite component to act as the using page for another composite component. When a composite component exposes a behavioral interface to the using page, such as a , , or other behavioral interface, it must be possible to “propogate” the exposure of such an interface in the case of a nested composite component. The composite component author must ensure that the value of the name attributes exactly match at all levels of the nesting to enable this exposure to work. The implementation is not required to support “re-mapping” of names in a nested composite component.

It goes on to show an example of nesting <composite:actionSource> however, I have been testing an example almost exactly like that and it doesn't work. Here is my code:

Inner Composite Component:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:composite="http://java.sun.com/jsf/composite">

<composite:interface>
  <composite:attribute name="actionText" 
    type="java.lang.String" default="nestedastest action" />
  <composite:actionSource name="someaction" />
</composite:interface>

<composite:implementation>
  <h:commandLink id="someaction">#{cc.attrs.actionText}</h:commandLink>
</composite:implementation>
</html>

Outer Composite Component:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:composite="http://java.sun.com/jsf/composite"
  xmlns:test="http://java.sun.com/jsf/composite/components/test">

<composite:interface name="nestedActionTester"
  displayName="A test composite component for nested actions">
  <composite:attribute name="actionText" 
    type="java.lang.String" default="astest action" />
  <composite:actionSource name="someaction" />
</composite:interface>

<composite:implementation>
  <test:nestedastest actionText="#{cc.attrs.actionText}" />
</composite:implementation>
</html>

Facelet:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:test="http://java.sun.com/jsf/composite/components/test">

<h:head />
<h:body>
  <ui:composition template="template.xhtml">
    <ui:define name="content">
      <h:form id="communityMembersForm">
        <div>
          <test:astest>
            <f:actionListener for="someaction"
              binding="#{testController.someActionActionListener}" />
          </test:astest>
        </div>
        <div>
          <test:nestedastest>
            <f:actionListener for="someaction"
              binding="#{testController.someActionActionListener}" />
          </test:nestedastest>
        </div>
      </h:form>
    </ui:define>
  </ui:composition>
</h:body>
</html>

Managed Bean:

package test.controller;


import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@ManagedBean
@ViewScoped
public class TestController {
    private static Logger logger = 
            LoggerFactory.getLogger( TestController.class );

    public ActionListener getSomeActionActionListener() {
        return new ActionListener() {
            @Override
            public void processAction( ActionEvent event ) 
                    throws AbortProcessingException {
                logger.debug( "someaction occurred..." );
            }

        };
    }
}

I am using Mojarra 2.1.13:

<dependency>
  <groupId>com.sun.faces</groupId>
  <artifactId>jsf-api</artifactId>
  <version>2.1.13</version>
  <type>jar</type>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.sun.faces</groupId>
  <artifactId>jsf-impl</artifactId>
  <version>2.1.13</version>
  <type>jar</type>
  <scope>runtime</scope>
</dependency>

On tomcat 6.0.32:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>el-api</artifactId>
  <version>6.0.32</version>
  <type>jar</type>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>servlet-api</artifactId>
  <version>6.0.32</version>
  <type>jar</type>
  <scope>provided</scope>
</dependency>

When I run this application, the link for nestedastest action (which is the link that uses the inner composite component directly) causes the action listener to run and the log message to print. However, when the astest action (the outer composite component) is clicked nothing happens. Given that this is almost exactly the example shown in the official JSF javadoc, I would expect this to work. Any idea why it isn't?

---------- EDIT ---------

I have found that if I modify the outer composite component thusly:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:composite="http://java.sun.com/jsf/composite"
  xmlns:test="http://java.sun.com/jsf/composite/components/test">

<composite:interface name="nestedActionTester"
  displayName="A test composite component for nested actions">
  <composite:attribute name="actionText" 
    type="java.lang.String" default="astest action" />
  <composite:actionSource name="someaction" targets="inner" />
</composite:interface>

<composite:implementation>
  <test:nestedastest id="inner" actionText="#{cc.attrs.actionText}" />
</composite:implementation>
</html>

Note that I added a targets attribute to the actionSource and a matching id to the nested composite component

It will now chain the action appropriately. This does make sense, but the documentation leads you to believe this is unnecessary. Is this an error in the documentation? Or in the implementation (I tried it out both on Mojarra 2.1.13 and MyFaces 2.1.10)? Or in my understanding?

Lucas
  • 14,227
  • 9
  • 74
  • 124
  • Try with MyFaces to check if is a bug in Mojarra or not. In theory it should be so. – lu4242 Nov 30 '12 at 20:39
  • @lu4242, when i changed the project to myfaces (2.1.10), it failed on page load with this: `/xhtml/astestmain.xhtml at line 17 and column 70 Parent is not composite component or of type ActionSource, type is: javax.faces.component.html.HtmlForm@1ebdcc9a` which you can see from the facelet section above, the parent _is_ a composite component, _and_ it has an `` – Lucas Nov 30 '12 at 21:06
  • @lu4242 disregard the last comment, I just found [MYFACES-3454](https://issues.apache.org/jira/browse/MYFACES-3454) – Lucas Nov 30 '12 at 21:13
  • @lu4242 I have verified that this behavior is the same in myfaces. I have also verified that if I put an id on the `` and then add the targets attribute to `` it will pass through as expected. Perhaps the documentation is just not totally correct and you must include the targets and id values to chain it through? – Lucas Nov 30 '12 at 21:22
  • 1
    I can assert the documentation is correct. In this case, to avoid "targets" declaration, it is necessary to set the id to the same name to use in the for declaration, but if is nested it, it is necessary to do that all over the chain for all related composite components, because each composite component is a NamingContainer, which mean it only look on the components related. – lu4242 Dec 01 '12 at 18:33
  • @lu4242, Ok, so I do have the id's of the `actionSource` matching. And since the inner `cc`'s action source actually targets multiple components, I cannot have `id`'s of the components themselves match. So you are saying that since the actual end component does not have the same `id` as the all the `actionSource`'s in the chain, I _must_ use `targets` the whole way? If so, please make your response an answer so I can accept it. – Lucas Dec 03 '12 at 16:13

1 Answers1

0

The algorithm try to find by default a component with the same id as the one defined by cc:actionSource name in each nested level. In this case, the component id is only defined on the inner level. If you don't want to use the same id for each component all the way down, you can use "targets" attribute to indicate the algorithm which component is talking about, and pass the actionListener by all the levels.

lu4242
  • 2,318
  • 1
  • 15
  • 15