How can I do this with a custom/composite component? - jsf

I tried to ask a more specific question but I think i may have been overly specific so I'll zoom out to get a better advice.
I'm trying to create a composite/custom component that will accept two attributes
A list of strings --> names of fields
a list of lists of <String, Value> --> groups of fields with <fieldName, fieldValue>
example values:
[Street, Country, State, ZipCode]
[{(Street, Greenfield), (Country, USA)}, {(Country, Canada), (ZipCode, 3333)}]
The component will render this, based on the attributes:
The reason I am having difficulties with this component is that I don't know what's the proper way to maintain placeholders for fields that were not entered originally but can be added by the user through the component.
In the above example, for the first set, these would be State and ZipCode.
My Idea was to create a dummy object with all the fields and on submittion copy the dummy object's values to the data structure passed in the attributes.
The problem I was facing was not knowing how to read the values on component creation and altering the List passed through the attribute on submission.
I will add sample code soon (Although that shouldn't be a crucial for answering this question)
Thank you just for reading this far!! :-)
My Code (Again, not required for answering the question but may be helpful to understand my challenge)
The Composite Component Code:
<cc:interface componentType="dynamicFieldList">
<cc:attribute name="styleClass" default="fieldType" />
<cc:attribute name="form" default="#form"
shortDescription="If used, this is the name of the form that will get executed" />
<cc:attribute name="list" type="java.util.List" required="true"
shortDescription="The values of the list. The type must be List of FieldGroup" />
<cc:attribute name="groupTypes" type="java.util.List" required="true"
shortDescription="The types that will be available to choose from for each field. The type of this must be List of FieldType" />
</cc:interface>
<cc:implementation>
<h:dataTable id="table" value="#{cc.model}" var="fieldGroup">
<h:column>
<ui:repeat var="field" value="#{fieldGroup.values}">
<utils:fieldType value="#{field.value}" type="#{field.type}"/>
</ui:repeat>
</h:column>
<h:column>
<h:commandButton value="delete" action="#{cc.remove}">
<f:ajax render="#{cc.attrs.form}" execute="#{cc.attrs.form}" />
</h:commandButton>
</h:column>
</h:dataTable>
<h:commandButton value="add" action="#{cc.add}">
<f:ajax render="#{cc.attrs.form}" execute="#{cc.attrs.form}" />
</h:commandButton>
</cc:implementation>
The component bean java code:
#FacesComponent(value = "dynamicFieldGroupList")
// To be specified in componentType attribute.
#SuppressWarnings({ "rawtypes", "unchecked" })
// We don't care about the actual model item type anyway.
public class DynamicFieldGroupList extends UIComponentBase implements NamingContainer
{
private transient DataModel model;
private List<FieldGroup> displayList;
public DynamicFieldGroupList()
{
super();
List<FieldGroup> list = new ArrayList<FieldGroup>();
for (FieldGroup group : getList()){
FieldGroup currGroup = new FieldGroup("Untitled");
//Assumption - Each type may exist only once in a group.
Map<FieldType, FieldDetail> hash = new HashMap<FieldType, FieldDetail>();
for (FieldDetail detail: group.getDetails()){
hash.put(detail.getType(), detail);
}
// While creating the dummy object, insert values the exist or placeholders if they don't.
for (FieldType type : getGroupTypes()){
if (hash.containsKey(type)){
currGroup.addDetail(hash.get(type));
} else {
currGroup.addDetail(new FieldDetail(type,null));
}
}
list.add(currGroup);
}
// Assign the created list to be the displayed (dummy) object
setDisplayList(list);
}
public void add()
{
// Add a new group of placeholders
FieldGroup group = new FieldGroup("Untitled");
for (FieldType type: getGroupTypes()){
group.addDetail(new FieldDetail(type, null));
}
getList().add(group);
}
public void remove()
{
getDisplayList().remove(model.getRowData());
}
#Override
public String getFamily()
{
return "javax.faces.NamingContainer"; // Important! Required for
// composite components.
}
public DataModel getModel()
{
if (model == null)
model = new ListDataModel(getDisplayList());
return model;
}
private List<FieldGroup> getList()
{ // Don't make this method public! Ends otherwise in an infinite loop
// calling itself everytime.
return (List) getAttributes().get("list");
}
private List<FieldType> getGroupTypes()
{ // Don't make this method public! Ends otherwise in an infinite loop
// calling itself everytime.
return (List) getAttributes().get("groupTypes");
}
public void setDisplayList(List<FieldGroup> displayList)
{
this.displayList = displayList;
}
public List<FieldGroup> getDisplayList()
{
return displayList;
}
}

You can use a Map to hold the values for each group.
Map<String, Object> values = new HashMap<String, Object>();
Assuming that you've all those maps in a List<Map<String, Object>> and the field names in a List<String>, then you can get/set them all basically as follows
<ui:repeat value="#{allValues}" var="values">
<ui:repeat value="#{fieldNames}" var="fieldName">
<h:outputLabel value="#{fieldName}" />
<h:inputText value="#{values[fieldName]}" />
<br/>
</ui:repeat>
</ui:repeat>

Related

DataTable .rowIndex always returns 0

What am I not doing right?
#Named("utilityController")
#RequestScoped
public class UtilityController {
public DataModel<Result> getResultSample() {
Result[] resultSample = new Result[11];
//Populate the array
return new ArrayDataModel<>(resultSample);
}
}
In The JSF:
<h:dataTable id="sampleResult" value="#{utilityController.resultSample}" var="item" styleClass="table table-bordered table-striped table-hover table-condensed" >
<h:column>
<f:facet name="header">SN</f:facet>
#{utilityController.resultSample.rowIndex}
</h:column>
<h:column>
<f:facet name="header">Subject</f:facet>
#{item.subject.name}
</h:column>
....
</h:dataTable>
The rowIndex always returns 0 as can be seen above. Please can some help me pin point what I am doing wrongly
What am I not doing right?
Creating the model in a getter method. Never do that. All getter methods should look like this:
public DataModel<Result> getResultSample() {
return resultSample;
}
The getter method is invoked on every iteration round. You're basically clearing out the model from the previous iteration round and returning a brand new one, with all state (such as current row index) reset to default value.
Move that job to bean's #PostConstruct method.
private DataModel<Result> resultSample;
#PostConstruct
public void init() {
Result[] results = new Result[11];
// ...
resultSample = new ArrayDataModel<Result>(results);
}
public DataModel<Result> getResultSample() {
return resultSample;
}
As to your concrete functional requirement, you can also just reference UIData#getRowIndex() without wrapping the value in a DataModel.
public Result[] getResults() { // Consider List<Result> instead.
return results;
}
<h:dataTable binding="#{table}" value="#{bean.results}" var="result">
<h:column>#{table.rowIndex + 1}</h:column>
<h:column>#{result.subject.name}</h:column>
</h:dataTable>
Note that I incremented it with 1 as it's 0-based while humans expect an 1-based index.
See also:
How and when should I load the model from database for h:dataTable
Why JSF calls getters multiple times
How does the 'binding' attribute work in JSF? When and how should it be used?

Assign 'value expression' in place of 'method expression' in JSF

In my composite component, I iterate a list<list<javaDetailClass>>. I get all my <h:commandButon> attribute's values through value expression like #{iterator.value}. But the problem comes with attribute action, where action accepts only method expression. whereas I can assign only value expression there, resulting in MethodNotFoundException
<cc:interface>
<cc:attribute name="formElements" />
</cc:interface>
<cc:implementation>
<c:forEach items="#{cc.attrs.formElements}" var="element">
<c:forEach items="#{element}" var="iterator">
<h:commandButton id="#{iterator.id}"
value="#{iterator.value}"
action="#{iterator.action}">
</h:commandButton>
</c:forEach>
</c:forEach>
</cc:implementation>
Can anyone help me in fixing this?
Thanks in advance.
UPDATE
this will be the detail class in my situation,
package com.stackoverflow.test;
public class TestData {
/*Properties based on the implementation of your composite.
Change type where it is needed*/
private String id;
private String value;
private String attributeName;
private String action;
public TestData() {
}
/*Getters and setters omitted*/
}
Bean.java simply holds an ArrayList of ArrayList. The constructor simply created five TestData objects and assigns some default value to its attributes.
package com.stackoverflow.test;
import java.util.ArrayList;
import javax.faces.bean.*;
#ManagedBean
#RequestScoped
public class Bean {
private ArrayList<ArrayList<TestData>> list = new ArrayList<ArrayList<TestData>>();
public Bean() {
ArrayList<TestData> testDataList = new ArrayList<TestData>();
TestData data;
for(int i = 0; i < 5; i++) {
data = new TestData();
data.setId("ID" + i);
data.setValue("VALUE" + i);
data.setAttributeName("ATTRIBUTE" + i);
/**this sets the action attribute of TestData with a API from some other managed bean**/
data.setAction("someOtherManagedbean.someactionAPI");
testDataList.add(data);
}
list.add(testDataList);
}
public ArrayList<ArrayList<TestData>> getList() {
return list;
}
public void setList(ArrayList<ArrayList<TestData>> list) {
this.list = list;
}
}
index.html simply calls the composite by assinging the value of "#{bean.list} to the name attribute
I'm assuming that your TestData.java has the following method public String getAction() (since I'm seeing a setAction(String)) and not
public String action(). Therefore, the reason why you are getting a MethodNotFoundException is because you are supplying the wrong method name to the action attribute. In your case it should be iterator.getAction and not iterator.action. You only supply the abbreviated names when an attribute is expecting a value expression. The interface below has been modifed.
<cc:interface>
<cc:attribute name="formElements" />
</cc:interface>
<cc:implementation>
<c:forEach items="#{cc.attrs.formElements}" var="element">
<c:forEach items="#{element}" var="iterator">
<h:commandButton id="#{iterator.id}"
value="#{iterator.value}"
action="#{iterator.getAction}">
</h:commandButton>
</c:forEach>
</c:forEach>
</cc:implementation>

Values of h:inputText inside ui:repeat are not processed

I want to process this form (valueChangueListener is not valid in real case).
This is the back bean:
public class TestBean extends PrivateBaseBean implements Serializable {
private List<String> strings;
#PostConstruct
public void init() {
strings = new ArrayList<String>();
strings.add("");
strings.add("");
strings.add("");
}
public void saveAction(ActionEvent event) {
StringBuilder textToShowInMessage = new StringBuilder();
for (String string : strings) {
textToShowInMessage.append(string);
textToShowInMessage.append("; ");
}
FacesMessage msg = new FacesMessage(super.getBundle().getString(
textToShowInMessage.toString()), "");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
getters... setters...
An the view:
....
<h:form>
<ui:repeat var="string" value="#{testBean.strings}">
<h:inputText value="#{string}" />
<br />
</ui:repeat>
<p:commandButton value="#{msg.save}"
actionListener="#{testBean.saveAction}" icon="ui-icon-disk"
update="#form" />
</h:form>
...
When the form is processed in the back bean string list always is blank.
How to process form intput's inside iteration, without any value changue listener?
There are some screenshots:
The same problem occurs with action or actionListener on
Your problem is not connected with PrimeFaces <p:commandButton>'s behaviour, but rather with a scoping problem that is implicilty created when using the <ui:repeat> tag.
First of all, let's depart from your example. Basically, you've got
<ui:repeat value="#{bean.strings}" var="s">
<h:inputText value="#{s}"/>
</ui:repeat>
with the backing List<String> strings.
The culprit is here: value="#{s}". The exported by <ui:repeat> variable s is visible only within its loop and it is not bound to any managed bean's property, but instead only to a local variable. Put it differently, s is not bound/equal to bean.strings[index] as one would expect and has no knowledge, as we see, where it originated from. So basically, you're off with a unilateral relationship: value from the bean is printed in your input properly, but the reverse is not happening.
The workarounds
Workaround #1: wrapper classes / model objects
The situation can be overcome by using a wrapper object for your class. In case of a string it could be a 'simple mutable string', like below:
public class MString {
private String string;//getter+setter+constructor
}
In this case the iteration will be working as predicted:
<ui:repeat value="#{bean.mstrings}" var="ms">
<h:inputText value="#{ms.string}"/>
</ui:repeat>
with the backing List<MString> mstrings.
Note that if you have your model class, like User, and will change its properties within <ui:repeat> the class itself will be effectively a wrapper, so that the properties will be set appropriately.
Workaround #2: chained property access
Another workaround consists of accessing an element of your collection directly from within a <h:inputText> tag. This way, any such property will be set by accessing the bean, then collection, then setting the property at the desired index. Excessively long, but that's how it is. As to the how question, <ui:repeat> provides for an exported current iteration status variable, varStatus, that will be used to access the array/collection in the managed bean.
In this case the iteration will also be working as predicted:
<ui:repeat value="#{bean.strings}" var="s" varStatus="status">
<h:inputText value="#{bean.strings[status.index]}"/>
</ui:repeat>
with the ordinary backing List<String> strings.
My workaround solution take the value directly from the page:
<ui:repeat id="repeat" value="#{bean.strings}" var="s" varStatus="status">
<h:inputText id="x" value="#{s.field}"/>
<h:commandLink style="margin: .5em 0" styleClass="commandLink" actionListener="#{bean.save(status.index)}" value="#{bundle.Send}"/>
</ui:repeat>
The save method:
public void save(String rowid) {
String jsParam = Util.getJsParam("repeat:" + rowid + ":x");
System.out.println("jsParam: " + jsParam); //persist...
}
The getJsParam method:
public static String getJsParam(String paramName) {
javax.faces.context.FacesContext jsf = javax.faces.context.FacesContext.getCurrentInstance();
Map<String, String> requestParameterMap = jsf.getExternalContext().getRequestParameterMap();
String paramValue = requestParameterMap.get(paramName);
if (paramValue != null) {
paramValue = paramValue.trim();
if (paramValue.length() == 0) {
paramValue = null;
}
}
return paramValue;
}

How to passing value of hiddenFiled as parameter for actionbean?

My page display list Category Name. i want to when user clicks category name , it will
display list product by category name. in this code, i want to passing CateogryId as value of h:inputHidden. it's same as <h:inputText value="#{produtBean.categoryId}"></h:inputText> .
Tkanks you for reading !
Code from xhtml
<ui:repeat value="#{productBean.listCategory}" var="c">
<h:form>
<h:inputHidden value="#{productBean.categoryId}" ></h:inputHidden>
<h:commandLink value="#{c.name}" action="#{productBean.listProductByCt}" ></h:commandLink>
</h:form>
</ui:repeat>
Code from ProductBean
public String listProductByCt()
{
if(categoryId==0)
{
return "index";
}
listProduct = new ProductsDB().listProducts(categoryId);
return "product";
}
The <h:inputHidden> doesn't work that way. The value you attempted to "pass" into it is also kind of weird. It's the same value for every item of the list. You should be using <f:param> instead. You probably also want to pass the #{c.id} or #{c.name} instead.
<h:commandLink value="#{c.name}" action="#{productBean.listProductByCt}">
<f:param name="categoryId" value="#{c.id}" />
</h:commandLink>
With
#ManagedProperty("#{param.categoryId}")
private Integer categoryId; // +setter
Alternatively, if you're already on Servlet 3.0 / EL 2.2, then you can just pass it as method argument.
<h:commandLink value="#{c.name}" action="#{productBean.listProductByCt(c.id)}">
with
public String listProductByCt(Integer categoryId) {
// ...
}
See also:
How can I pass selected row to commandLink inside dataTable?

Is there an alternative to c:choose in JSF when using f:validator

I understand I cannot use <c:choose> within a component in a jsf page. I am trying to see if there is an alternative. I looked at the Tomahawk and that isn't what I really need. I am trying to validate negative and positive numbers in a column. I want to be able to choose between the 2 validator tags that I have created. I tried using the rendered attribute but it still doesn't work. Below is kind of what I am looking for but it is not working like I want it to. Does anyone have any suggestions??
Thanks in advance.
<c:choose>
<c:when test="#{entry.dataEntry.posValue}">
<f:validator validatorId="hits.positiveNumberValidator"/>
</c:when>
<c:otherwise test="#{entry.dataEntry.negValue}">
<f:validator validatorId="hits.negativeNumberValidator"/>
</c:otherwise>
</c:choose>
Wrap in another validator and add them as attributes.
<f:validator validatorId="hits.numberValidator"/>
<f:attribute name="posValue" value="#{entry.dataEntry.posValue}" />
<f:attribute name="negValue" value="#{entry.dataEntry.negValue}" />
And then in the NumberValidator:
Boolean negValue = component.getAttributes().get("negValue");
if (posValue != null && posValue) {
new PositiveNumberValidator().validate(context, component, value);
}
Boolean posValue = component.getAttributes().get("posValue");
if (negValue != null && negValue) {
new NegativeNumberValidator().validate(context, component, value);
}
Note that this doesn't work when #{entry} is actually an iterated item like as declared in var attribute of h:dataTable or ui:repeat, because the f:attribute is tied to the JSF component, not to its output. Since the variable name #{entry} hints less or more that this is actually the case, here's how you could do it.
Wrap the collection in a DataModel:
private DataModel entries;
public Bean() {
entries = new ListDataModel(someDAO.list());
}
// ...
Use it in h:dataTable or ui:repeat as follows:
<h:dataTable value="#{bean.entries}" var="entry">
<h:column>
<h:inputText validator="#{bean.numberValidator}" />
</h:column>
</h:dataTable>
And implement the validator in the Bean as follows:
public void numberValidator(FacesContext context, UIComponent component, Object value) throws ValidatorException) {
Entry entry = (Entry) entries.getRowData();
if (entry.isPosValue()) {
new PositiveNumberValidator().validate(context, component, value);
}
if (entry.isNegValue()) {
new NegativeNumberValidator().validate(context, component, value);
}
}
(you may want to make those validators an instance variable of the bean instead (only if they are threadsafe))

Resources