Declare markup outside cc:implementation and let the cc:implementation include it - jsf

Well.
I created a custom component to CRUD and the form page is a parameter.
In this case I need two pages, one to define the component and another with the form.
Is there any way to do that?
As follow:
Implementation:
Component
<cc:interface>
<cc:attribute name="formPage" type="java.lang.String" required="true"/>
.....
</cc:interface>
<cc:implementation>
...
<h:form id="form-crud">
<ui:include src="#{cc.attrs.formPage}" />
</h:form>
...
</cc:implementation>
Page 1: crud.xhtml
<ui:define name="content-template" >
<comp:crud
paginaForm="crud-form.xhtml" />
</ui:define>
Page 2: crud-form.xhtml
<html ...
<p:panelGrid id="grid-crud" columns="2" >
<h:outputLabel for="dsName" value="Name: " />
<p:inputText id="dsName" value="#{crudBean.dsName}"/>
</p:panelGrid>
</html>
New Implementation (sample)
To simplify, I would like to have component defnition and form in the same page, something like that. Is it possible?
I know I could use a template, but the custom componet has more attributes.
New Component
<cc:implementation>
...
<h:form id="form-crud">
<XX:SOMETING name="#{cc.attrs.formContent}" />
</h:form>
...
</cc:implementation>
New Page 1: new-crud.xhtml
...
<ui:define name="content-template" >
<comp:crud
form="new-form" />
<XX:SOMETING id="new-form">
<p:panelGrid id="grid-crud" columns="2" >
<h:outputLabel for="dsName" value="Name: " />
<p:inputText id="dsName" value="#{crudBean.dsName}"/>
</p:panelGrid>
</XX:SOMETING>
</ui:define>
...

You can make use of <f:facet> exactly like as those work with e.g. <h:dataTable>/<h:column> header/footer.
First declare a <cc:facet> with the desired name:
<cc:interface>
<cc:facet name="form" />
</cc:interface>
Then declare a <cc:renderFacet> in the desired place where it should end up:
<cc:implementation>
...
<h:form>
<cc:renderFacet name="form" />
</h:form>
...
</cc:implementation>
Now you can use it as follows:
<comp:crud>
<f:facet name="form">
...
</f:facet>
</comp:crud>
Please note that this construct is also possible with "plain vanilla" Facelets tagfiles via <ui:define>/<ui:insert> mechanisms.

Related

How pass a converter into a composite component with p:inputText?

I want to put a filter of a p:datatable into a composite component. The filter use the p:inputText tag and an p:ajax tag to define a typing delay. The problem is, that the converter inside the composite component do nothing. When I use javax.faces.Long, the filter is always String.
The working code, that I want to use in a facelet:
<p:column headerText="colum title"
filterBy="#{p.p.idx}" sortBy="#{p.p.idx}">
<f:facet name="filter">
<p:inputText id="indexFilterInput"
converter="javax.faces.Long" class="ui-column-filter">
<p:ajax event="keyup" delay="800"
oncomplete="PF('portDT').filter()" />
</p:inputText>
</f:facet>
<h:outputText value="#{portRow.portIdx}" />
</p:column>
I put the f:facet into the composite component:
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
<cc:interface>
<cc:attribute name="widgetVar" />
<cc:attribute name="delay" />
<cc:attribute name="converter" />
<cc:insertFacet name="filter" />
</cc:interface>
<cc:implementation>
<f:facet name="filter">
<p:inputText id="filterInput" converter="#{cc.attrs.converter}"
class="ui-column-filter" type="search">
<p:ajax event="keyup" delay="#{cc.attrs.delay}"
oncomplete="PF('#{cc.attrs.widgetVar}').filter()" />
</p:inputText>
</f:facet>
</cc:implementation>
</ui:component>
Here is the call of the component gf:filterField:
<p:column style="width:4em"
headerText="#{lbl['general.column.id']}" exportable="true"
filterBy="#{p.p.id}" sortBy="#{p.p.id}">
<gf:filterField id="index" widgetVar="portDT" delay="800" converter="javax.faces.Long"/>
<h:outputText value="#{portRow.portId}" />
</p:column>
When change the converter in the p:inputText inside the composite component, the converter is also ignored. I need to pass the converter as a parameter, because some columns are String columns and some are Long.
Is there any other tag in the cc:interface as cc:attribute?

Mojarra-Bug while resolving #{cc} when passed as include parameter and used by included composite?

Basic Question
Is this a bug in Mojarra or am I trying to do something that is not supported by the spec?
Overview
I have a composite component "outerComposite" that ui:includes a facelet with another composite component "innerComposite". I want to pass the client id of the outer composite to the inner composite using <ui:param>. Depending on how the EL expression looks like, this works or fails on Wildfly 10/11 but works in all cases on Websphere Liberty 17.0.0.1.
Basic Code
outerCompositeUsingPage.xhtml
<h:head />
<h:body>
<h:form id="form">
<app:outerComposite id="outer" value="Test" />
</h:form>
</h:body>
outerComposite.xhtml that includes a facelet with innerComposite:
<cc:implementation>
<c:set var="clientIdViaSet" value="#{cc.clientId}" />
<div id="#{cc.clientId}">
<ui:include src="innerCompositeInclude.xhtml">
<ui:param name="clientIdDirect" value="#{cc.clientId}" /> <-- WORKS
<ui:param name="clientIdViaMethod" value="#{compositeHelperBean.getClientId(cc)}" /> <-- FAILS
<ui:param name="clientIdViaSet" value="#{clientIdViaSet}" /> <-- WORKS
</ui:include>
</div>
innerCompositeInclude.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:app="http://xmlns.jcp.org/jsf/composite/components/app" >
<app:innerComposite id="inner"
value1Key="Direct"
value1Value="#{clientIdDirect}"
value2Key="Via Method"
value2Value="#{clientIdViaMethod}"
value3Key="Via c:set"
value3Value="#{clientIdViaSet}"
/>
</ui:composition>
innerComposite.xhtml
<cc:interface>
<cc:attribute name="value1Key" type="java.lang.String" />
<cc:attribute name="value2Key" type="java.lang.String" />
<cc:attribute name="value3Key" type="java.lang.String" />
<cc:attribute name="value1Value" type="java.lang.String" />
<cc:attribute name="value2Value" type="java.lang.String" />
<cc:attribute name="value3Value" type="java.lang.String" />
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<h:panelGrid columns="2">
<h:outputText value="#{cc.attrs.value1Key}" />
<h:outputText value="#{cc.attrs.value1Value}" />
<h:outputText value="#{cc.attrs.value2Key}" />
<h:outputText value="#{cc.attrs.value2Value}" />
<h:outputText value="#{cc.attrs.value3Key}" />
<h:outputText value="#{cc.attrs.value3Value}" />
</h:panelGrid>
</div>
</cc:implementation>
ComponentHelperBean.java
#ApplicationScoped
#Named
public class CompositeHelperBean {
public String getClientId(UIComponent comp) {
return comp.getClientId();
}
}
Observation
Because #{cc.clientId} is passed as a parameter to an include file, I expect #{cc.clientId} to be the value of the outer composite. This indeed works.
Because #{compositeHelperBean(cc)} just calls cc.getClientId(), I expect this to yield the same result as the direct EL expression #{cc.clientId}. This is not the case; instead, it resolves to the client id of the inner composite component.
The result looks as follows in Wildfly 10/11:
Direct form:outer <--- OK
Via Method form:outer:inner <--- WRONG
Via c:set form:outer <--- OK
The result looks as follows in Websphere Liberty Profile 17.0.0.1
Direct form:outer <--- OK
Via Method form:outer <--- OK
Via c:set form:outer <--- OK
Research
I have looked into the code of Mojarra as well as MyFaces. Both have a TagHandlerImpl that create a ValueExpression. In there, they use a regular expression to determine if the EL has something to do with cc. Both expressions are different which could explain why the behaviour is different.

Referencing components inside of composite components (and vice versa)

I'm having one or two problems with Primefaces (v5.2):
Referencing components inside composite components
Let's say I have a composite component that wraps an inputfield:
myinputfield.xhtml
<composite:interface>
<composite:attribute name="value" />
...
</composite:interface>
<composite:implementation>
<h:inputText value="#{cc.attrs.value}" />
</composite:implementation>
(Of course the real application does "a little" more.)
In my page I now use this field like this:
index.xhtml
<my:myinputputfield value=#{controller.inputstring} />
This works. But:
Know I want to reference that inner inputfield from outside, for example for labels or messages. Something like:
<p:inputLabel for="mif" value="Your Input:"/>
<my:myinputputfield id="mif" value=#{controller.inputstring} />
<p:message for="mif" />
Of course that doesn't work, because id isn't defined for myinputfield.
So the first idea that pops to mind is to extent the cc like this:
myinputfield.xhtml (new)
<composite:interface>
<composite:attribute name="id" />
<composite:attribute name="value" />
...
</composite:interface>
<composite:implementation>
<h:inputText id="{cc.attrs.id}" value="#{cc.attrs.value}" />
</composite:implementation>
Which does not work as well. I tried different things and read different answers and articles without finding an answer to this.
The second problem is the complete opposite:
Referencing components outside composite components
This time imagine it the other way around. I have a customized label, message or in my case a tooltip:
mytooltip.xhtml
<composite:interface>
<composite:attribute name="for" />
<composite:attribute name="value" />
...
</composite:interface>
<composite:implementation>
<p:toolTip for="#{cc.attrs.for}" value="#{cc.attrs.value}" />
</composite:implementation>
This time I want to attach mytooltip to an existing component:
index.xhtml
<h:outputtext id="ot" value="Hello World!" />
<my:mytooltip for="ot" value="since 1974" />
Which also does not work. (Of course!?)
This problem I had some time ago and solved it by inclduing the outputText in the composite component.
But I have the feeling it should be possible to manage both user cases. But how?
Referencing components inside composite components
give the internal input a static id
<composite:interface>
<composite:attribute name="value" />
...
</composite:interface>
<composite:implementation>
<h:inputText id="input" value="#{cc.attrs.value}" />
</composite:implementation>
reference the internal component as with any naming container:
<p:inputLabel for="mif:input" value="Your Input:"/>
<my:myinputputfield id="mif" value=#{controller.inputstring} />
<p:message for="mif:input" />
Referencing components outside composite components
the canonical way is to use the full client id:
<h:form id="form">
<h:outputText id="ot" value="Hello World!" />
<my:mytooltip for=":form:ot" value="since 1974" />
</h:form>
but, since you are passing the search expression to a PF component, you can also:
<h:form>
<h:outputText id="ot" value="Hello World!" />
<my:mytooltip for="#form:ot" value="since 1974" />
</h:form>
or generically:
<p:tabView>
<p:tab title="random tab">
<h:outputText id="ot" value="Hello World!" />
<my:mytooltip for="#namingcontainer:ot" value="since 1974" />
</p:tab>
</p:tabView>
or even:
<h:outputText value="Hello World!" />
<my:mytooltip for="#composite:#previous" value="since 1974" />
however, in such cases, a tag-component/facelet-tag-file could be a better approach.

How to define the content of repeater inside composite through its interface?

I have a composite component with a ui:repeat and want to define the content of the ui:repeat through the interface of the composite.
Following code is working in MyFaces but looks more like a hack since variable name varRepeat must be known outside of composite and it only works if no other childrens are provided that should be rendered somewhere else.
View
Define content of the ui:repeat
<comp:myRepeater value="#{of:createIntegerArray(1,5)}">
<h:outputText id="varComp" value="#{varRepeat}"/>
</comp:myRepeater>
Composite myRepeater
<composite:attribute name="value" type="java.lang.Object"/>
<composite:implementation>
<ui:repeat var="varRepeat" value="#{cc.attrs.value}">
<composite:insertChildren/>
</ui:repeat>
</composite:implementation>
That's the best you can get given that var attribute doesn't support EL. To make it clear to the enduser, document the name of var in <cc:interface shortDescription> and/or <cc:attribute shortDescription>.
<cc:interface>
<cc:attribute name="value" type="java.util.Collection"
shortDescription="A collection of items. Each item is exposed in EL under the variable name 'item'." />
<cc:interface>
<cc:implementation>
<ui:repeat value="#{cc.attrs.value}" var="item">
<cc:insertChildren />
</ui:repeat>
</cc:implementation>
Usage:
<my:repeat value="#{bean.list}">
<h:outputText value="#{item}" />
</my:repeat>
The OmniFaces showcase application has also a similar composite for long: <os:listDocs>:
<cc:implementation>
<c:set var="docs" value="#{page.current.documentation[cc.attrs.paths]}" />
<ui:fragment rendered="#{not empty docs}">
<h3>#{cc.attrs.header}</h3>
<ul>
<ui:repeat value="#{docs}" var="path">
<li><code>#{cc.attrs.label}</code></li>
</ui:repeat>
</ul>
</ui:fragment>
</cc:implementation>
Usage:
<os:listdocs header="VDL" paths="vdl" url="#{_vdlURL}#{path}.html" label="#{fn:replace(path,'/', ':')}" />
<os:listdocs header="API" paths="api" url="#{_apiURL}#{path}.html" label="#{fn:replace(path,'/', '.')}" />
<os:listdocs header="Source code" paths="api" url="#{_srcURL}#{path}.java" label="#{fn:replace(path,'/', '.')}" />
As a design hint, if you use a sensible attribute name for the collection, the var may become more self-documenting. E.g. ask for items as value and provide a var="item".
<my:repeat items="#{[1,2,3,4,5]}">
<h:outputText value="#{item}"/>
</my:repeat>

JSF datatable, tomahawk radio

I'm attempting to use a t:radio in my rich:datatable but it's always complaining about the fact that it can't find the component. I did some googling and I have to declare the full name, but I actually think I'm doing that.
Anyone can point me what I'm doing wrong? The code (I'm not going to give a minified version because I'm guessing it's going wrong with the compositions):
letterDetailTemplate.xhtml:
<h:form id="generateLetterForm">
<ui:include src="addStandardLetterMain.xhtml" />
<ui:include src="addStandardLetterText.xhtml" />
<ui:include src="buttons.xhtml" />
</h:form>
addStandardLetterText.xhtml (the radio button is over here, in the slrDataTable):
<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:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:e="http://minfin.regondes2/entity"
xmlns:ccffdecorate="http://java.sun.com/jsf/composite/ccffdecorate"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:l="http://minfin.regondes2/layout"
xmlns:o="http://omnifaces.org/ui"
xmlns:t="http://myfaces.apache.org/tomahawk">
<ui:param name="entityBB" value="#{letterBB}" />
<ui:param name="type" value="Dossier.Letter" />
<l:screenzone id="addStandardLetterTextPanel"
title="#{AppMessages[type.concat('.addStandardLetterText.title')]}">
<h:outputStylesheet name="letterText.css" library="stylesheets" />
<h:outputScript name="letter.js" library="scripts" />
<a4j:region>
<a4j:outputPanel id="letterTextPanel">
<h:inputTextarea id="letterText" name="letterText"
value="#{entityBB.entity.text}" styleClass="letterText" />
<script type="text/javascript">
language: '#{screen.locale.language}'
CKEDITOR.replace( 'generateLetterForm:letterTex', {
});
</script>
</a4j:outputPanel>
<h:panelGroup styleClass="rButtonPanelAlignLeft" layout="block"
rendered="#{dossierContextBB.dossierContext == 'EDIT'}">
<a4j:commandButton
value="#{AppMessages[type.concat('.add.region')]}"
actionListener="#{entityBB.findAvailableRegionStandardLetters()}"
render="slrPopupFormPanel" limitRender="true" status="ajaxStatus"
onclick="LETTER.CKUpdate()"
oncomplete="#{rich:component('addStandardLetterRegion')}.show();" />
</h:panelGroup>
</a4j:region>
<rich:popupPanel id="addStandardLetterRegion" modal="true"
onmaskclick="#{rich:component('addStandardLetterRegion')}.hide()"
autosized="true">
<f:facet name="header">
<h:outputText
value="#{AppMessages['Dossier.Letter.StandardLetter.region.popup.title']}" />
        </f:facet>
<f:facet name="controls">
<h:outputLink value="#"
onclick="#{rich:component('addStandardLetterCentrum')}.hide(); return false;">
X
</h:outputLink>
</f:facet>
<a4j:outputPanel id="slrPopupFormPanel">
<t:selectOneRadio id="slrOption"
value="#{standardLetterBB.selected}" layout="spread"
converter="#{standardLetterBB.converter}">
<f:selectItems value="#{entityBB.availableStandardLetterText}"
var="standardLetter" itemLabel="" itemValue="#{standardLetter}" />
</t:selectOneRadio>
<rich:dataTable id="slrDataTable"
value="#{entityBB.availableStandardLetterText}"
var="standardLetter" noDataLabel="No standard letters found"
rowKeyVar="index">
<rich:column headerClass="ListTitle">
<t:radio for="generateLetterForm:slrDataTable:slrOption" index="#{index}" />
</rich:column>
<e:column entity="#{standardLetter.dossierType}" type="#{type}"
property="type" />
<e:column entity="#{standardLetter}" type="#{type}"
property="numbering" />
<e:columnTranslatable entity="#{standardLetter}" type="#{type}"
property="text" />
</rich:dataTable>
</a4j:outputPanel>
<div class="rButtonPanel">
<h:commandButton value="#{AppMessages['general.action.add']}">
<a4j:ajax event="click" execute="generateLetterForm"
listener="#{entityBB.addMotivationToReport}"
oncomplete="#{rich:component('addStandardLetterRegion')}.hide()"
render="reportMotivationTextPanel" limitRender="true" />
</h:commandButton>
<a4j:commandButton value="#{AppMessages['general.action.cancel']}"
onclick="#{rich:component('addStandardLetterRegion')}.hide()"
limitRender="true" immediate="true" bypassUpdates="true" />
</div>
</rich:popupPanel>
</l:screenzone>
Note that this view works and renders and displays the data from the backingbean when I remove the t:radio.
The errormessage I'm getting is:
SEVERE: java.lang.IllegalStateException: Could not find component 'generateLetterForm:slrDataTable:slrOption' (calling findComponent on component 'generateLetterForm:slrDataTable:0:j_idt258')
javax.faces.FacesException: java.lang.IllegalStateException: Could not find component 'generateLetterForm:slrDataTable:slrOption' (calling findComponent on component 'generateLetterForm:slrDataTable:0:j_idt258')
Change for="generateLetterForm:slrDataTable:slrOption" to for=":generateLetterForm:slrOption" and specify id for your t:radio in datatable also. Don't forget the ":" before generateLetterForm.

Resources