h:dataTable composite component, cc.attrs.var, IllegalArgumentException - jsf

I'm trying to create my own dataTable like the primefaces one. The problem is that cc.attrs.var when used throws a IllegalArgumentException. So I'm wondering how I can have the var attribute like Primefaces.
<cc:interface>
<cc:attribute name="value"/>
<cc:attribute name="var"/>
<cc:attribute name="styleClass"/>
</cc:interface>
<cc:implementation>
<div>Previous</div>
<div>Next</div>
<h:dataTable value="#{cc.attrs.value}" var="#{cc.attrs.var}" styleClass="#{cc.attrs.styleClass}">
<ui:insert/>
</h:dataTable>
</cc:implementation>

As per the UIData#setValueExpression() javadoc, it's not allowed to have an EL expression in var attribute.
Throws:
IllegalArgumentException - if name is one of id, parent, var, or rowIndex
Your best bet is to create a backing component wherein you manually evaluate and set the var attribute of the UIData component bound to <h:dataTable> during the postAddToView event.
<cc:interface componentType="yourTableComposite">
<cc:attribute name="value" />
<cc:attribute name="var" />
</cc:interface>
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
<h:dataTable binding="#{cc.table}" value="#{cc.attrs.value}">
<cc:insertChildren />
</h:dataTable>
</cc:implementation>
#FacesComponent("yourTableComposite")
public class YourTableComposite extends UINamingContainer {
private UIData table;
public void init() {
table.setVar((String) getAttributes().get("var"));
}
public UIData getTable() {
return table;
}
public void setTable(UIData table) {
this.table = table;
}
}
Note that I fixed the <ui:insert> to be <cc:insertChildren>. The <ui:insert> can only be used in <ui:composition>/<ui:decorate>.
See also:
Initialize a composite component based on the provided attributes
How does the 'binding' attribute work in JSF? When and how should it be used?

Related

JSF composite component, multiple value fields

Is it possible to have multiple values for a composite component. here is the example of what I want to be able to do:
<foo:zipcode id="zipcode" value="#{bean.zipcode}" city="#{bean.city}" >
<f:ajax eventt="changeEvent" update="city">
</foo:zipcode>
<h:inputText id="city" value="#{bean.city} />
Where the composite component has an input for the zipcode. Once entered I do a lookup/validation of the zipcode and update the city.
The composite component looks like:
<cc:interface componentType="component.zipCode">
<cc:attribute name="value" required="true" />
<cc:attribute name="city" />
</cc:interface>
<cc:implementation>
<p:inputText id="zipCode" value="#{cc.attrs.value}" >
<p:ajax partialSubmit="true" process="#this" update="#this" listener="#{cc.lookupZip}"/>
</p:inputText>
</cc:implementation>
Then for the backing component I have:
#FacesComponent(value = "component.zipCode")
public class UIZipCode extends UIInput implements NamingContainer {
public void lookupZip(AjaxBehaviorEvent event) {
// Code to lookup zipcode and get city.
// What goes HERE to update the city passed as attribute?
}
}
Am I missing something obvious. I've dug through tons of BalusC's posts and tried something like:
ValueExpression city = (ValueExpression) getAttributes().get("city");
city.setValue(FacesContext.getCurrentInstance().getELContext(), cityString);
I have most of this working, but can't figure out how to set the value of city in the backing bean from the lookupZip method. The value of city is always
null. Is this possible?

Get form data in Backing Component from Composite Component

My Composite Component contains the following form:
<cc:interface componentType="answerCompositeComponent">
<cc:attribute name="AnswerType" type="code.elephant.domainmodel.AnswerType" required="true" />
<cc:attribute name="ItemSource" type="code.elephant.domainmodel.Answer" required="true" />
<cc:attribute name="QuestionId" type="java.lang.Long" required="true" />
</cc:interface>
<cc:implementation>
<input jsf:id="sc#{cc.attrs.ItemSource.answerId}" />
</cc:implementation>
How can I access the value of the <input jsf:id="sc#{cc.attrs.ItemSource.answerId}" /> in my Backing Component? I tried the following in my backing bean in the overriden processUpdates method.
Answer ItemSource = (Answer) getValueExpression("ItemSource").getValue(context.getELContext());
String formid = String.format("sc%d", ItemSource.getAnswerId());
String get = context.getExternalContext().getRequestParameterMap().get(formid);
String get is always null. Is there a way to get the value of the input?
PS: I know that using plain html in jsf is not the purpose of it. I'm just interessted how my plan is achievable.
I never used plain html with jsf attributes, so I don't know if it's applicable.
Generally, this is a common way to access nested components in a composite:
<cc:interface componentType="answerCompositeComponent">
<cc:attribute name="AnswerType" type="code.elephant.domainmodel.AnswerType" required="true" />
<cc:attribute name="ItemSource" type="code.elephant.domainmodel.Answer" required="true" />
<cc:attribute name="QuestionId" type="java.lang.Long" required="true" />
</cc:interface>
<cc:implementation>
<h:inputText id="questionInput" binding="#{cc.input}" />
<!-- maybe something like this might work
<input jsf:id="questionInput" jsf:binding="#{cc.input}" />
-->
</cc:implementation>
where
#FacesComponent("answerCompositeComponent")
public class AnswerCompositeComponent extends UINamingContainer
{
private UIInput input;
#Override
public void processUpdates(FacesContext context)
{
super.processUpdates(context);
Object value = input.getValue();
Object localValue = input.getLocalValue();
Object submittedValue = input.getSubmittedValue();
// do your things with values
}
public UIInput getInput()
{
return input;
}
public void setInput(UIInput input)
{
this.input = input;
}
}
Note that a composite backing component is a NamingContainer, so prefer static (or none at all) nested component IDs. Avoid dynamic IDs, unless you really need them and you know exactly what you're doing.

Composite component - change external bean value

I have got a composite component:
<cc:interface>
<cc:attribute name="value" required="true">
</cc:interface>
<cc:implementation>
<h:outputText value="#{cc.attrs.value}"/>
<h:commandButton action="#{internalBean.someAction}"/>
</cc:implementation>
And I would like to change the #{cc.attrs.value} by #{internalBean.someAction}, in other words: change the (String) value of user defined (external) bean by a method of my composite component. How I can do it?
Thanks.
UPDATE
One way I can think of is to use <f:setPropertyActionListener>.
<cc:interface>
<cc:attribute name="value" required="true"/>
</cc:interface>
<cc:implementation>
<h:outputText value="#{cc.attrs.value}"/>
<h:commandButton action="#{internalBean.someAction}">
<f:setPropertyActionListener value="#{cc.attrs.value}" target="#{internalBean.stringValueFromExternalBean}"/>
</h:commandButton>
</cc:implementation>
But it is not necessary to use StringBuilder:
<composite:interface>
<composite:attribute name="value" required="true"/>
</composite:interface>
<cc:implementation>
...
<f:setPropertyActionListener target="#{cc.attrs.value}" value="#{internalBean.value}"/>
...
</cc:implementation>
Where values are normal String. It works fine!
I have finally found the best solution ever. It works immediately as a normal component - every change updates the external bean property:
public void setValue(String value) {
this.value = value;
FacesContext facesContext = FacesContext.getCurrentInstance();
ELContext elContext = facesContext.getELContext();
ValueExpression valueExpression = facesContext.getApplication().getExpressionFactory()
.createValueExpression(elContext, "#{cc.attrs.value}", String.class);
valueExpression.setValue(elContext, value);
}

validator method not work in the JSF custom composite components

JSF custom composite components
input.xhtml
<cc:interface>
<cc:attribute name="validator"/>
</cc:interface>
<cc:implementation>
<h:inputText validator="#{cc.attrs.validator}"/>
</cc:implementation>
*.xhtml
<l:input value = ... validator="#{testValidator.validator}"/>
java code
#ManagedBean
public class TestValidator {
public void validator(FacesContext context, UIComponent component, Object value) throws ValidatorException {
System.out.println("Call validator");
}
}
PropertyNotFoundException:
validator="#{testValidator.validator}": The class 'TestValidator' does not have the property 'validator'.
How to solve this problem?
my final way:
This is indeed not going to work. In order to attach a validator to an input component specified by the composite, you need to register the input component as a <cc:editableValueHolder> in the <cc:interface> first.
<cc:interface>
<cc:editableValueHolder name="yourInputName" targets="yourInputId" />
</cc:interface>
<cc:implementation>
<h:inputText id="yourInputId" ... />
</cc:implementation>
This way, any <f:validator for="yourInputName"> nested in the composite component declaration will be applied to the desired input component.
<l:input>
<f:validator validatorId="myValidator" for="yourInputName" />
</l:input>
You'll only need to replace the tight coupled validator method by a real standalone Validator implementation.
#FacesValidator("myValidator")
public class MyValidator implements Validator {
// ...
}
Note: the standard JSF validators like <f:validateLength>, <f:validateRequired>, etc have all also a for attribute for this purpose.
You need to define a cc:attribute "validator" with targets attribute:
<cc:interface>
<cc:attribute name="validator" targets="inputId"/>
</cc:interface>
<cc:implementation>
<h:inputText id="inputId"/>
</cc:implementation>
(Notice that I don't define validator attribute in h:inputText)
This is easy as long as you don't implement component class for your composite component. Well... even then it is easy, but it won't work if your component class extends UIInput. It works if it extends UINamingComponent class (I am not sure if this is the best solution, or if you can make it work with UIInput but it works).
So if you want to have your own component class try this one:
<cc:interface componentType="myComponent>
<cc:attribute name="validator" targets="inputId"/>
</cc:interface>
<cc:implementation>
<h:inputText id="inputId"/>
</cc:implementation>
#FacesComponent("myComponent")
public class MyComponent extends UINamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
//your code here
}

JSF composite button reinitiliazes view scoped managed bean

As soon as a composite which encapsulates a commandButton is included in my .xhtml, the viewscoped bean is reinitialized no matter which commandButton is used. Is my composite wrong? Please let me know because I realy would like to use composites for my buttons.
Simplyfied testcase:
#ManagedBean
#ViewScoped
public class Test implements Serializable {
private static final long serialVersionUID = 123456L;
private static int i = 0;
private int counter;
private String table;
private transient DataModel<String> model;
#PostConstruct
public void test() {
System.out.println(".......... PostConstruct");
i++;
List<String> modelData = new ArrayList<String>();
modelData.add("hello");
modelData.add("world");
model = new ListDataModel<String>(modelData);
}
public int getCounter() {
return counter;
}
public String getTable() {
return table;
}
public DataModel<String> getModel() {
return model;
}
public void tableListener() {
String data = model.getRowData();
table = data.toUpperCase();
}
}
No matter which button is clicked (2nd or 3th column), the postConstruct method is called over and over again
<h:form>
<h:dataTable style="width: 40em" var="line" value="#{test.model}">
<h:column>
<f:facet name="header">string</f:facet>
#{line}
</h:column>
<h:column>
<f:facet name="header">actie...1</f:facet>
<h:commandButton value="toUpper" immediate="true" >
<f:ajax event="click" execute="#form" render=":testTable" listener="#{test.tableListener}" />
</h:commandButton>
</h:column>
<h:column>
<f:facet name="header">actie...2</f:facet>
<cmp:rowAction managedBean="#{test}" label="button" listener="tableListener"
tooltip="test via composite" img="stop.png" render=":testTable"/>
</h:column>
</h:dataTable>
</h:form>
As soon as the last column (header actie...2) is removed, then the #PostConstruct is called only once, as expected.
Why does the presence of my composite forces to reinitialize the viewscoped bean? What's wrong with my composite, it works, but it shouldn't reinitialize the managed bean:
<?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:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<!-- INTERFACE -->
<cc:interface>
<cc:attribute name="label" required="true"/>
<cc:attribute name="render"/>
<cc:attribute name="tooltip"/>
<cc:attribute name="img"/>
<cc:attribute name="listener" required="true"/>
<cc:attribute name="managedBean" required="true"/>
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<h:commandButton id="btn_#{cc.attrs.label}" title="#{cc.attrs.tooltip}" immediate="true"
image="/resources/img/#{cc.attrs.img}">
<f:ajax event="click" execute="#form" render="#{cc.attrs.render}" listener="#{cc.attrs.managedBean[cc.attrs.listener]}" />
</h:commandButton>
</cc:implementation>
based on this post, JSF 2 - How can I add an Ajax listener method to composite component interface? i've found a solution. The problem was the declaration of the listener attribute cc:attribute name="listener" required="true"/> it should be cc:attribute name="listener" method-signature="void listener()" required="true"/>
There is still one (in my case minor) problem as mentioned by BalusC in the previously mentioned post: I can't us the AjaxBehvaiorEvent. I'm using Netbeans 6.9.1, Gfish3.1 and Mojarra2.1.1

Resources