Once again I need some help with my pizza-search-programm I have written with Java Server Faces.
The program: A user can search for pizzas by entering a form. A filtered search is possible as the user can decide whether he searches for a pizza name, a pizza id or other specified criteria. The program will generate a SQL query which returns pizza objects and stores it into a list of objects. A JSF page displays the list of pizza objects by iterating them through a ui:repeat tag. Pizza name, pizza ID, available sizes (displayed as radio buttons) and a list of possible quantities are displayed. For each displayed pizza object there is an "add-to-cart-button", to add the pizza to the shopping cart under the parameterized values of the chosen size and quantity.
The problem: Almost everything is displayed correctly. But when it comes to add a pizza to the cart, errors will occur. If the user searches for a specific pizza there will be no problems by submitting the pizza ID, the chosen size and the chosen quantity to the add-to-cart-method. But when the list comprises more than only one pizza object, only the last pizza can be added to the cart correctly by submitting the right values of pizza ID, chosen size and chosen quantity. If the user tries to put one of the upper pizzas to his cart, the previous submitted size and quantity chosen will be taken, provided that there was already an successfully executed "add-to-cart-action" before. If not 0 will be submitted, no matter what the user choses for size and quantity.
Example: User searches for "pizza salami". He adds 2 of them in size 40" to his cart. (chosenPizzaID: 1; chosenSize: 40; chosenQuantity 2). Everything is executed correctly. But after that the user searches for all pizzas. He wants to add the first pizza of the displayed list. This pizza is only available in size 30". He chose 3 of that pizza in size 30" and clicks "add-to-cart-button". The program takes the previous parameters for chosenSize and chosenQuantity (chosenSize: 40; chosenQuantity: 2).
The code snippet of PizzaSearch:
@ManagedBean
@SessionScoped
public class PizzaSearch {
// variables in order to submit the search criteria
private List<PizzaObject> results = new ArrayList<PizzaObject>();
// methods to generate the search
// each search result will fill/replace the list of pizza objects 'results'
// getter and setter methods
}
The code snippet of PizzaResult:
@ManagedBean
@SessionScoped
public class PizzaResult {
// injection of PizzaSearch
@ManagedProperty(value="#{pizzaSearch}")
private PizzaSearch pizzaSearch;
// variables
private List<PizzaObject> results;
private int _chosenSize;
private int _chosenQuantity;
@PostConstruct
public void initResults() {
this.results = pizzaSearch.getResults();
}
// method to add the pizza object to the cart
// a simple text output for testings
public void addToCart(int chosenPizzaID) {
System.out.println("chosen pizza ID: " + chosenPizzaID);
System.out.println("chosen size: " + _chosenSize);
System.out.println("chosen quantity: " + _chosenQuantity);
}
// getter and setter methods
}
The code snippet of the JSF output page
<ui:repeat var="result" value="#{pizzaResult.results}">
<h:form>
<ul>
<li><p>Name: #{result.pizza.name}</p></li>
<li><p>ID: #{result.pizza.pizzaID}</p></li>
<li>
<p>Toppings:</p>
<ui:repeat var="topping" value="#{result.toppingList}">
<p>#{topping.toppingName}</p>
</ui:repeat>
</li>
<li>
<p>Sizes:</p>
<h:selectOneRadio id="chosenSize" value="#{pizzaResult.chosenSize}">
<f:selectItems value="#{result.sizeList} var="size" itemLabel="#{size.diameter}" itemValue="#{size.sizeID}"/>
</h:selectOneRadio>
</li>
<li>
<p>Quantity:</p>
<h:selectOneListbox id="chosenQuantity" value="#{pizzaResult.chosenQuantity}" size="1">
<f:selectItem id="quantity1" itemLabel="1x" itemValue="1">
<f:selectItem id="quantity2" itemLabel="2x" itemValue="2">
</h:selectOneListbox>
</li>
<li>
<h:commandButton value="add to cart" action="#{pizzaResult.addToCart(result.pizza.pizzaID)}"/>
</li>
</ul>
</h:form>
</ui:repeat>
I have the feeling that the problem will be invoked by the variables chosenSize and chosenQuantity. But I don't have a clue of how to solve that problem. I hope you could help me somehow. Thanks!
I assume that you use Mojarra - reference implementation of JSF.
Original code does not work because of bug in Mojarra described in this answer. In short ui:repeat does not maintain state of its rows.
In order to get it working you have to either:
- switch to another implementation like Apache MyFaces where this code works (see my implementation)
- or move form outside the
ui:repeat
- as suggested in that answer:
use another iterating component (e.g.
<c:forEach>
,<h:dataTable>
,<t:dataList>
,<p:dataList>
, etc)
However, simply moving form outside the ui:repeat
like @user2314868 suggested does not work. It is because all fields are posted from the form. As a result each h:selectOneRadio
updates #{pizzaResult.chosenSize}
during Update Model Values phase. Therefore only last update will be visible in Invoke Application phase. Similarly for #{pizzaResult.chosenQuantity}
.
In order to get it working I propose to replace single value like chosenSize with array of values. Than we can take advantage index
property of status variable of ui:repeat
.
<h:form id="pizzasForm">
<ui:repeat var="result" value="#{pizzaResult.results}" varStatus="loop">
<ul>
<li><p>Name: #{result.pizza.name}</p></li>
<li><p>ID: #{result.pizza.pizzaID}</p></li>
<li>
<p>Sizes:</p> <h:selectOneRadio id="chosenSize"
value="#{pizzaResult.chosenSize[loop.index]}">
<f:selectItems value="#{result.sizeList}" var="size"
itemLabel="#{size.diameter}" itemValue="#{size.sizeID}" />
</h:selectOneRadio>
</li>
<li>
<p>Quantity:</p> <h:selectOneListbox id="chosenQuantity"
value="#{pizzaResult.chosenQuantity[loop.index]}" size="1">
<f:selectItem id="quantity1" itemLabel="1x" itemValue="1" />
<f:selectItem id="quantity2" itemLabel="2x" itemValue="2" />
</h:selectOneListbox>
</li>
<li><h:commandButton value="add to cart"
action="#{pizzaResult.addToCart(loop.index)}"/></li>
</ul>
</ui:repeat>
</h:form>
Changes in PizzaResult
:
@ManagedBean
@SessionScoped
public class PizzaResult {
// injection of PizzaSearch
@ManagedProperty(value = "#{pizzaSearch}")
private PizzaSearch pizzaSearch;
// variables
private List<PizzaObject> results;
private int[] _chosenSize;
private int[] _chosenQuantity;
@PostConstruct
public void initResults() {
this.setResults(getPizzaSearch().getResults());
int size = this.getResults().size();
this._chosenSize = new int[size];
this._chosenQuantity = new int[size];
}
// method to add the pizza object to the cart
// a simple text output for testings
public void addToCart(int index) {
System.out.println("chosen pizza ID: " + results.get(index).getPizza().getPizzaID());
System.out.println("chosen size: " + getChosenSize()[index]);
System.out.println("chosen quantity: " + getChosenQuantity()[index]);
}
...
Full working example can be found here
Step to do:
Step 1:
Move the h:form outside the ui:repeat (In JSF)
Step 2:
Give some id to the form (In JSF)
Step 3:
update the form once add method exucuted
<h:commandButton value="add to cart" action="#{pizzaResult.addToCart(result.pizza.pizzaID)}" update="give form id name here"/>
Step 4:
In addToCart() method, reset chosenPizzaID, _chosenSize and _chosenQuantity to 0
i.e
chosenPizzaID=0
_chosenSize=0
_chosenQuantity=0
来源:https://stackoverflow.com/questions/25180621/jsf-uirepeat-issue-with-adding-an-object-to-cart