JSF 2.2 custom validator not triggered in ui:repeat - jsf-2

after a desperate period of trials and errors i come to you looking for help, since now i am even unsure if i am doing it all wrong.
My code below displays some fields from a user profile in read only input text boxes. The data comes from a bean as ArrayList of "field" objects, whos properties are described in the ui:param directives.
The user can click an unlock/lock button next to the box to start/finish editing her data.
I want to trigger input validation when the user presses enter after editing or clicks the lock button.
But whatever i tried, after editing and pressing enter or clicking the button, the changed value is not written back and my custom validator is not triggered. Somehow it seems that after clicking the button the then rendered input box is not bound to the field? However, the validator works (and all attributes get passed to it) if used in an input text that is always rendered - and i don't understand it :)
Please have a look at it. If i did not explain my intent well enough or if you need more code, please let me know.
Edit: the annotations of the backing bean:
#ManagedBean( name = "beanProfile" )
#RequestScoped
The jsf code:
<ui:composition>
<ui:param name="i18n" value="#{field.i18n}" />
<ui:param name="val" value="#{field.value}" />
<ui:param name="req" value="#{field.required}" />
<ui:param name="min" value="#{field.min}" />
<ui:param name="max" value="#{field.max}" />
<ui:param name="validationType" value="#{field.validationType}" />
<ui:param name="useHidden" value="#{field.hidden}" />
<ui:param name="locked" value="#{field.locked}" />
<ui:param name="fullName" value="#{beanProfile.name}" />
<h:form id="profileFrm">
<h:outputText value="#{msg.role_profile} " styleClass="headingOutputText" />
<h:outputText value="#{fullName}" styleClass="headerTitle" />
<p />
<h:panelGroup>
<ui:repeat var="field" value="#{beanProfile.userFields}">
<h:panelGrid columns="2">
<h:outputText value="#{msg[i18n]}" styleClass="headerTitle" />
<h:message showDetail="true" for="visInput" styleClass="warn"
rendered="#{!useHidden}" />
<h:message showDetail="true" for="invisInput" styleClass="warn"
rendered="#{useHidden}" />
</h:panelGrid>
<h:panelGrid columns="2" columnClasses="nameColumn">
<h:inputText id="visInput" required="true" value="#{val}"
rendered="#{!useHidden}" readonly="#{locked}" >
<f:validator validatorId="customValidator" />
<f:attribute name="requiredField" value="#{req}" />
<f:attribute name="minLen" value="#{min}" />
<f:attribute name="maxLen" value="#{max}" />
<f:attribute name="type" value="#{validationType}" />
</h:inputText>
<h:inputSecret id="invisInput" required="true" value="#{val}"
rendered="#{useHidden}" readonly="#{locked}">
<f:validator validatorId="customValidator" />
<f:attribute name="requiredField" value="#{req}" />
<f:attribute name="minLen" value="#{min}" />
<f:attribute name="maxLen" value="#{max}" />
<f:attribute name="type" value="#{validationType}" />
</h:inputSecret>
<h:commandLink id="lock" action="#{beanProfile.lock(field)}"
rendered="#{!locked}" title="#{msg.profile_lock}">
<h:graphicImage height="16" width="16"
name="images/padlock_unlocked.svg" alt="#{msg.profile_lock}" />
</h:commandLink>
<h:commandLink id="unlock" action="#{beanProfile.unlock(field)}"
rendered="#{locked}" title="#{msg.profile_unlock}">
<h:graphicImage height="16" width="16"
name="images/padlock_locked.svg" alt="#{msg.profile_unlock}" />
</h:commandLink>
</h:panelGrid>
</ui:repeat>
</h:panelGroup>
<p />
<h:commandButton id="submitProfileBtn" value="#{msg.cmd_submit}"
styleClass="button" includeViewParams="true"
action="#{beanProfile.submitProfile()}" />
</h:form>
</ui:composition>

The origin of your problem is that you are using the RequestScoped scope for your backing bean containing the Model, so when you want to do a postback request in order to execute the backing bean method the binding just have dissapeared (not in the server memory) and the JSF lifecycle doesn't execute as expected. The solution is to use a longer scope as the ViewScoped scope. Ideally you should use the CDI compatible viewScoped scope, so you have to use JSF 2.2.x version.

Thank you lametaweb, you led me in the right direction.
Being unable to use the #ViewScoped annotation i found the link
how-to-install-cdi-in-tomcat
and followed BalusC's directions for using CDI in Tomcat. Then i had to let all the java classes implement the Serializable interface.
Now i can use the #ViewScoped annotation and the validator gets triggered.

Related

f:param works only first time when using the same name in multiple commandButtons

My requierement is to upload some data and generate different reports for download with one click. In the example below I show appropriate button witch has a parameter that is sent to the download bean to select the correct version of the report.
But the f:param is only set the first time a p:commandButton is executed and then stays the same also for other buttons. Why is that and how can I solve this issue?
<h:form id="mainForm" enctype="multipart/form-data">
<p:fileUpload ...../>
....
....
<p:selectBooleanCheckbox id="opt1"
<p:ajax event="change" update="hiddingPanel"/>
</p:selectBooleanCheckbox>
<p:selectBooleanCheckbox id="opt2"
<p:ajax event="change" update="hiddingPanel"/>
</p:selectBooleanCheckbox>
<h:panelGroup id="hiddingPanel">
<p:commandButton id="doOpt1" rendered="#{bean.opt1 and !bean.opt2}"
actionListener="#{bean.readFile}" ajax="false" />
<p:fileDownload value="#{downloadBean.content}" />
<f:param name="reportType" value="REPORT1"/>
</p:commandButton>
<p:commandButton id="doOpt1" rendered="#{!bean.opt1 and bean.opt2}"
actionListener="#{bean.readFile}" ajax="false" />
<p:fileDownload value="#{downloadBean.content}" />
<f:param name="reportType" value="REPORT2"/>
</p:commandButton>
</h:panelGroup>
</h:form>
Thanks for any suggestions!
Regards.

Primefaces data for data table is getting confused

Sorry for the title but I have to explain what I want to do and what isn't working. My web ui with JSF 2.2.10 and primefaces 5.2.6 shows a search mask and search results upon a model.
Each search instance is shown as a tab in a tabView. When a tab is clicked, its corresponding search instance is shown under the tabView in a dataTable. A search instance is created upon a search template, so different search instances can have different input fields and different result fields (which are the columns in the data table).
The user is able to execute the search for an existing search instance again with or without changed search fields. Here is my problem...he ui work until the user has executed a search instance 2nd time. Now when the tab in the tabView is changed an exception is thrown and the javascript console shows 'Request return with error:parsererror primefaces.js.jsf?ln=primefaces$v=5.2.6:1'
Now the data of for instance search instance 1 is mixed up with search instance 2. A result field (data table column) from search instance 1 is tried to get from search instance 2, but this can fail.
This only occurs if a search instance is executed the 2nd time. Without 2nd execution I can change between the tabs without problem. My data structure is ok I think.
A class SearchInstance is "behind" a tab and contains a list of SearchResult. This SearchResult is a Map with the result fields.
This is the xhtml of the tabView. The tabView is only used as trigger here. A tab does not really contain the dataTable with the components, it updates only the form and the content is included under the tabView:
<h:form id="searchInstancesFormId">
<p:tabView id="searchTabViewId" value="#{searchBL.searchInstances}" var="curSearch" activeIndex="#{searchBL.activeTabIndex}">
<p:ajax event="tabChange" oncomplete="resizeResultViews();" listener="#{searchBL.setSelectedSearch(curSearch)}"
update="#form" immediate="true" process="#this" />
<p:ajax event="tabClose" listener="#{searchBL.removeSearchInstance(curSearch)}" update="#this"
oncomplete="resizeResultViews();" immediate="true" process="#this" />
<p:tab closable="#{curSearch.isCloseable()}">
</p:tab>
</p:tabView>
<h:panelGrid id="mainSearchAreaGridId">
<ui:include
src="#{not searchBL.selectedSearch.closeable ? '/sections/search/firstSearchTab.xhtml' : '/sections/search/searchInstanceTab.xhtml'}">
<ui:param name="curSearchInst" value="#{searchBL.selectedSearch}" />
</ui:include>
</h:panelGrid>
</h:form>
The 1st tab is the search template tab from the 2nd tab on there are search instances.
Here is the dataTable xhtml. The problem occurs when curValue is retrieved. Here in 'hitlistBL' bean the result field map is queried with the current property name. Here the name from the search instance selected before and it is tried to get the value from the new selected search instance:
<p:dataTable id="searchResultTableId" scrollable="true" value="#{curSearch.getSearchResults()}" scrollHeight="300" var="curSearchResult"
sortMode="multiple" rowKey="#{curSearchResult.hashCode()}" draggableColumns="true" resizableColumns="true" styleClass="hitlistDataTable"
paginator="true" rows="#{curSearch.hitlistRowsPerPage}" rowIndexVar="rowIndex" filteredValue="#{curSearch.filteredValues}"
selection="#{curSearch.selectedSearchResults}" widgetVar="hitlistTableVar">
<p:ajax event="rowSelect" update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId" />
<p:ajax event="rowUnselect" update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId" />
<p:ajax event="toggleSelect" update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId" />
<p:ajax event="rowSelectCheckbox"
update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId" />
<p:ajax event="rowUnselectCheckbox"
update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId" />
<p:ajax event="contextMenu" update="#(.resultlistActionGrid) :searchInstancesFormId:listResultTabViewId:searchResultTableContextMenuId"
oncomplete="PF('searchResultTableContextMenuVar').show(currentEvent);" />
<!-- double click listener -->
<p:ajax event="rowDblselect"
listener="#{editPropertyBL.initForEdit(curSearch.selectedSearchResults.get(0), curSearch.getDataViewDefinition())}"
oncomplete="PF('editPropertyDialogVar').show();" update=":editPropertyFormId #(.resultlistActionGrid)" />
<p:column style="width: 16px;">
<p:rowToggler />
</p:column>
<p:column selectionMode="multiple" style="width: 16px; text-align:center; padding-right: 12px;" />
<p:columns value="#{curSearch.determinePrimaryPropertyNames()}" var="curPrimaryPropName"
sortBy="#{curSearchResult[curPrimaryPropName].getValue()}" filterBy="#{curSearchResult[curPrimaryPropName].getValue()}"
filterMatchMode="contains">
<f:facet name="header">
<h:outputText value="#{displayNameResolver.resolveDisplayNameOfHitlistProperty(curPrimaryPropName, curSearch)}" />
</f:facet>
<ui:param name="curValue" value="#{hitlistBL.generatePropertyValue(curSearchResult, curPrimaryPropName)}" />
<ui:param name="isDocumentTitle" value="#{hitlistBL.isDocumentTitle(curPrimaryPropName)}" />
<ui:param name="isChoice" value="#{hitlistBL.isChoice(curPrimaryPropName, curSearch)}" />
<!-- doc title -->
<h:panelGroup rendered="#{curSearchResult.isChanged() and (isDocumentTitle)}" style="padding-right: 4px;">
<i class="fa fa-refresh" />
</h:panelGroup>
<!-- <p:commandLink action="#{contentBL.showContentExtern(curSearch.getViewId(), curSearchResult)}" value="#{curValue}" ajax="false"
rendered="#{(curSearchResult.isOfDataType('STRING', curPrimaryPropName)) and (isDocumentTitle) and (not isChoice)}" target="_blank" /> -->
<!-- document title -->
<h:outputText value="#{curValue}" title="#{curSearchResult.getRepresentationsAsString()}"
rendered="#{(curSearchResult.isOfDataType('STRING', curPrimaryPropName)) and (isDocumentTitle) and (not isChoice)}" />
<!-- data type STRING -->
<h:outputText value="#{curValue}"
rendered="#{curSearchResult.isOfDataType('STRING', curPrimaryPropName) and (not isDocumentTitle) and (not isChoice)}" />
<!-- data type UUID -->
<h:outputText value="#{curValue}" rendered="#{curSearchResult.isOfDataType('UUID', curPrimaryPropName) and (not isChoice)}" />
<!-- data type INTEGER32 -->
<h:outputText value="#{curValue}" rendered="#{curSearchResult.isOfDataType('INTEGER32', curPrimaryPropName) and (not isChoice)}">
<f:convertNumber integerOnly="true" groupingUsed="false" />
</h:outputText>
<!-- data type DOUBLE -->
<h:outputText value="#{curValue}" rendered="#{curSearchResult.isOfDataType('DOUBLE', curPrimaryPropName) and (not isChoice)}">
<f:convertNumber groupingUsed="false" />
</h:outputText>
<!-- data type DATETIME -->
<h:outputText value="#{curValue}" rendered="#{curSearchResult.isOfDataType('DATETIME', curPrimaryPropName) and (not isChoice)}">
<f:convertDateTime type="both" dateStyle="short" timeStyle="short" timeZone="#{settingsBL.getTimeZoneIdSet()}"
pattern="#{settingsBL.dateTimePattern}" />
</h:outputText>
<!-- CHOICE -->
<h:outputText value="#{hitlistBL.determineChoiceUiValue(curSearch.getViewId(), curSearchResult, curPrimaryPropName)}"
rendered="#{(not isDocumentTitle) and (isChoice)}" />
</p:columns>
</p:dataTable>
A search instance if executed with the following. A new search is done and the search instance member search result is exchanged with the new results:
<p:commandButton id="doSearchButtonFullMaskId" value="#{msgs['label.button.searchTemplate.search']}" icon="searchButtonIcon"
styleClass="searchButton" action="#{searchMaskBL.doSearch(curSearchInst)}" update="#form" process="#form"
oncomplete="resizeResultViews(); PF('hitlistTableVar').clearFilters();" style="margin-top: 10px;" />
Sorry this is a lot of text and it is quite difficult to explain. I don't know if this is my flaw or there is a bug somewhere. Maybe this javascript parsererror could give a hint, but I do not know how to get into/debug it.
Update: looks like a not wellformed xml of the ajax response?:
XML-Verarbeitungsfehler: nicht wohlgeformt Adresse: moz-nullprincipal: {96f3db57-4075-4d80-a250-03491a2ce33f} Zeile Nr. 90, Spalte 132: ...rrorpages/500.xhtml?dswid=-9189"></redirect>]]>]]></update></partial-response......-------------------------------------------------^
It points to the closing brackets of ></redirect>]]>]]><

binding on p:dataTable which is included multiple times causes ID unqiueness exceptions

I'm attempting to use a special PrimeFaces data exporter implementation as being used in this answer:
https://stackoverflow.com/a/14413932/396732
Our use case however is a little more complex, e.g. using Facelets to show the respective datatable for export, which has row expansions with sub tables in them.
insiderListManagerDialogs.xhtml:
<ui:include src="insiderListManagerListDialog.xhtml">
<ui:param name="idPrefix" value="il-emp" />
<ui:param name="widgetVarPrefix" value="ilEmp" />
<ui:param name="dialogTitle" value="#{msg['entity.employee.internal.plural.label']}" />
<ui:param name="mapPropertyName" value="insiderListEmployeesMap" />
<ui:param name="sortColumn" value="#{ent.lastTime}" />
<ui:param name="keyColumnsXhtml" value="listDialogInsiderListKeyColumns.xhtml" />
<ui:param name="valueColumnsXhtml" value="listDialogEmployeeValueColumns.xhtml" />
</ui:include>
insiderListManagerListDialog.xhtml:
<p:dialog id="#{idPrefix}-il-view-dialog"
widgetVar="#{widgetVarPrefix}Dialog"
header="#{dialogTitle}"
modal="true"
resizable="false"
appendToBody="true"
onShow="resetFilterInput( '#{idPrefix}' );"
width="1000"
height="750">
<h:form id="#{idPrefix}-il-view-form">
<p:dataTable id="list-entities"
widgetVar="listEntitiesTable"
binding="#{table}"
value="#{insiderListManager.listInstances}"
var="key"
scrollable="true"
scrollHeight="625"
scrollWidth="985"
resizableColumns="true"
expandedRow="#{insiderListManager.listEntitiesRowTogglerOpen}"
filteredValue="#{insiderListManager.filteredEntities}"
sortBy="#{sortColumn}"
styleClass="bx-datatable-header-hidden-m"
tyle="width: 970px;">
<f:facet name="header">
<p:inputText id="globalFilter"
onkeypress="if (event.keyCode == 13) { listEntitiesTable.filter(); return false;}"
size="70"
styleClass="bx-dt-header-element" />
<p:commandButton id="search-btn"
icon="ui-icon bx-icon-search"
value="#{msg['common.action.search.label']}"
process="#this"
onclick="listEntitiesTable.filter(); return false;"
styleClass="bx-dt-header-element" />
<p:commandButton title="#{msg['common.action.excelExport.all.hint']}"
icon="ui-icon bx-icon-excel"
action="#{insiderListManager.exportXls( table )}"
ajax="false"
styleClass="bx-button-border bx-dt-header-element" />
<div class="bx-clear-float" />
</f:facet>
<p:column width="15"
resizable="false">
...
<p:rowExpansion>
<p:dataTable value="#{insiderListManager[mapPropertyName].get(key)}"
var="value">
<ui:include src="#{valueColumnsXhtml}" />
</p:dataTable>
</p:rowExpansion>
</p:dataTable>
<h:panelGroup id="button-panel" layout="block" styleClass="left-button-panel mt-5px">
<p:commandButton id="toggle-button"
icon="#{insiderListManager.listEntitiesRowTogglerOpen ? 'ui-icon ui-icon-circle-triangle-e' : 'ui-icon ui-icon-circle-triangle-s'}"
value="#{insiderListManager.listEntitiesRowTogglerOpen ? msg['common.action.collapseAll.label'] : msg['common.action.expandAll.label']}"
process="#this"
update="#form">
<f:setPropertyActionListener target="#{insiderListManager.listEntitiesRowTogglerOpen}"
value="#{not insiderListManager.listEntitiesRowTogglerOpen}" />
</p:commandButton>
</h:panelGroup>
</h:form>
</p:dialog>
Note the
<p:commandButton title="..."
icon="ui-icon bx-icon-excel"
action="#{insiderListManager.exportXls( table )}"
... />
which is using the bound data table as described in the link posted at the top.
We are using JSF 2.1 (Mojarra 2.1.22 - and we cannot switch to MyFaces) which allows expressions to construct IDs like #{idPrefix}-il-view-dialog from a <ui:param name="idPrefix" value="il-emp" />.
However, we are getting ID uniqueness exception:
Caused by: java.lang.IllegalStateException: Component ID il-emp-il-view-form:list-entities:globalFilter has already been found in the view.
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:846)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.util.Util.checkIdUniqueness(Util.java:830)
at com.sun.faces.application.view.StateManagementStrategyImpl.saveView(StateManagementStrategyImpl.java:144)
at com.sun.faces.application.StateManagerImpl.saveView(StateManagerImpl.java:133)
at com.sun.faces.application.view.WriteBehindStateWriter.flushToWriter(WriteBehindStateWriter.java:225)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:419)
at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:131)
at com.ocpsoft.pretty.faces.application.PrettyViewHandler.renderView(PrettyViewHandler.java:163)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:288)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:288)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:288)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:121)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594)
... 49 more
Same happens to other buttons with explicit IDs if omitted, e.g. the search button (<p:commandButton id="search-btn" ...).
QUESTION:
Why is this happening and how can we avoid this problem when we are bound to using PrimeFaces dialogs for each use case displayed? (we have more than one dialog)
Note that when removing the <p:dataTable ... binding="..." the page works correctly.
You're basically binding physically different <p:dataTable> components to one and same EL variable. If this EL variable is not null (i.e. already set by a previous component in the same view), then JSF will reuse it instead of creating a new one, and override all of its attributes during view build time, resulting in "weird" behavior during view render time and potential duplicate component ID errors during state saving.
In your specific case, you can use UIComponent#getNamingContainer() to directly reference the parent <p:dataTable> component from inside a child component.
<p:dataTable ... (NO binding!)>
...
<p:comandButton ... action="#{bean.exportXls(component.namingContainer)}" />
Might it happen that it's nested in another NamingContainer component, then you need to use #{component.namingContainer.parent.namingContainer} instead. And so forth for multiple ones.

actionListener not called inside <c:if>

I have the following code, Using jsf2.2, primefaces 3.2.
My requirement is to update the Project depending on the updateFlag.
when i use c:if (xmlns:c="http://java.sun.com/jsp/jstl/core") like the following code the action Listener for the Update commandButton is not called. but if i use < p:panel rendered="#{projectBean.updateFlag}" > instead of < c:if > it works. Please help i dint get it, i think i should use c:if but its not working.
<p:dialog widgetVar="projectUpdate" id="projectUpdatePanel" modal="false" >
<p:panel>
<c:if test="#{projectBean.updateFlag == false}">
<h:outputText value="Project Title" />
<p:inputText disabled="true" value="#{projectBean.selectedProjectDo.projectTitle}" />
<p:commandButton value="Update" disabled="true" />
<p:commandButton value="Cancel" actionListener="#{projectBean.cancelUpdate}" />
</c:if>
<c:if test="#{projectBean.updateFlag == true}">
<h:outputText value="Project Title"/>
<p:inputText value="#{projectBean.selectedProjectDo.projectTitle}" />
<p:commandButton value="Update" actionListener="#{projectBean.updateProject}" />
<p:commandButton value="Cancel" actionListener="#{projectBean.cancelUpdate}" />
</c:if>
</p:panel>
</p:dialog>
You better just use it the following way (put a condition on the disabled attribute)
<p:panel>
<h:outputText value="Project Title"/>
<p:inputText disabled="#{not projectBean.updateFlag}"
value="#{projectBean.selectedProjectDo.projectTitle}" />
<p:commandButton disabled="#{not projectBean.updateFlag}" value="Update"
actionListener="#{projectBean.updateProject}" />
<p:commandButton value="Cancel" actionListener="#{projectBean.cancelUpdate}" />
</p:panel>
In general : don't use JSTL tags unless you really need them...
This is a classic example of non-DRY code, which is bad. Daniel shows perfectly how to make it DRY, however he didn't explain the cause of your problem.
Based on the problem symptoms, this will happen when #{projectBean} is a view scoped bean. View scoped beans are stored in the JSF view state. So, view scoped beans are only available after restore view phase. However, JSTL tags runs during restore view phase, while the view scoped beans are not available yet. This causes creation of a brand new view scoped bean instance, which is then later replaced by the real view scoped bean which was stored in the restored JSF view state. The brand new and separate view scoped bean which is used by JSTL will have all its properties set to default and thus the block which has updateFlag=false will always be invoked.
See also:
JSTL in JSF2 Facelets... makes sense?

f:param to composite components

In JSF2.1 composite component if we try to pass f:param to a composite component (command button) and recieve in the component as editableValueHolder ,It doesn't seems to be working,
Any ideas?
<mycomp id="button" outcome="newpage" >
<f:param name="foo" outcome="bar" for="button"/>
</mycomp>
compositeComponent....
<cc:interface>
<cc:attribute name="action" targets="commandLink" required="true" />
</cc:interface>
<cc:implementation>
<h:commandLink id="commandLink" action="#{cc.attrs.action}">
</h:commandLink>
</cc:implementation>
Use <cc:insertChildren>.
<h:commandLink ...>
<cc:insertChildren />
</h:commandLink>

Resources