I'm working on a project that requires me to display and be able to select and store tags to the product. Tags are provided in tree-like structure. I can't assume maximum depth of a tags tree.
I wanted to display tags split by levels, using c:forEach - p:selectManyCheckbox - f:selectItems, and handling selections using p:ajax components.
I use following types to store possible values and selections in Tree object:
HashMap<Long, ArrayList<Tag>> tree;
HashMap<Long, Object[]> selected;
Hashmap keys are equal to "tag level".
In order to display values I use following code for testing:
<p:panelGrid id="tagDisplay" columns="2">
<c:forEach begin="1" end="5" var="idx">
<p:outputLabel value="#{idx}"></p:outputLabel>
<p:selectManyCheckbox value="#{product.tags.selected[1]}">
<f:selectItems value="#{product.tags.tree[1]}" var="tag" itemLabel="#{tag.name}" itemValue="#{tag.id}" />
<p:ajax listener="#{product.selectorListener}" update="tagDisplay" />
</p:selectManyCheckbox>
</c:forEach>
</p:panelGrid>
Code seemed to work fine, though displayed five times.
Now I'm stuck trying to dynamically bind Hashmaps to selectors. As I replaced "1" with "idx", I got no results.
I tried to use ui-repeat with a dummy table, but then I lost panelgrid structure.
Any help will be appreciated!
My environment - Websphere 8.5, JSF 2.2, Primefaces 5.2
The <c:forEach begin end> is only for static iteration, not for dynamic iteration.
You'd better iterate over #{product.tags.tree} itself in <c:forEach items>. Each iteration over a Map will give Map.Entry back which in turn has getKey() and getValue() methods.
<p:panelGrid ...>
<c:forEach items="#{product.tags.tree}" var="entry" varStatus="loop">
<p:outputLabel value="#{loop.index}"></p:outputLabel>
<p:selectManyCheckbox value="#{product.tags.selected[entry.key]}">
<f:selectItems value="#{entry.value}" ... />
...
</p:selectManyCheckbox>
</c:forEach>
</p:panelGrid>
That said, must it really be a HashMap? Don't you rather want a fixed ordered LinkedHashMap?
Related
Basically, I have an input text box that I want to submit for each item in the array. The code below is cut down to the relevant portions
<c:forEach items="${mybean.mats}" var="mat">
<p:dataTable var="datarow" value="#{mybean.getDatarows(mat.itemId)}" rowIndexVar="row">
<p:column>
<p:inputText value="#{bean.amt}" />
</p:column>
</p:dataTable>
</p:panel>
<p:commandButton value="Confirm" action="#{mybean.runSubmit}" process="#this" />
</c:forEach>
From what I know, each individual item needs to have its own variable name to save the data in each input text box. I know the method I'm currently using is wrong, so is there another way to save all the data? Perhaps in an array or something?
First, mixing the JSTL c:foreach with a datatable is not recommended. Use ui:repeat instead.
Second, your p:dataTable value has to reference a collection that exists during the lifetime of the backing bean. It looks to me like the call to #{mybean.getDatarows(mat.itemId)} generates a list of items dynamically. That's going to be a problem since your backing bean will need to call the getDatarows when the values are re-applied to the bean on your ajax call runSubmit to save the values. In this case, you will need to save the dynamically created list to the backing bean so that the same collection will match up exactly to the collection used to produce the html.
For example, suppose your backing bean contains the property List<List<Mat>> mats, where the Mat class contains a single property 'dataId'. Then, your EL could be:
<h:form id="input-form">
<ui:repeat id="mats-repeat" value="#{mats}" var="mat">
<p:dataTable id="mat-table" value="#{mat}" var="dataObj">
<p:column>
<p:inputText id="dataId" value="#{dataObj.dataId}" />
</p:column>
</p:dataTable>
</ui:repeat>
</h:form>
Since the form, repeat, and dataTable components are naming containers, they ensure that any child components are named uniquely. In this case, the first input mat element would be rendered as:
<input id="input-form:mats-repeat:0:mat-table:0:dataId" ...
I'm struggling to implement a fairly trivial functionality with JSF which involves dynamically displaying the content of a nested map on a page and editing capabilities for its values. But it has turned out that the MappedValueExpression$Entry that you get when iterating over a map with c:forEach is not writable!
<c:forEach items='#{inflectionBean.word.inflectionalForms}' var="number" >
<p:fieldset legend="#{number.key}">
<c:forEach items="#{number.value}" var="case" >
<p:panel header="#{case.key}">
<h:inputText value="#{case.value}" />
</p:panel>
</c:forEach>
</p:fieldset>
</c:forEach>
When I am trying to submit the above form I'm getting:
javax.el.PropertyNotWritableException: /inflection.xhtml #39,56 value="#{case.value}": The class 'com.sun.faces.facelets.tag.jstl.core.MappedValueExpression$Entry' does not have a writable property 'value'.
I wonder if there are reasonable workarounds or if I am approaching the problem in a wrong way. Thanks!
Basically, what your code is attempting is invoking Map.Entry#setValue(value). This is indeed not possible in EL. Instead, you should be referencing the map value directly on the map itself by key, so that EL can do Map#put(key, value).
<c:forEach items="#{number.value}" var="case">
...
<h:inputText value="#{number.value[case.key]}" />
I have a problem with submitting composite components.
Most of my composite components contain both the input components and the "submit" button.
When I tried to put the button still in the same h:form but not in the same composite component, the submitted value seemed to be "lost" somewhere. And, for instance, my validators got called on original values.
Example :
<composite:interface>
<composite:attribute name="titreContext" required="true"/>
</composite:interface>
<composite:implementation>
<p:outputPanel id="selectionTitreDetailsPanel" styleClass="selectionTitreDetails">
<p:outputPanel id="selectionTitreDetailsPanelInner" rendered="#{not empty cc.attrs.titreContext.selected}">
<p:panelGrid columns="2" id="panelId">
<h:outputText id="idLabel" value="Id :"/>
<h:outputText id="id" value="#{cc.attrs.titreContext.selected.titeluid}"/>
<p:tooltip for="id" value="Identifiant unique"/>
</p:panelGrid>
<p:panelGrid columns="2" id="titelePanel">
<p:outputLabel for="selectTitele" value="Titre :"/>
<p:selectOneMenu id="selectTitele" value="#{cc.attrs.titreContext.selected.titele}" effect="fold" styleClass="fullWidth">
<f:selectItems value="#{constants.getTitelesForTypman(cc.attrs.titreContext.selected.titele.typman)}" var="titele" itemLabel="#{titele.titelelil}" itemValue="#{titele}" styleClass="fullWidth"/>
<p:column styleClass="fullWidth">#{titele.titelelil}</p:column>
</p:selectOneMenu>
</p:panelGrid>
[...]
<p:commandButton id="confirmerModifications" icon="small_edit" type="submit" value="Confirmer les modifications"
action="#{elutersEditionContext.confirmeModifsSelection}" process="mandatsTerritorial"
update="mandatsTerritorial #{cc.attrs.notifUpdates}"/>
</composite:implementation>
works.
But putting the p:commandButton out of the composite :
<h:form>
<mylib:mycomponent /*parameters *//>
<p:commandButton /*parameters*/ />
</h:form>
does not work. When I debug my validators, I can see that the modified values where not even submitted. Neither getLocalValue, getSubmittedValue nor getValue is changed.
Is there a syntax in composite component declaration to use to correct this situation ?
By the way : when I was writing my components as composite components rather than custom components, retrieving #{asen} in the backing bean just worked.
Thanks in advance.
I am using :
PrimeFaces 3.4.1
CODI 1.0.5
OpenWebBeans 1.1.6
MyFaces 2.1.9
Tomcat 7.0.32
(update) This very strange problem was caused by h:form nesting.
Very strange because h:form nesting did not perturbate the processing of the first level of composite components, but caused this strange "input lost" in nested composite.
Nesting looked like this :
<h:form>
...
<p:tabView ...>
<p:tab>
<h:form>
<my:composite ....>
</h:form>
</p:tabView>
</h:form>
You're using a relative client ID in the process attribute of the <p:commandButton>:
<p:commandButton ... process="mandatsTerritorial" />
A relative client ID is relative to the parent NamingContainer component. It will be searched as direct child of the NamingContainer component. If the child is by itself a NamingContainer, then its children would not be searched.
Composite components are by itself in fact also NamingContainer components. If the button is placed in the composite, then this will be searched as direct child of the <cc:implementation>. In your particular case, only the component with id="mandatsTerritorial" will be processed on form submit, including all of its children (note that this component is nowhere visible in the code posted so far, but I'd imagine that you omitted it for brevity).
If the button is placed in <h:form>, then this will be searched as direct child of the <h:form>. However as this is apparently been placed inside the composite (which is, as said, another NamingContainer component), it wouldn't be found and hence basically nothing would be processed. You'd need to fix the process to point to the right client ID. E.g.
<h:form>
<mylib:mycomponent id="mycomponent" />
<p:commandButton ... process="#this mycomponent:mandatsTerritorial" />
</h:form>
This way it will process itself (mandatory to invoke the action!) and the component with id="mandatsTerritorial" inside the <cc:implementation> of the composite with id="mycomponent".
As a completely different alternative, which would work just fine in this particular construct, is to remove the process attribute altogether. It defaults to #form already which will thus process the entire form.
Update as per your question update: nesting forms is invalid in HTML. Using the JSF <h:form> representation doesn't change that; you'd still end up with nested forms in HTML. The browser behaviour is unspecified as to which data would be submitted to the server. Make sure that you don't nest <h:form> in JSF as well.
i am using facelets jsf 2.0 with primefaces 3.0.M2 component library.
i am trying to achieve dynamic numbers of rows including iput fields that are filled when a datatable selection occurs.
whenever a selection is made the dynamic rows generated correctly with input fields but after the first selection for following selections dynamic row count changes correctly but the input fields does not update and keeps showing inputs from the first selection.
here is how i iterate list in facelet;
<ui:repeat value="#{goalEntranceBean.selectedCard.parameterList}" var="prmBean" >
<li><h:outputText value="#{prmBean.lookUp.value}"/></li>
<li>
<h:outputText value="Weight:"/>
<p:inputText id="wx" required="true" value="#{prmBean.weight}">
</p:inputText>
<h:outputText value="Percent:"/>
<p:inputText required="true" value="#{prmBean.percent}">
</p:inputText>
</li>
</ui:repeat>
my bean where i get the list of cards and set the selectedCard with rowSelect event in datatable.
#ManagedBean(name = "goalEntranceBean")
#ViewScoped
public class GoalEntranceAction implements Serializable {
private List<ScoreCard> personalCards = new ArrayList<ScoreCard>();
private ScoreCard selectedCard = new ScoreCard();
......
}
when i checked in debug mode i can see the true list but in screen the elements does not change.
This is a common problem (gets asked every couple of days). To make long story short, inputs inside ui:repeat do not work, period.
It is a problem with JSF, a long standing, famous one. Maybe it will be fixed. Maybe not, it seems that no one really cares (I mean - an input? in a... ui:repeat? such crazy scenario!).
A quick-fix is to use a h:dataTable, possibly ungodly abused with css to make it look like a list. A long-fix is to use some iterator from a different library. Primefaces has an element that should work that renders an unordered list.
thanks for your replies. Sorry for forget sharing the solution.
As i mentioned above i have primefaces datatable.
On row selection event i render datatable and want to update the cells of that datatable.
USING p:inputtext easily solved my problem. Now i can change the data on screen and i can see the values after update operation on screen. I don't understand the reason but it works.
<p:dataTable var="orgPrmBean"
value="#{scoreCardOperationsBean.selectedCard.orgParameterList}"
emptyMessage="#{labels.norecord}"
rowKey="#{orgPrmBean.id}"
>
<p:columnGroup type="header">
<p:row>
<p:column headerText="Parameters" colspan="3" style="text-align:left;width:480;"/>
</p:row>
</p:columnGroup>
<p:column style="text-align:left;width:200px;">
<h:outputText value="#{orgPrmBean.info}"/>
</p:column>
<p:column style="text-align:left;width:180px;">
<p:inputText value="#{orgPrmBean.weight}"
rendered="#{scoreCardOperationsBean.selectedCard.goalEdit}">
<f:convertNumber maxFractionDigits="0"/>
</p:inputText>
</p:column>
</p:dataTable>
It IS possible to make it work, but the solution is to bind the inputs to a backing bean, and update the values of the controls in the backing bean via listeners (using the new value received in the argument). Obviously this isn't a good solution if you have a complex form, as you need to add a listener/control in the backing bean for each control in the page, but it's practical if you just have one or two inputs.
While using <c:forEach> the items values is not substituted properly. If i use <a4j:repeat> or <ui:repeat> instead of <c:forEach> inside a <rich:dataTable>, radio button is not rendering properly. I also found reason for this in http://community.jboss.org/wiki/Cantusea4jrepeattoiteratethemenuItemstabsetc
How do I resolve this issue?
<f:selectItems> is working inside but i want to send a choice type to server
<rich:dataTable var="answer" value="#{answers}">
<rich:column>
<f:selectOneRadio value="#{response.value}">
<c:forEach items="#{answer.choices}" var="choice">
<f:selectItem itemLabel="#{choice.value}" itemValue="#{choice.type}"/>
</c:forEach>
</f:selectOneRadio>
</rich:column>
</rich:dataTable>
If you're already on JSF 2.x, then you can just use the following construct:
<f:selectItems value="#{answer.choices}" var="choice" itemValue="#{choice.type}" itemLabel="#{choice.value}" />
If you're still on JSF 1.x, then best is to use f:selectItems in combination with the following logic in the constructor of answer bean to prepopulate it:
this.selectItems = new ArrayList<SelectItem>();
for (Choice choice : this.choices) {
selectItems.add(new SelectItem(choice.getType(), choice.getValue()));
}
so that you can end up with
<f:selectItems value="#{answer.selectItems}" />