Referencing components inside of composite components (and vice versa) - jsf

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.

Related

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.

UIComponent getAttributes only gets last value from JSF

I have a JSF 2.2 composite component which is used more than once on the same page.
#FacesComponent("myComponent")
public class MyComponent extends UIComponent {
public void test() {
System.out.printlin(getAttributes("value"));
}
}
component xhtml:
<composite:interface componentType="myComponent">
<composite:attribute name="value" required="true" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<a4j:jsFunction name="test" action="#{cc.test()}" execute="input" />
<s:span id="input">
<h:inputText value="#{cc.attrs.value}" onclick="test()" />
</s:span>
</composite:implementation>
page xhtml
<my:myComponent value="#{bean.value}" /> -- line 1
<my:myComponent value="#{bean2.value}" /> -- line 2
When I click on the first myComponent, it calls test() but prints the value of bean2.value instead of bean.value. If I remove line 2 then it prints bean.value. I thought that the call to getAttributes() would get the value for current composite component, but it seems that it is only getting the value for the last composite component on the page. Could someone explain to me how the attributes are supposed to work or what I am missing?
Here is the solution, with id attribute appended to the jsFunction to distinguish it from other same components on the page:
<composite:interface componentType="myComponent">
<composite:attribute name="id" type="java.lang.String" />
<composite:attribute name="value" required="true" type="java.lang.String" />
</composite:interface>
<composite:implementation>
<a4j:jsFunction name="#{cc.attrs.id}test" action="#{cc.test()}" execute="input" />
<s:span id="input">
<h:inputText value="#{cc.attrs.value}" onclick="#{cc.attrs.id}test()" />
</s:span>
</composite:implementation>

o:validateBean method="validateCopy" does not validate inputs in composite component

I'm facing a problem using OmniFaces 2.1 o:validateBean with method="validateCopy" when my inputs are composite components. With method="validateActual", it validates as expected.
JSF implementation: Mojarra 2.2.8-jbossorg-1.
My composite component source:
<composite:interface>
<composite:attribute name="target" />
<composite:attribute name="label"/>
<composite:attribute name="value" />
<composite:attribute name="required" />
<composite:attribute name="size" />
<composite:attribute name="disabled" />
<composite:attribute name="styleInput" required="false" />
<composite:editableValueHolder name="input" targets="input" />
<composite:clientBehavior name="change" event="change" targets="input" />
<composite:clientBehavior name="keypress" event="keypress" targets="input" />
</composite:interface>
<composite:implementation>
<p:outputLabel id="label" for="input" value="#{cc.attrs.label}" />
<h:panelGrid columns="3">
<p:inputText id="input" value="#{cc.attrs.value}"
style="#{cc.attrs.styleInput}" size="#{cc.attrs.size}"
disabled="#{cc.attrs.disabled}" required="#{cc.attrs.required}">
</p:inputText>
<p:message for="input" display="icon">
<p:effect type="pulsate" event="load" delay="500" />
</p:message>
</h:panelGrid>
</composite:implementation>
I tracked down to the omnifaces source and got to the source point below, in the o:validateBean:
ValueReference valueReference = getValueReference(context.getELContext(), valueExpression);
if (valueReference.getBase().equals(base)) {
operation.run((EditableValueHolder) component, valueReference);
}
In a case where I use a simple input text, the valueReference().getBase() returns my to be validated bean. In a case where I use a composite component, the valueReference().getBase() returns a reference to CompositeComponentAttributesELResolver.ExpressionEvalMap.
Is threre a way to change my composite component so that it works with o:validateBean?

How to return value from backing bean as composite component attribute

I have the following JSF composite component:
<composite:interface componentType="myComp">
<composite:attribute name="input" type="java.lang.Integer" />
<composite:attribute name="output" type="java.lang.Integer" />
<composite:attribute name="action" method-signature="java.lang.String action()"/>
</composite:interface>
<composite:implementation>
<h:inputText id="input" value="#{cc.attrs.input}" />
</composite:implementation>
Assuming the composite is invoked as
<h:form id="form">
<cc:myComposite id="cc" input="#{bean.input}"
output="#{bean.output}" action="#{bean.action}" />
</h:form>
Once bean.action is invoked it sets a value in bean.output. I need to access this value in my javascript, something like this
document.getElementById('form:cc:output').value
From myComp I can set the attribute with
getAttributes().put("output", output);
But the data resides in the backing bean. Any ideas?
You need a component to submit the 'output' value to the backing bean. Use inputHidden to accomplish this:
<composite:interface componentType="myComp">
<composite:attribute name="input" type="java.lang.Integer" />
<composite:attribute name="output" type="java.lang.Integer" />
<composite:attribute name="action" method-signature="java.lang.String action()"/>
</composite:interface>
<composite:implementation>
<h:inputText id="input" value="#{cc.attrs.input}" />
<h:inputHidden id="output" value="#{cc.attrs.output}" />
</composite:implementation>
Then you'll be able to set the 'output' value using its hidden input element via javascript.

a composite component inside panelgrid is not 'expanded'

Basically, i've been using this pattern again and again inside a panelgrid :
<h:panelGrid columns="2" >
<h:outputLabel for="heroName" value="Hero Name : " />
<h:panelGroup>
<h:inputText label="Hero Name : "
value="#{ccBean.data.map['heroName']}" id="heroName" />
<p:message for="heroName" />
</h:panelGroup>
...
</h:panelGrid>
I notice that i duplicate things so much, like the id in h:inputText, which will be duplicated again in for="idValue" for outputLabel, and then again in p:message. The same thing happens with the inputText's label value and h:outputLabel value.
So, i was thinking about making a simple composite component to wrap these up :
<composite:interface>
<composite:attribute name="id" required="true" />
<composite:attribute name="label" required="true" />
<composite:attribute name="value" required="true" />
<composite:attribute name="includeLabel" required="false" default="false"/>
</composite:interface>
<composite:implementation>
<h:outputLabel for="#{cc.attrs.id}" value="#{cc.attrs.label}"
render="#{cc.attrs.includeLabel}" />
<h:panelGroup>
<h:inputText label="#{cc.attrs.label}"
value="#{cc.attrs.value}" id="#{cc.attrs.id}" />
<p:message for="#{cc.attrs.id}" />
</h:panelGroup>
</composite:implementation>
And then i make use of the composite components :
<h:panelGrid columns="2" >
<s:inputText
id="heroName"
label="Hero Name : "
value="#{ccBean.data.map['heroName']}"
includeLabel="true"
/>
<s:inputText
id="heroName2"
label="Hero Name 2 : "
value="#{ccBean.data.map['heroName2']}"
includeLabel="true"
/>
...
</h:panelGrid>
That composite component works fine, but the panelGrid now assumes that the s:inputText is one component. Before using the composite component, there are 2 components in one row, which is the outputLabel and the panelGroup.
Is it possible to hint the composite component to 'expand' itself in this case so that it takes up 2 columns instead of 1 column in my panelGrid ?
Thank you !
No, unfortunately not. Your best bet is to create a simple tag file instead.
See also:
How to make a grid of JSF composite component?

Resources