1

The way it works

Seeking hard for the solution, I found that I just forgot the leading h:head tags in my usage component. Adding them made all the errors disappear. So for a complete solution this here is my last code:

  • the composite component
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
    <composite:attribute name="value" required="true"  />
    <composite:attribute name="size" required="false" default="20" />
    <composite:clientBehavior name="change" event="change" targets="input" />
</composite:interface>
<composite:implementation>
    <h:inputText id="input" value="#{cc.attrs.value}" size="#{cc.attrs.size}" />
</composite:implementation>

  • the bean
@ManagedBean
@SessionScoped
public class MyBean
{
private String value1;
private String value2;
private String value3;

public String exec()
{
    this.value2 = value3;
    return "";
}

public void listenAjax(AjaxBehaviorEvent e)
{
    UIInput i = (UIInput) e.getComponent();     
    value2 = (String) i.getValue();
    System.out.println("ajax value = " + i.getValue());
    System.out.println("value1 = " + value1);
    System.out.println("value2 = " + value2);
    System.out.println("value3 = " + value3);
}

public String getValue3()
{
    return value3;
}

public String getValue2()
{
    return value2;
}

public String getValue1()
{
    return value1;
}

public void setValue1(String value1)
{
    this.value1 = value1;
}

public void setValue3(String value3)
{
    this.value3 = value3;
}
}
  • the calling xhtml file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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/test">
<h:head>
</h:head>
<h:body>
<h:form id="form">
    <h:panelGrid columns="2">
        <h:outputText value="value3" />
        <h:inputText value="#{myBean.value3}">
            <f:ajax event="change" render="see" listener="#{myBean.listenAjax}" />
        </h:inputText>
        <h:outputText value="value1" />
        <test:test value="#{myBean.value1}">
            <f:ajax event="change" render=":form:see" listener="#{myBean.listenAjax}" />
        </test:test>
        <h:outputText value="value2" />
        <h:outputText id="see" value="#{myBean.value2}" />
        <h:outputText value="" />
        <h:commandButton action="#{myBean.exec}" value="set" />
    </h:panelGrid>
</h:form>
</h:body>
</html>

Finally, thanks to all that gave me some hints and helped me finding out about the bugs.

Improvements step 2

Redesigning the component now I have this:

  • composite component:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
    <composite:attribute name="value" required="true"  />
    <composite:attribute name="size" required="false" default="20" />
    <composite:clientBehavior name="change" event="change" targets="input" />
</composite:interface>
<composite:implementation>
    <h:inputText id="input" value="#{cc.attrs.value}" size="#{cc.attrs.size}" />
</composite:implementation>

  • managed bean:
@ManagedBean
@SessionScoped
public class MyBean {
private String value1;
private String value2;
private String value3;

public String exec()
{
    this.value2 = value3;
    return "";
}

public void listen(AjaxBehaviorEvent e)
{
    UIInput i = (UIInput) e.getComponent();     
    value2 = (String) i.getValue();
    System.out.println("ajax value = " + i.getValue());
    System.out.println("value1 = " + value1);
    System.out.println("value2 = " + value2);
    System.out.println("value3 = " + value3);
}

public String getValue3()
{
    return value3;
}

public String getValue2()
{
    return value2;
}

public String getValue1()
{
    return value1;
}

public void setValue1(String value1)
{
    this.value1 = value1;
}

public void setValue3(String value3)
{
    this.value3 = value3;
}

}

  • usage:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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/test">
<h:body>
<h:form>
    <test:test value="#{myBean.value1}">
        <f:ajax event="change" render=":out" listener="#{myBean.listen}" />
    </test:test>
    <h:inputText value="#{myBean.value3}">
        <f:ajax event="change" render=":out" listener="#{myBean.listen}" />
    </h:inputText>
    <h:commandButton action="#{myBean.exec}" value="set" />
</h:form>
<h:outputText id="out" value="#{myBean.value2}" />
</h:body>
</html>

Nevertheless no ajax response is done to my component outside of the form (no results in the log window). This is confusing me, how can I make it work?

Improvements step 1 (old)

So I tried to improve my code, changing the composite component to

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
    <composite:attribute name="value" required="true" />
    <composite:attribute name="size" required="false" default="20" />
    <composite:clientBehavior name="change" event="change" targets="#{cc.clientId}:input" />
</composite:interface>
<composite:implementation>
    <h:inputText id="input" value="#{cc.attrs.value}" size="#{cc.attrs.size}" />
</composite:implementation>

and my call would be

<my:test value="#{test.value1}">
  <f:ajax event="change" render=":out" listener="#{test.listen}" />
</my:test>
<h:outputText id="out" value="#{test.value2}" />

with the result that exactly nothing happens. What may I do to make this work?

The original post*

I'd like to make my composite component working with AJAX. I googled a lot, found some solutions even here on stackoverflow, but they all seemed to work with buttons only. Here I have an inputText component, how can I give my component an AJAX event listener? Executing my example (see below) gives this error:

com.sun.faces.lifecycle.InvokeApplicationPhase execute
WARNING: 0
java.lang.ArrayIndexOutOfBoundsException: 0
at org.apache.el.parser.AstValue.convertArgs(AstValue.java:320)
at org.apache.el.parser.AstValue.invoke(AstValue.java:274)
at org.apache.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:274)
at com.sun.faces.facelets.el.ContextualCompositeMethodExpression.invoke(ContextualCompositeMethodExpression.java:187)
at com.sun.faces.facelets.tag.TagAttributeImpl$AttributeLookupMethodExpression.invoke(TagAttributeImpl.java:473)
at com.sun.faces.facelets.tag.jsf.core.AjaxBehaviorListenerImpl.processAjaxBehavior(AjaxHandler.java:459)
at javax.faces.event.AjaxBehaviorEvent.processListener(AjaxBehaviorEvent.java:113)
at javax.faces.component.behavior.BehaviorBase.broadcast(BehaviorBase.java:106)
at javax.faces.component.UIComponentBase.broadcast(UIComponentBase.java:809)
at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:800)
at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1292)
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:181)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:645)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)

My composite component:

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
    <composite:attribute name="value" required="true" />
    <composite:attribute name="size" required="false" default="20" />
    <composite:attribute name="enableAjax" required="false" default="false" />
    <composite:attribute name="ajaxRender" required="false" />
    <composite:attribute name="ajaxListener" required="false" method-signature="void listen(javax.faces.event.AjaxBehaviorEvent)" />
</composite:interface>
<composite:implementation>
    <h:inputText id="input" value="#{cc.attrs.value}" size="#{cc.attrs.size}">
        <f:ajax event="change" render="#{cc.attrs.ajaxRender}" listener="#{cc.attrs.ajaxListener}" disabled="#{!cc.attrs.enableAjax}" />
    </h:inputText>
</composite:implementation>

My call:

<my:test value="#{test.value1}" ajaxRender=":out" ajaxListener="#{test.listen}" enableAjax="true" />
<h:outputText id="out" value="#{test.value2}" />

My Bean:

@ManagedBean
@SessionScoped
public class Test {
  private String value1;
  private String value2;
  ...
  public void listen(AjaxBehaviorEvent e)
  {
    value2 = (String) ((UIInput) e.getComponent()).getValue();
  }
  ... (getter & setter)
}

BTW. by composite component is much more complex, I reduced this example to the relevant parts.

Jörg Henke
  • 123
  • 2
  • 10
  • The stacktrace points to `MethodExpressionImpl`, could be related to the `method-signature` ? – Christophe Roussy Feb 13 '13 at 10:43
  • 1
    Actually this looks a lot like this question: http://stackoverflow.com/questions/6453842/jsf-2-how-can-i-add-an-ajax-listener-method-to-composite-component-interface – Christophe Roussy Feb 13 '13 at 10:48
  • you're right, but as mentioned, the question you refer to contains a button element, not an inputText element – Jörg Henke Feb 13 '13 at 14:32
  • found one of the bugs: just forgot to add h:head tag to my usage file in improvement 2. Adding this makes the input tag ajax call work. – Jörg Henke Feb 18 '13 at 12:07

2 Answers2

3

The <cc:clientBehavior> should work. Your targets is only wrong.

<composite:clientBehavior name="change" event="change" targets="#{cc.clientId}:input" />

It must be relative to the composite itself, not to the parent/viewroot or whatever (as that would theoretically require editing the composite component everytime when you put it in a different naming container parent!).

Thus, so

<composite:clientBehavior name="change" event="change" targets="input" />
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
1

If we look at your composite component definition (especially at ajaxListener attribute) you defined a method with AjaxBehaviorEvent (which is correct signature for this kind of event listener) but in use of your component you defined an attribute as:

ajaxListener="#{test.listen}"

So, no arguments - and that is place where this exception is thrown (in trying to find arguments of method).

With this your use case, you should change your component definition little bit. So change:

<composite:attribute name="ajaxListener" required="false" method-signature="void listen(javax.faces.event.AjaxBehaviorEvent)" />

to something like this:

<composite:attribute name="bean" required="false" type="java.lang.Object" />
<composite:attribute name="ajaxListener" required="false" type="java.lang.String" />

Also change your listener attribute in f:ajax tag to:

listener="#{cc.attrs.bean[cc.attrs.ajaxListener]}"

Finally change the way how your composite component is used:

<my:test value="#{test.value1}" ajaxRender=":out" ajaxListener="listen" bean="#{test}" enableAjax="true" />
partlov
  • 13,789
  • 6
  • 63
  • 82
  • Thanks at all for the fast answer. I tried this out, but sorry, it doesn't work... even though the exception is gone, my listener method doesn't know any content from its sent ajax call. Regarding my managed bean Test (see above), method listen sets value2 always to null, no matter what I put into the inputText element. – Jörg Henke Feb 13 '13 at 15:11
  • Actually, in this your example input will be processed and it will be set to `value1` field, so in `value1` you will have text from `inputText` (you can use it in listener function) – partlov Feb 13 '13 at 15:31
  • As I tried to set value2 using value1, value1 was not set (due to ajax?) – Jörg Henke Feb 14 '13 at 09:28