Ajax Event rowToggle is not working inside composite component - ajax

I have the composite component for dataTable where I would like to add the ajax event="rowToggle". I tried the below options:
<cc:interface>
<cc:attribute name="rows" />
<cc:attribute name="value"
type="org.primefaces.model.LazyDataModel" />
<cc:attribute name="var" />
<cc:attribute name="id" />
<cc:attribute name="rowStyle" required="false"/>
<cc:attribute name="ajaxEvent" required="false" />
</cc:interface>
<cc:implementation>
<p:dataTable value="#{cc.attrs.value}"
rendered="#{not empty cc.attrs.value}" id="#{cc.attrs.id}"
paginator="true" rows="25"
currentPageReportTemplate="Showing {startRecord}-{endRecord} of {totalRecords}"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="25,50,100" paginatorPosition="bottom"
lazy="true" rowStyleClass="#{cc.attrs.rowStyle}">
<p:ajax event="#{cc.attrs.ajaxEvent}" global="false" />
<c:set target="#{component}" property="var" value="#{cc.attrs.var}"/>
<cc:insertChildren />
</p:dataTable>
</cc:implementation>
And from the main page, I am calling:
<t:lazydatatable var="changeSummary"
value="#{changeLogInspectorBean.changeLogInsertModel}" rows="25"
id="change_overview_insert" ajaxEvent="rowToggle">
It is failing with the below error message:
<p:ajax> Event:#{cc.attrs.ajaxEvent} is not supported.
Can you suggest what wrong I am doing here?

as for the ajax event call in a composite component you need to define the proper event in your table.
Try declaring the attribute like:
<cc:attribute name="rowToggleListener" required="false" />
And the actual call inside the datatable like:
<p:ajax event="rowToggle" listener="#{cc.attrs.rowToggleListener}"/>
And for the component:
<t:lazydatatable var="changeSummary"
value="#{changeLogInspectorBean.changeLogInsertModel}"
rows="25" id="change_overview_insert"
ajaxEvent="#{yourBackingBean.onRowToggle}">
The Mthod inside your backing bean could look like this:
public void onRowToggle(RowToggleEvent evt){
MytoggledRowObject rowObject = ((MytoggledRowObject) event.getData());
}
Hope that helped :)

Related

OmniFaces validateEquals and composite component

I'm using JSF 2.3 with Omnifaces 3.0.
I'm trying to create a custom component that represents an e-mail text field. Nothing special.
I've got the following code that is perfectly working
<h:form id="formAggiungiUtente">
<p:outputLabel for="inputTextEmail" value="#{msg['email']}"/>
<h:inputText id="inputTextEmail" pt:type="email" value="#{userController.userBean.mail}" class="form-control" required="true" requiredMessage="#{msg['email']} #{msg['campoObbligatorio']}" validatorMessage="#{msg['email']} #{msg['nonValido']}">
<f:validator binding="#{emailJsfValidator}"/>
</h:inputText>
<p:outputLabel for="inputTextSecondEmail" value="#{msg['ripetiMail']}"/>
<h:inputText id="inputTextSecondEmail" pt:type="email" value="#{userController.secondMail}" class="form-control" required="true" requiredMessage="#{msg['ripetiMail']} #{msg['campoObbligatorio']}" validatorMessage="#{msg['ripetiMail']} #{msg['nonValido']}">
<f:validator binding="#{emailJsfValidator}"/>
</h:inputText>
<o:validateEqual id="equal" components="inputTextEmail inputTextSecondEmail" message="#{msg['indirizziMailDiversi']}" />
</h:form>
In other parts of the application, I need the component that represents the e-mail text field, so the first step was to create something like the following
<h:form id="formAggiungiUtente">
<ui:include src="templates/mailTextField.xhtml">
<ui:param name="id" value="inputTextEmail" />
<ui:param name="label" value="#{msg['email']}" />
<ui:param name="value" value="#{userController.userBean.mail}" />
<ui:param name="required" value="true" />
<ui:param name="requiredMessage" value="#{msg['email']} #{msg['campoObbligatorio']}" />
<ui:param name="validatorMessage" value="#{msg['email']} #{msg['nonValido']}" />
</ui:include>
<ui:include src="templates/mailTextField.xhtml">
<ui:param name="id" value="inputTextSecondEmail" />
<ui:param name="label" value="#{msg['ripetiMail']}" />
<ui:param name="value" value="#{userController.secondMail}" />
<ui:param name="required" value="true" />
<ui:param name="requiredMessage" value="#{msg['ripetiMail']} #{msg['campoObbligatorio']}" />
<ui:param name="validatorMessage" value="#{msg['ripetiMail']} #{msg['nonValido']}" />
</ui:include>
<o:validateEqual id="equal" components="inputTextEmail inputTextSecondEmail" message="#{msg['indirizziMailDiversi']}" />
</h:form>
and here the mailTextField.xhtml
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<p:outputLabel for="#{id}" value="#{label}"/>
<h:inputText
id="#{id}"
pt:type="email"
value="#{value}"
class="form-control"
required="#{required}"
requiredMessage="#{requiredMessage}"
validatorMessage="#{validatorMessage}">
<f:validator binding="#{emailJsfValidator}"/>
</h:inputText>
</ui:composition>
Also, this solution is working but, by reading a lot of answers I've understood that if there're too many parameters may is the case to create a custom component. So I've created the following component by using "cvl" as library name
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
<cc:interface>
<cc:attribute name="label" type="java.lang.String" default="#{msg['email']}" />
<cc:attribute name="value" type="java.lang.String" />
<cc:attribute name="required" type="java.lang.Boolean" default="true" />
<cc:attribute name="requiredMessage" type="java.lang.String" default="#{msg['email']} #{msg['campoObbligatorio']}" />
<cc:attribute name="validatorMessage" type="java.lang.String" default="#{msg['email']} #{msg['nonValido']}" />
</cc:interface>
<cc:implementation>
<p:outputLabel for="#{cc.clientId}" value="#{cc.attrs.label}"/>
<h:inputText
id="#{cc.clientId}"
pt:type="email"
value="#{cc.attrs.value}"
class="form-control"
required="#{cc.attrs.required}"
requiredMessage="#{cc.attrs.requiredMessage}"
validatorMessage="#{cc.attrs.validatorMessage}">
<f:validator binding="#{emailJsfValidator}"/>
</h:inputText>
</cc:implementation>
</html>
and now my page is like this
<h:form id="formAggiungiUtente">
<cvl:mailTextField
id="inputTextEmail"
value="#{userController.userBean.mail}"
/>
<cvl:mailTextField
id="inputTextSecondEmail"
value="#{userController.userBean.mail}"
requiredMessage="#{msg['ripetiMail']} #{msg['campoObbligatorio']}"
validatorMessage="#{msg['ripetiMail']} #{msg['nonValido']}"
/>
<o:validateEqual id="equal" components="inputTextEmail inputTextSecondEmail" message="#{msg['indirizziMailDiversi']}" />
</h:form>
The problem is that I don't how to fill the parameter "components" of the validateEqual because the error is always something like the following.
ValidateEqual attribute 'components' must refer UIInput client IDs. Client ID is of type 'javax.faces.component.UINamingContainer'
I've tried a lot of combination but without success.
What I'm doing wrong?
Thanks
by reading a lot of answers I've understood that if there're too many parameter may is the case to create a custom component
This one? When to use <ui:include>, tag files, composite components and/or custom components? It says to create a tag file, not a custom component, let alone a composite component.
If you had used a tag file, then this construct would have worked just fine. Composite components, on the other hand, are naming containers. This means that they add an extra client ID layer over their children, like as <h:form> and <h:dataTable> also do. You should also include the composite component's own id in the client ID search expression. It goes like:
<cc:someComposite id="idOfComposite1" ... />
<cc:someComposite id="idOfComposite2" ... />
<o:validateEqual components="idOfComposite1:idOfInput idOfComposite2:idOfInput" />
Whereby the composite implementation has:
<h:inputText id="idOfInput" ... />
You thus only need to fix your composite component to not reference #{cc.clientId} anymore in the id attribute of a child JSF component. This is outright wrong. It's supposed to be only used on a plain HTML <span> or <div> wrapping the composite component's implementation, and then with the sole reason of Rerendering composite component by ajax.

<f:ajax> not working with composite component

<cc:interface componentType="inputAutoComplete">
<cc:attribute name="reRender" type="java.lang.String" shortDescription="Provide The ids of the component to be refreshed"></cc:attribute>
<cc:attribute name="baseURL" type="java.lang.String" shortDescription="Provide service method name of frontController API" />
<cc:attribute name="labelValue" required="true" shortDescription="This is the binding value for label similar to drop down label(ItemLable).If key not present ,Then label is must.It will be considered as key "/>
<cc:attribute name="keyValue" shortDescription="This is the binding value for label similar to drop down key(ItemKey) .Its a hidden field"/>
<cc:attribute name="keyType" type="java.lang.String" shortDescription="The value has to be KEY or KEYVALUE . It defines weather labelValue is key or label"/>
<cc:attribute name="isRequired" type="java.lang.String"/>
<cc:attribute name="datatype" type="java.lang.String" required="true" shortDescription="Data type value only to be dynamic or static"/>
<cc:attribute name="varname" type="java.lang.String" shortDescription="If It is a static json list , then varname is must"/>
<cc:attribute name="manageBeanName" shortDescription="If static list to be prepared from ManagedBean then ManagedBean name is must"/>
<cc:attribute name="manageBeanMethodName" type="java.lang.String" shortDescription="If static list to be prepared from ManagedBean then ManagedBean method name is must and method should return String only as return type"/>
<cc:attribute name="onchange" />
<cc:editableValueHolder name="inTxt" />
<cc:clientBehavior name="clientEvent" targets="inTxt" event="change" />
</cc:interface>
<cc:implementation>
<h:inputText id="intext" value="#{cc.attrs.labelValue}" binding="#{cc.userInputbinding}"
onchange="#{cc.attrs.onchange}">
<cc:insertChildren />
</h:inputText>
<p style="display: none;">THIS VALUE IS INVALID</p>
<h:inputText style="display:none" styleClass="hiddenClass" value="#{cc.attrs.keyValue}" />
<h:panelGroup rendered="#{cc.includeJsonInPage}">
</h:panelGroup>
<f:param name="baseURL" value="#{cc.attrs.baseURL}"/>
<f:param name="datatype" value="#{cc.attrs.datatype}"/>
<f:param name="varname" value="#{cc.attrs.varname}"/>
<f:param name="manageBeanName" value="#{cc.attrs.manageBeanName}"/>
<f:param name="manageBeanMethodName" value="#{cc.attrs.manageBeanMethodName}"/>
<f:param name="keyType" value="#{cc.attrs.keyType}"/>
<f:param name="isRequired" value="#{cc.attrs.isRequired}"/>
<f:param name="keyValue" value="#{cc.attrs.keyValue}"/>
</cc:implementation>
Implementation --
<h:panelGroup id="pnlGrp_editCustomerGroupManageLocal">
<asset:inputAutoComplete datatype="static" labelValue="abcd" id="samleAuto" varname="availableTags" >
<f:ajax event="clientEvent" onevent="alert('sfdksd')"></f:ajax>
</asset:inputAutoComplete>
</h:panelGroup>
The above is the implementation of composite component . I am not able to trigger ajax event from inside the component.
Please help me to know what could be the possible issue with the code .

After adding second composite component Error <f:ajax> contains an unknown id

I try to use composite components in my application. When i have added first component it has worked, but when i have added component once again, i have gotten error:
"javax.servlet.ServletException: contains an unknown id 'j_idt202:kladrRegion-dctKladrRegion' - cannot locate it in the context of the component searchButton"
This is code of implementation:
<cc:interface>
<!-- properties -->
<cc:attribute name="isDisabled" type="java.lang.Boolean" />
<cc:attribute name="freeForm" type="java.lang.String" />
<cc:attribute name="houseNum" type="java.lang.String" />
<cc:attribute name="korpusNum" type="java.lang.String" />
<cc:attribute name="flatNum" type="java.lang.String" />
<cc:attribute name="kladrRegion" type="java.lang.String" />
<cc:attribute name="cbKladrRegion" type="javax.faces.component.html.HtmlSelectOneMenu" />
<cc:attribute name="kladrRegions" type="java.util.List" />
<cc:attribute name="kladrCity" type="java.lang.String" />
<cc:attribute name="cbKladrCity" type="javax.faces.component.html.HtmlSelectOneMenu" />
<cc:attribute name="kladrCities" type="java.util.List" />
<cc:attribute name="kladrSearch" type="java.lang.String" />
<cc:attribute name="cbKladrSearch" type="org.icefaces.ace.component.combobox.ComboBox" />
<cc:attribute name="kladrSearches" type="java.util.List" />
<!-- methods -->
<cc:attribute name="listener"
method-signature="void actionListener(javax.faces.event.AjaxBehaviorEvent)"/>
<cc:attribute name="kladrRegionChange"
method-signature="void actionListener(javax.faces.event.AjaxBehaviorEvent)"/>
<cc:attribute name="kladrCityChange"
method-signature="void actionListener(javax.faces.event.AjaxBehaviorEvent)"/>
<cc:attribute name="kladrDlgCloseClick"
method-signature="void actionListener(javax.faces.event.AjaxBehaviorEvent)"/>
<cc:attribute name="kladrDlgSaveClick"
method-signature="void actionListener(javax.faces.event.AjaxBehaviorEvent)"/>
<cc:attribute name="searchKladrAction"
method-signature="java.lang.String action(java.lang.String)"/>
</cc:interface>
<cc:implementation>
<h:panelGrid styleClass="panelGrid" columns="2">
<h:panelGroup styleClass="panelGroup">
<div class="searchWrap">
<h:commandLink id="searchButton"
disabled="#{cc.attrs.isDisabled}"
onclick="openKladrDialog(this, '#{cc.clientId}')">
<h:graphicImage value="/resources/images/search.png"
alt="#{usertag.labelSearchAddr}" />
<f:ajax event="click"
render="kladrRegion-dctKladrRegion"
listener="#{cc.attrs.listener}"/>
</h:commandLink>
</div>
</h:panelGroup>
...etc.
</h:panelGrid>
<!-- dialog -->
<div id="#{cc.clientId}_kladrDialog">
<h:panelGrid styleClass="panelGrid" columns="3">
<h:outputLabel value="Country" class="outputLabel" for="kladrCountry" />
<h:panelGroup styleClass="panelGroup">
<ice:inputText disabled="true" value="Russia"
styleClass="ui-inputfield ui-corner-all inputText kladrSearchFormInput kladrSearchFormInputWidth" />
<h:inputHidden id="kladrCountry" value="179" />
</h:panelGroup>
<h:outputLabel />
<h:outputLabel value="Region" class="outputLabel" />
<h:selectOneMenu id="kladrRegion-dctKladrRegion"
value="#{cc.attrs.kladrRegion}" >
<f:selectItems value="#{cc.attrs.kladrRegions}" var="region"
itemLabel="#{region.value}" itemValue="#{region.id}" />
<f:ajax render="#this kladrCity-dctKladrCity"
listener="#{cc.attrs.kladrRegionChange}" />
</h:selectOneMenu>
... etc.
</div>
</cc:implementation>
This is code of tag:
<kladr:usertag isDisabled="#{PolicyBean.policyDisabled}"
freeForm="#{PolicyBean.policyAuto.holder.baseAddress.freeForm}"
houseNum="#{PolicyBean.policyAuto.holder.baseAddress.houseNum}"
korpusNum="#{PolicyBean.policyAuto.holder.baseAddress.korpusNum}"
flatNum="#{PolicyBean.policyAuto.holder.baseAddress.flatNum}"
kladrRegion="#{PolicyBean.kladrRegion}"
cbKladrRegion="#{PolicyBean.cbKladrRegion}"
kladrRegions="#{PolicyBean.kladrRegions}"
kladrCity="#{PolicyBean.kladrCity}"
cbKladrCity="#{PolicyBean.cbKladrCity}"
kladrCities="#{PolicyBean.kladrCities}"
kladrSearch="#{PolicyBean.kladrSearch}"
cbKladrSearch="#{PolicyBean.cbKladrSearch}"
kladrSearches="#{PolicyBean.kladrSearches}"
listener="#{PolicyBean.holderKladrDlgOpenClick}"
kladrRegionChange="#{PolicyBean.kladrRegionChange}"
kladrCityChange="#{PolicyBean.kladrCityChange}"
kladrDlgCloseClick="#{PolicyBean.kladrDlgCloseClick}"
kladrDlgSaveClick="#{PolicyBean.kladrDlgSaveClick}"
searchKladrAction="#{PolicyBean.kladrSearchClick(PolicyBean.kladrSearch)}" />
any ideas?
The problem is, that your <h:selectOneMenu id="kladrRegion-dctKladrRegion"> is in another parent component than your <h:commandLink id="searchButton" ....
It will work correctly, if you specify the correct absolute component id (or move your dialog inside the same parent).
See Find component by ID in JSF on how to find the correct component id.
The exception tells you that there is not component with id "kladrRegion-dctKladrRegion" inside the component <h:panelGroup> whid got the generated id "j_idt202".
If you're using PrimeFaces, you can use the EL Function component('id') which returns the correct component id. See http://www.primefaces.org/docs/guide/primefaces_user_guide_5_1.pdf, chapter 11.2.

Bean scope loss when using composite JSF components & action methods never being called

I've been asked to refactor the following code, which works:
<a4j:commandButton type="image"
image="/someImage.gif"
action="#{SomeViewController.someDeleteAction}"
onclick="return confirm('#{msg['a.message']}');"
render="someDataTableWithItems">
<f:setPropertyActionListener
target="#{SomeViewModel.selectedItem}" value="#{item}" />
</a4j:commandButton>
..to an alternative which allowed to use a custom popup to confirm the action. This snippet is embedded into a column inside a rich:dataTable component. #{item} is a reference to the object assigned to the row where this button is (defined in the dataTable as var="item").
I decided to do a JSF composite component (first one I do) to have something I could reuse. It's based on this answer by elias
<!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:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface>
<cc:attribute name="message" default="#{msg['a.default.message']}" />
<cc:attribute name="header"
default="#{msg['a.default.header']}" />
<cc:attribute name="action" required="true"
method-signature="java.lang.String action()" />
<cc:attribute name="actionListener" required="false"
method-signature="java.lang.String action()" />
<cc:attribute name="value" default="Send" />
<cc:attribute name="cancelBtn" default="#{msg['a.default.cancel']}" />
<cc:attribute name="confirmBtn" default="#{msg['a.default.ok']}" />
<cc:attribute name="render" default="#form" />
<cc:attribute name="type" default="submit" />
<cc:attribute name="image" required="false"/>
<cc:attribute name="tooltip" required="false" />
</cc:interface>
<cc:implementation>
<a4j:commandButton type="#{cc.attrs.type}" rendered="#{empty cc.attrs.actionListener and not empty cc.attrs.image}"
image="#{cc.attrs.image}" value="#{cc.attrs.value}"
oncomplete="#{rich:component('somePopup')}.show()">
<rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
#{cc.attrs.tooltip}
</rich:tooltip>
</a4j:commandButton>
<a4j:commandButton type="#{cc.attrs.type}" rendered="#{not empty cc.attrs.actionListener and not empty cc.attrs.image}"
actionListener="#{cc.attrs.actionListener}"
image="#{cc.attrs.image}" value="#{cc.attrs.value}"
oncomplete="#{rich:component('somePopup')}.show()">
<rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
#{cc.attrs.tooltip}
</rich:tooltip>
</a4j:commandButton>
<a4j:commandButton type="#{cc.attrs.type}" rendered="#{empty cc.attrs.actionListener and empty cc.attrs.image}"
value="#{cc.attrs.value}"
oncomplete="#{rich:component('somePopup')}.show()">
<rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
#{cc.attrs.tooltip}
</rich:tooltip>
</a4j:commandButton>
<a4j:commandButton type="#{cc.attrs.type}" rendered="#{not empty cc.attrs.actionListener and empty cc.attrs.image}"
actionListener="#{cc.attrs.actionListener}" value="#{cc.attrs.value}"
oncomplete="#{rich:component('somePopup')}.show()">
<rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
#{cc.attrs.tooltip}
</rich:tooltip>
</a4j:commandButton>
<rich:popupPanel id="somePopup"
header="#{cc.attrs.header}" autosize="true" resizeable="false">
<p>#{cc.attrs.message}</p>
<a4j:commandButton action="#{SomeViewController.someDeleteAction}" <!-- It should be #{cc.attrs.action} but I just put this for debug -->
value="#{cc.attrs.confirmBtn}" render="#{cc.attrs.render}"
oncomplete="#{rich:component('somePopup')}.hide()">
<cc:insertChildren />
</a4j:commandButton>
<h:commandButton value="#{cc.attrs.cancelBtn}"
onclick="#{rich:component('somePopup')}.hide(); return false;" />
</rich:popupPanel>
</cc:implementation>
</html>
..and replace the previous a4j:commandButton with this:
<my:buttonConfirm type="image" id="someID"
image="/someImage.gif"
action="#{SomeViewController.someDeleteAction}"
message="#{msg['a.confirmation.message']}"
render="someDataTableWithItems"
tooltip="#{msg['a.tooltip.message']}">
<f:setPropertyActionListener for="someID"
target="#{SomeViewModel.selectedItem}" value="#{item}" />
</my:buttonConfirm>
The popup appears and you can cancel the action, but when confirming it, SomeViewModel is reinstantiated again, losing the previous bean in scope.
The scope is a custom view scope got from here
I then changed the scope of the model to this one:
#Component("SomeViewModel")
#ViewScoped
Although I tried to use #ManagedBean instead of #Component, the app gave me autowiring errors so I left #Component. The scope was now kept.
I don't know whether the mix of JSF and Spring annotations on this way may have any other consequences.
However the scope of SomeViewModel was now fine, the f:setPropertyActionListener target was never set and the action #{SomeViewController.someDeleteAction} was never called. I have been unable to debug this (not sure where to put breakpoints to see what happens in the middle).
Thanks in advance for your help.
The <f:setPropertyActionListener> does not attach to a component id, but to an ActionSource. See this answer for how to do it.
Edit:
Basically instead of <f:setPropertyActionListener for="buttonId"> you'll have
<cc:interface>
<cc:actionSource name="source" targets="buttonId" />
…
</cc:interface>
<cc:implementation>
<a4jcommandButton id="buttonId" … />
</cc:implementation>
And point to it by <f:setPropertyActionListener for="source">.
After trying some suggestions and researching a little on my own I concluded that there is some kind of problem when using a4j:commandButton inside a rich:popupPanel. Action methods are not being called and attributes defined on f:setPropertyActionListener are not being setted. I have been unable to find out what is exactly getting lost in there.
I've seen examples on the Internet with a4j:commandButton inside a popup so I am not sure if this is caused by any of my dependencies. I use jsf-api 2.1.19, jsf-impl 2.1.19-jbossorg-1 and richfaces 4.3.5.Final.
This is the workaround I finally did. I hope it can be helpful to anyone with the same problem I had:
confirmButton.xhtml
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<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:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface>
<cc:attribute name="message"
default="Some message" />
<cc:attribute name="header"
default="Some header" />
<cc:attribute name="cancelBtn" default="No" />
<cc:attribute name="confirmBtn" default="Yes" />
<cc:attribute name="type" default="submit" />
<cc:attribute name="icon" required="false" />
<cc:attribute name="image" required="false" />
<cc:attribute name="action"
targets="popupConfirmButton" />
<cc:actionSource name="confirmListeners"
targets="popupConfirmButton" />
</cc:interface>
<cc:implementation>
<a4j:commandButton type="#{cc.attrs.type}"
image="#{cc.attrs.image}"
oncomplete="#{rich:component('popupConfirm')}.show()">
</a4j:commandButton>
<a4j:commandButton id="popupConfirmButton"
style="visibility: hidden;" render="#{cc.attrs.render}">
</a4j:commandButton>
<rich:popupPanel id="popupConfirm" header="#{cc.attrs.header}"
autosized="true" width="475" resizeable="false">
<f:facet name="controls">
<h:outputLink value="#"
onclick="#{rich:component('popupConfirm')}.hide(); return false;" />
</f:facet>
<h:panelGrid columns="2">
<h:graphicImage value="#{cc.attrs.icon}" height="64" width="64" />
<p>#{cc.attrs.message}</p>
</h:panelGrid>
<br />
<div align="right">
<a4j:commandButton value="#{cc.attrs.confirmBtn}"
onclick="#{rich:element('popupConfirmButton')}.click();
#{rich:component('popupConfirm')}.hide();" />
 
<h:commandButton value="#{cc.attrs.cancelBtn}"
onclick="#{rich:component('popupConfirm')}.hide(); return false;" />
</div>
</rich:popupPanel>
</cc:implementation>
</html>
Component usage
<my:confirmButton type="image" image="someButtonImage.gif"
icon="someWarningImage.gif"
action="#{SomeViewController.doStuff}"
message="Some message"
render="someComponentID">
<f:setPropertyActionListener for="confirmListeners"
target="#{SomeViewModel.someProperty}" value="foo" />
</my:confirmButton>

JSF 2 -- Composite component with optional listener attribute on f:ajax

I have a composite component that looks something like this:
<!DOCTYPE html>
<html
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:dm="http://davemaple.com/dm-taglib"
xmlns:rich="http://richfaces.org/rich"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:a4j="http://richfaces.org/a4j">
<cc:interface>
<cc:attribute name="styleClass" />
<cc:attribute name="textBoxStyleClass" />
<cc:attribute name="inputTextId" />
<cc:attribute name="labelText" />
<cc:attribute name="tabindex" />
<cc:attribute name="required" default="false" />
<cc:attribute name="requiredMessage" />
<cc:attribute name="validatorId" />
<cc:attribute name="converterId" />
<cc:attribute name="title"/>
<cc:attribute name="style"/>
<cc:attribute name="unicodeSupport" default="false"/>
<cc:attribute name="tooltip" default="false"/>
<cc:attribute name="tooltipText" default=""/>
<cc:attribute name="tooltipText" default=""/>
<cc:attribute name="onfail" default=""/>
<cc:attribute name="onpass" default=""/>
</cc:interface>
<cc:implementation>
<ui:param name="converterId" value="#{! empty cc.attrs.converterId ? cc.attrs.converterId : 'universalConverter'}" />
<ui:param name="validatorId" value="#{! empty cc.attrs.validatorId ? cc.attrs.validatorId : 'universalValidator'}" />
<ui:param name="component" value="#{formFieldBean.getComponent(cc.attrs.inputTextId)}" />
<ui:param name="componentValid" value="#{((facesContext.maximumSeverity == null and empty component.valid) or component.valid) ? true : false}" />
<ui:param name="requiredMessage" value="#{! empty cc.attrs.requiredMessage ? cc.attrs.requiredMessage : msg['validation.generic.requiredMessage']}" />
<ui:param name="clientIdEscaped" value="#{fn:replace(cc.clientId, ':', '\\\\\\\\:')}" />
<h:panelGroup layout="block" id="#{cc.attrs.inputTextId}ValidPanel" style="display:none;">
<input type="hidden" id="#{cc.attrs.inputTextId}Valid" value="#{componentValid}" />
</h:panelGroup>
<dm:outputLabel for="#{cc.clientId}:#{cc.attrs.inputTextId}" id="#{cc.attrs.inputTextId}Label">#{cc.attrs.labelText}</dm:outputLabel>
<dm:inputText
styleClass="#{cc.attrs.textBoxStyleClass}"
tabindex="#{cc.attrs.tabindex}"
id="#{cc.attrs.inputTextId}"
required="#{cc.attrs.required}"
requiredMessage="#{requiredMessage}"
title="#{cc.attrs.title}"
unicodeSupport="#{cc.attrs.unicodeSupport}">
<f:validator validatorId="#{validatorId}" />
<f:converter converterId="#{converterId}" />
<cc:insertChildren />
<f:ajax
event="blur"
execute="#this"
render="#{cc.attrs.inputTextId}ValidPanel #{cc.attrs.inputTextId}Msg"
onevent="on#{cc.attrs.inputTextId}Event" />
</dm:inputText>
<rich:message for="#{cc.clientId}:#{cc.attrs.inputTextId}" id="#{cc.attrs.inputTextId}Msg" style="display: none;" />
<script>
function on#{cc.attrs.inputTextId}Event(e) {
if(e.status == 'success') {
$('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').trigger($('##{cc.attrs.inputTextId}Valid').val()=='true'?'pass':'fail');
}
}
$('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').bind('fail', function() {
$('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}, ##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Label, ##{cc.attrs.inputTextId}Msg, ##{cc.id}Msg').addClass('error');
$('##{cc.id}Msg').html($('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Msg').html());
#{cc.attrs.onfail}
}).bind('pass', function() {
$('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}, ##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Label, ##{cc.attrs.inputTextId}Msg, ##{cc.id}Msg').removeClass('error');
$('##{cc.id}Msg').html($('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}Msg').html());
#{cc.attrs.onpass}
});
</script>
<a4j:region rendered="#{facesContext.maximumSeverity != null and !componentValid}">
<script>
$(document).ready(function() {
$('##{clientIdEscaped}\\:#{cc.attrs.inputTextId}').trigger('fail');
});
</script>
</a4j:region>
</cc:implementation>
</html>
I'd like to be able to add an optional "listener" attribute which if defined would add an event listener to my f:ajax but I'm having trouble figuring out how to accomplish this. Any help would be appreciated.
You need to specify the method-signature attribute of the <cc:attribute> tag in order to treat the attribute value as a method expression. You can use the JSTL view build time tag <c:if> to conditionally add the <f:ajax> tag.
<cc:interface>
<cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
<h:someComponent>
<c:if test="#{cc.getValueExpression('listener') != null}">
<f:ajax listener="#{cc.attrs.listener}" />
</c:if>
</h:someComponent>
</cc:implementation>
(the #{not empty cc.attrs.listener} won't work as EL would implicitly evaluate the attribute as a value expression)
Then you can use it as follows:
<my:someComposite listener="#{bean.listener}" />
Or when your environment doesn't support EL 2.2, then create a backing component:
#FacesComponent("someComponent")
public class SomeComponent extends UINamingContainer {
public boolean isHasListener() {
return getValueExpression("listener") != null;
}
}
which is to be declared and used as
<cc:interface type="someComponent">
<cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
<h:someComponent>
<c:if test="#{cc.hasListener}">
<f:ajax listener="#{cc.attrs.listener}" />
</c:if>
</h:someComponent>
</cc:implementation>
I've the same problems too, and my solution was create default value for the action method. I have only to create a class: MyComponent.java that contains all default methods signature.
<cc:interface>
<cc:attribute name="listener" method-signature="void listener()"
default="#{myComponent.doNothing()}" />
</cc:interface>
<cc:implementation>
<h:someComponent>
<f:ajax listener="#{cc.attrs.listener}" />
</h:someComponent>
</cc:implementation>
All of the above didn't work for me unfortunately, so I fiddled around and came up with the following solution.
<cc:interface type="someComponent">
<cc:attribute name="listener" method-signature="void listener()" />
</cc:interface>
<cc:implementation>
<h:someComponent>
<p:ajax listener="#{cc.attrs.listener}" onstart="#{cc.attrs.containsKey('listener') ? '' : 'return false'}" />
</h:someComponent>
</cc:implementation>
This will always bind the ajax behavior, but only execute it if there actually is a listener.

Resources