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.