I'm not sure how to properly ask that question but I will try like that:
Question is about Primefaces, JSF2 Calendar in composite.. I want to catch an event that is called after that calendar was changed (and catch its new Date value).
my composite xhtml:
<composite:interface componentType="myComponent">
<composite:attribute name="value" required="true"/>
</composite:interface>
<composite:implementation>
<p:calendar
id="tempCalendar"
pattern="dd.MM.yyyy" value="#{cc.attrs.value}"
valueChangeListener="#{cc.valueChanged}"
validator="DateValidator" converter="MyDateConverter" showOn="button" showButtonPanel="true" navigator="true" >
<p:ajax event="dateSelect" update="#this" listener="#{cc.event1}"/>
</p:calendar>
</composite:implementation>
my composite's bean:
public void valueChanged(Object event) {
log("valueChanged");
}
public void event1(AjaxBehaviorEvent ab) {
log("Event1");
if (ab != null && ab.getSource() != null && ab.getSource() instanceof Calendar) {
//....
}
}
page where I'm using composite:
<cc:inputdate value="#{mainBean.aDate}" />
In code above I'm trying to do catch new value in compotents bean, but log looks like that:
valueChanged
Event1
setADate
When I'm in valueChangedListener I still have old value of calendar. New value is set at the end.
So, first of all I want to have new value in my composites bean.. but my main question is:
How to implement an event in my mainBean, that will catch new value of that calendar when changed ?
EDIT: My composite now:
<composite:interface componentType="myComponent">
<composite:attribute name="value" required="true"/>
<composite:attribute
name="myListener"
method-signature="void listener()" />
</composite:interface>
<composite:implementation>
<h:panelGroup id="container">
<p:calendar
value="#{cc.attrs.value}"
valueChangeListener="#{cc.valueChanged}"
<p:ajax event="dateSelect" update="#this,:buttonpanel" listener="#{cc.attrs.myListener}"/>
</p:calendar>
</h:panelGroup>
</composite:implementation>
And that way I call it in my main page (connected with mainBean):
<cc:inputdate
value="#{mainBean.item.myDate}"
myListener="#{mainBean.event1}"/>
I want to catch evet AFTER change in mainBean.java...
You've specified it as value attribute of the composite, so it should be available in any of the backing component's methods by the inherited UIComponent#getAttributes() as follows:
Object value = getAttributes().get("value");
You can access it from the component's local value.
i.e.,
My Test Facelet
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:view>
<h:head />
<h:body>
<h:form>
<p:calendar pattern="dd.MM.yyyy">
<p:ajax event="dateSelect" process="#this"
listener="#{testBean.event1}" />
</p:calendar>
</h:form>
</h:body>
</f:view>
</html>
My test bean
import org.primefaces.component.calendar.Calendar;
#ManagedBean(name="testBean")
#SessionScoped
public class TestBackingBean
{
public void event1(AjaxBehaviorEvent ab)
{
if (ab != null)
{
Calendar calendar = (Calendar) ab.getSource();
if(calendar != null)
{
System.out.println(String.format("Newly selected value: %s",
calendar.getLocalValue()));
}
}
}
}
Related
I have a strange situation with a composite component. I'm using it all over my web application but now I noticed that if I update a form containing my composite component, the component get's rendered twice (at times).
My component (let's say it's called datecc) is defined as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!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:composite="http://java.sun.com/jsf/composite" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui">
<h:body>
<composite:interface>
<composite:attribute name="value"/>
<composite:attribute name="shortFormat"/>
<composite:attribute name="style"/>
<composite:attribute name="styleClass"/>
<composite:attribute default="false" name="inputLabel"/>
</composite:interface>
<composite:implementation>
<span id="#{cc.clientId}">
<h:outputText rendered="#{not cc.attrs.inputLabel}" style="#{cc.attrs.style}" styleClass="#{cc.attrs.styleClass}" value="#{cc.attrs.value}">
<f:convertDateTime pattern="#{cc.attrs.shortFormat ? 'dd/MM/yy' : 'dd/MM/yyyy'}" timeZone="#{timezone}"/>
</h:outputText>
<span>asdasdfasdf</span>
<h:inputText disabled="true" rendered="#{cc.attrs.inputLabel}" style="#{cc.attrs.style}" styleClass="#{cc.attrs.styleClass}" value="#{cc.attrs.value}">
<f:convertDateTime pattern="#{cc.attrs.shortFormat ? 'dd/MM/yy' : 'dd/MM/yyyy'}" timeZone="#{timezone}"/>
</h:inputText>
</span>
</composite:implementation>
</h:body>
</html>
The page I'm calling it from is something similar to this:
<h:form id="form">
<p:dataTable id="rowsTable" value="#{myBean.rows}" var="it"
selectionMode="single" selection="#{myBean.selectedRow}" rowKey="#{it.key}"
rowStyleClass="#{myBean.isRed(it) ? 'red' : null}">
<p:ajax event="rowSelect" update=":menuForm :detailForm :contextualMenu"/>
<column>....</column>
<column><mycc:datecc value="#{it.date}" inputLabel="true" /></column>
</p:dataTable>
</h:form>
<h:form id="detailForm>
<!-- this field is rendered twice once I select a row in the above table -->
<mycc:datecc value="#{myBean.selectedRow.date}" inputLabel="true" />
</h:form>
I'm unfortunatelly doing some work on the setSelectedRow method in my bean #Named #ConversationScoped public class MyBean { ... } however I don't think that's causing the problem.
I solved my problem by implementing the following class.
package com.company.faces.cc;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
#FacesComponent("inputDate")
public class Date extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
}
Although I don't really know why this solves the issue since it doesn't add much to the component.
I'm new to web development using JSF , i have an issue in displaying a message for a validation.
I'm trying to display a message related to length validation.
this is the code for the page :
<!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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form>
<h:message showDetail="true" showSummary="true" for="txtNum" />
<p:panelGrid columns="2">
<p:outputLabel value="value" id="lblValue" for="txtNum" />
<p:inputText value="#{test.num}" required="true" id="txtNum">
<f:facet name="">
<f:validateLength maximum="3"></f:validateLength>
</f:facet>
</p:inputText>
<p:commandButton value="do" action="#{test.func()}"></p:commandButton>
</p:panelGrid>
</h:form>
</h:body>
</html>
this the code for the managed bean (configured from faces config):
public class Test {
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String func()
{
return null;
}
}
This should work. Don't forget about IDs and naming containers.When using AJAX do not skip process and update attributes.
<h:form id="form">
<p:panelGrid columns="2" id="formGrid">
<p:outputLabel value="value" id="lblValue" for=":form:txtNum" />
<p:inputText value="#{test.num}" required="true" id="txtNum">
<f:validateLength maximum="3"/>
</p:inputText>
<h:message showDetail="true" showSummary="true" for=":form:txtNum" />
<p:commandButton value="do" action="#{test.func()}" process=":form:formGrid" update=":form:formGrid"/>
</p:panelGrid>
</h:form>
I have multiple forms in page and each form have datatables.
When i select the datatable mapped to that control should be shown on the page .
Facing the below exception when developing application using primefaces:
javax.faces.view.facelets.TagAttributeException: //C:/Workspace/Application/WebContent/transfer.xhtml #28,102 rendered="#{transferMB.mySelectedValues('1')}" Error Parsing: #{transferMB.mySelectedValues('1')}
at com.sun.faces.facelets.tag.TagAttributeImpl.getValueExpression(TagAttributeImpl.java:428)
at com.sun.faces.facelets.tag.TagAttributeImpl.getValueExpression(TagAttributeImpl.java:378)
at com.sun.faces.facelets.tag.jsf.ComponentRule$ValueExpressionMetadata.applyMetadata(ComponentRule.java:107)
at com.sun.faces.facelets.tag.MetadataImpl.applyMetadata(MetadataImpl.java:81)
at javax.faces.view.facelets.MetaTagHandler.setAttributes(MetaTagHandler.java:129)
at javax.faces.view.facelets.DelegatingMetaTagHandler.setAttributes(DelegatingMetaTagHandler.java:102)
weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2277)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2183)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1454)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:178)
Caused by: javax.el.ELException: Error Parsing: #{transferMB.mySelectedValues('1')}
at com.sun.el.lang.ExpressionBuilder.createNodeInternal(Unknown Source)
at com.sun.el.lang.ExpressionBuilder.build(Unknown Source)
at com.sun.el.lang.Expres
sionBuilder.createValueExpression(Unknown Source)
at com.sun.el.ExpressionFactoryImpl.createValueExpression(Unknown Source)
at com.sun.faces.facelets.tag.TagAttributeImpl.getValueExpression(TagAttributeImpl.java:412)
... 94 more
You should use comma to separate forms you are going to update like:
<p:ajax update=":transForm, :sprdForm" />
It means there are two forms to be updated.
If you code like:
<p:ajax update=":transForm :sprdForm" />
Means you want to update sprdForm in the transForm, which it not possible since you can't put a form in another form.
You are trying to compare a list with a single String:
rendered="#{transferMB.selectedItems == '1'}"
But instead, you should check if the list INCLUDES this single string:
In the html:
rendered="#{denemeBean.controlSelectedValues('1')}"
In the bean class:
// list should be initialized (unless, we'll get NullPointer
// when using the below method)
private List<String> selectedItems = new ArrayList<>();
// This methods returns true if parameter is included in the
// list of selected items
public boolean controlSelectedValues(String needed) {
for (String string : selectedItems) {
if (string.equals(needed)) {
return true;
}
}
return false;
}
Finally, I'm not sure if you really need to put those datatables inside forms.
Below you can see working copy that I worked on: (of course you should replace outputTexts with your datatables)
// bean file
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean(name = "denemeBean")
#ViewScoped
public class DenemeBean implements Serializable {
private List<String> selectedItems = new ArrayList<>();
/**
* #return the selectedItems
*/
public List<String> getSelectedItems() {
return selectedItems;
}
/**
* #param selectedItems the selectedItems to set
*/
public void setSelectedItems(List<String> selectedItems) {
this.selectedItems = selectedItems;
}
public boolean controlSelectedValues(String needed) {
for (String string : selectedItems) {
if (string.equals(needed)) {
return true;
}
}
return false;
}
}
// html file:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</h:head>
<h:body>
<h:form>
<h:selectManyCheckbox value="#{denemeBean.selectedItems}">
<f:selectItem itemValue="1" itemLabel="Transfer Status" />
<f:selectItem itemValue="2" itemLabel="Spread Status" />
<f:selectItem itemValue="3" itemLabel="Number1 - 3" />
<p:ajax event="change" update=":tables" />
</h:selectManyCheckbox>
</h:form>
<p:panel id="tables" >
<p:panel id="transForm" rendered="#{denemeBean.controlSelectedValues('1')}">
<h:outputText value="table1 Here" />
</p:panel>
<p:panel id="sprdForm" rendered="#{denemeBean.controlSelectedValues('2')}">
<h:outputText value="table 2 Here" />
</p:panel>
</p:panel>
</h:body>
</html>
Using <p:ajax update=":transForm :sprdForm" /> is totally valid.
You have multiple other issues here.
First point: Your rendered-attribute compares a List with a String (selectedItems=='1'). It should test if the List contains the String (selectedItems.contains('1')). (Attention: You must use EL 2.2 for this)
Second point is: I think <p:panelGrid> needs the columns-attribute.
I stripped down your code and this works fine for me:
<h:body>
<h:form>
<p:selectManyCheckbox value="#{transferMB.selectedItems}">
<f:selectItem itemValue="1" itemLabel="Transfer Status" />
<f:selectItem itemValue="2" itemLabel="Spread Status" />
<f:selectItem itemValue="3" itemLabel="Number1 - 3" />
<p:ajax update=":transForm :sprdForm"/>
</p:selectManyCheckbox>
</h:form>
<h:form id="transForm">
<p:panelGrid columns="1" rendered="#{transferMB.selectedItems.contains('1')}">
transForm
</p:panelGrid>
</h:form>
<h:form id="sprdForm">
<p:panelGrid columns="1" rendered="#{transferMB.selectedItems.contains('2')}">
sprdForm
</p:panelGrid>
</h:form>
</h:body>
I used <p:selectManyCheckbox> because <h:selectManyCheckbox> with inner <p:ajax> resulted in java.lang.ClassCastException: java.lang.Boolean cannot be cast to org.primefaces.component.api.ClientBehaviorRenderingMode. I think this could be a bug in my JSF Implementation jsf-impl-2.1.7-jbossorg-2.jar.
I have a form with 2 radio buttons: "type1" and "type2". If "type1" is chosen, then a dropdown must be displayed. If "type2" is chosen, then a textfield must be displayed.
Here's the view and the controller:
test.xtml
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j">
<h:form>
<h:selectOneRadio
id="type"
label="Type"
value="#{testBean.type}">
<f:selectItem itemLabel="Type1" itemValue="type1" />
<f:selectItem itemLabel="Type2" itemValue="type2" />
<f:ajax execute="#all" render="selectBox inputBox"/>
</h:selectOneRadio>
<h:selectOneMenu
id="selectBox"
label="Service"
value="#{testBean.service}"
rendered="#{testBean.isType1}"
style="width:285px">
<f:selectItem itemLabel="Medium" itemValue="medium" />
<f:selectItem itemLabel="Basic" itemValue="basic" />
<f:selectItem itemLabel="Premium" itemValue="premium" />
</h:selectOneMenu>
<h:inputText
id="inputBox"
size="50"
value="#{testBean.custom}"
rendered="#{!testBean.isType1}" />
</h:form>
</ui:composition>
TestBean.java
package com.test.backing;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean(name = "testBean")
#SessionScoped
public class TestBean implements Serializable
{
private static final long serialVersionUID = -4337084623546767911L;
private String type = "type1";
private String service;
private String custom;
public Boolean getIsType1()
{
if(type.equals("type1"))
{
System.out.println(type+":true");
return true;
}
else
{
System.out.println(type+":false");
return false;
}
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public String getService()
{
return service;
}
public void setService(String service)
{
this.service = service;
}
public String getCustom()
{
return custom;
}
public void setCustom(String custom)
{
this.custom = custom;
}
}
When I start my application, I have the following in my stdout:
type1:true
type1:true
type1:true
type1:true
type1:true
type1:true
However, nothing happens in the UI when I choose another type. How is this caused and how can I solve it?
Try to replace xhtml code with the following code
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j">
<h:head>
</h:head>
<h:form prependId="false">
<h:selectOneRadio
id="type"
label="Type"
value="#{testBean.type}">
<f:selectItem itemLabel="Type1" itemValue="type1" />
<f:selectItem itemLabel="Type2" itemValue="type2" />
<f:ajax execute="#all" render="selectInputPanel"/>
</h:selectOneRadio>
<h:panelGroup id="selectInputPanel">
<h:selectOneMenu
id="selectBox"
label="Service"
value="#{testBean.service}"
rendered="#{testBean.isType1}"
style="width:285px">
<f:selectItem itemLabel="Medium" itemValue="medium" />
<f:selectItem itemLabel="Basic" itemValue="basic" />
<f:selectItem itemLabel="Premium" itemValue="premium" />
</h:selectOneMenu>
<h:inputText
id="inputBox"
size="50"
value="#{testBean.custom}"
rendered="#{!testBean.isType1}" />
</h:panelGroup>
</h:form></ui:composition>
Main problem in your code is,
Missing h:head to import jsf.js which is required for jsf ajax.
Please wrap your component into a panelGroup as suggested by #BaluC because once the component not rendered (not available on page) then the ajax on it will not work with its id.
And regarding number of time getIsType1() method calling is due to the rendered attribute, for more information check #Baluc's answer here
JSF generates HTML. JS/Ajax works on HTML. JS/Ajax updates HTML elements by finding it in HTML DOM tree by document.getElementById() and replacing its contents based on Ajax response. However, if a JSF component is instructed to not render HTML, then JS/Ajax cannot find it in the HTML DOM tree and thus can't replace anything.
You can only ajax-update the HTML representation of a JSF component which is always rendered. So, wrap them in e.g. a <h:panelGroup>.
<h:selectOneRadio ...>
<f:ajax ... render="selectAndInputBox" />
</h:selectOneRadio>
<h:panelGroup id="selectAndInputBox">
<h:selectOneMenu ... rendered="..." />
<h:inputText ... rendered="..." />
</h:panelGroup>
See also:
Why do I need to nest a component with rendered="#{some}" in another component when I want to ajax-update it?
Unrelated to the concrete problem, that getIsType1() method is clumsy. Just do the comparison directly in the view so that you can get rid of it.
<h:selectOneMenu ... rendered="#{testBean.type == 'type1'}" />
<h:inputText ... rendered="#{testBean.type != 'type1'}" />
or perhaps, more matching your initial question,
<h:selectOneMenu ... rendered="#{testBean.type == 'type1'}" />
<h:inputText ... rendered="#{testBean.type == 'type2'}" />
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.
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" />
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" />