Search code examples
ajaxjsf-2behaviorcompositecomposite-component

JSF2 Composite component link using ajax


Here is my (simplified) issue :
I've got a page that is using 2 composite components of mine :
- CCSelection
- CCDisplay

In CCSelection, I have a list of values, each one has got a h:commandLink onto.
When clicking on a link, the CCDiaplay component is refreshed using the selected value.
To do this, CCSelection exposes a method attribute that is directly linked on each h:commandLink. The value is given to the method using f:attribute.
In the page backing bean, I've got a method (that is given to CCSelection as an attribute), that sets a member.
CCDisplay gets this value though an cc:attribute via the pages's member's getter.
It works !
Now, I want to ajaxize this behaviour.

I tryed to put an f:ajax for each h:commandLink in CCSelection... but if I put @form or @all in the render attribute, nothing is rendered (but the setter methods are called). If I put the id of the UIComponent (of the Page) to render, I get a nullpointerexception saying that a property is not defined for NamingContainer in CCDisplay. Quite strange because I didn't change anything inside CCDisplay !

I think the solution is to put the f:ajax not inside CCSelection but in Page.
So there may be 2 ways to achieve this :
- CCSelection raises an event f:ajax can manage... but how ?
- Using cc:clientBehaviour for CCSelection. But is it possible to target more that 1
component (I've got many h:commandLink, but I want only 1 event).
- Other ways ?


Here is a pseudo code

page.xhtml

<myComp:ccSelection actionMethod="#{pageBean.select}"
                    render="#{clientIDHelper.clientId['display']}" />
<h:panelGroup id="diplay" binding="#{clientIDHelper.bindings['display']}">
  <myComp:ccDisplay value="#{pageBean.value}" />
</h:panelGroup>

To recover the full clientid of the panel containing the ccDiaplay composite component, I use a clientIDMap technic described here.

PageBean.java

private String _value;

public String getValue() { return _value; }
public void setValue(String value) [ _value = value; }

public void select(String value) {
  setValue(value);
}

ccSelection.xhtml

<cc:interface>
  <cc:attribute method-signature="void selectAction(String)"
                name="actionMethod" />
  <cc:attribute name="render" />
</cc:interface>
<cc:implementation>
  <t:dataTable value="#{cc.values}"
               var="val"
               ...
               >
    <h:column>
       <t:commandLink actionListener="#{cc.selectionValueListener}"
         <f:ajax render="#{cc.attrs.render}" />
         <f:attribute name="value"
                      value="#{val}" />
       </t:commandLink>
    </h:column>
  </t:dataTable>
</cc:implementation>

ccSelection.java

public void selectionValueListener() {
  // recover the attribute value
  String value = event.getComponent().getAttributes().get("value");
  // call the callback method of the page
  FacesContext context = FacesContext.getCurrentInstance();
  MethodExpression method = (MethodExpression) this.getAttributes().get("actionMethod");
  if (method != null)
    method.invoke(context.getELContext(), new Object[] {value});
}

I don't think ccDisplay is interressting.

So, if I don't put the f:ajax tag, it works. When I put the f:ajax with the render pointing to the clientId passed in param, I get an error while loading the page. If I change the render for @form or @all, the pageBean.select method is called, but ccDisplay is not refreshed.


Solution

  • OK. It is solved... but I don't like it very much.
    You'll think I'm a fool : I solved the problem by removing the <![CDATA surrounding my scripts !
    I've already found some issue using CDATA. I don't know if this is a MyFaces bug or something I do the wrong way like putting many h:outputScript blocks with CDATA in composite components but with CDATA, I get errors or not working. Just removing it, it works !