1

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!

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
user3548416
  • 117
  • 1
  • 1
  • 11
  • Your PizzaResult bean is Session scoped or View scoped? – yavuzkavus Aug 07 '14 at 11:15
  • I tried both ViewScoped and SessionScoped but none of them solved the problem. @yavuzkavus – user3548416 Aug 07 '14 at 11:45
  • 1
    can you try again after moving `h:form` outside of `ui:repeat` at your page? – tt_emrah Aug 07 '14 at 11:54
  • If I place `h:form` outside of `ui:repeat` then there won't be any search results displayed. This isn't even my intention. I want an "add-to-cart-button" for each displayed pizza object. I really think that the reason for this problem is occurred by the variables chosenSize and chosenQuantity. @tt_emrah – user3548416 Aug 07 '14 at 12:19

2 Answers2

0

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
user2314868
  • 139
  • 1
  • 5
  • 12
  • Unfortunately after your steps the same error occurs. I've placed the h:form outside of ui:repeat, I gave some ID to it and because I'm using NetBeans I used the equivalent version of update with ajax ` `. Also I reset the values of chosenSize and chosenQuantity in my addToCard(), but nothing solved this problem. – user3548416 Aug 07 '14 at 12:48
0

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:

  1. switch to another implementation like Apache MyFaces where this code works (see my implementation)
  2. or move form outside the ui:repeat
  3. 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

Community
  • 1
  • 1
Dawid Pytel
  • 2,750
  • 1
  • 23
  • 30
  • 1
    correct. either ajax&execute must be used, or the variables must be stored in arrays. – tt_emrah Aug 08 '14 at 06:05
  • Thank you for your great detail answer! It seems logic to me, but I still have one problem when I want to execute my program: after I click on the "add-to-cart-button" my browser tells me there is an error: "/pizzasearch.xhtml @112,134 value="#{pizzaResult.chosenQuantity[loop.index]}": null". Do you know how to handle it? – user3548416 Aug 08 '14 at 09:24
  • Have you also made changes in `PizzaResult`? I added the file to the answer. – Dawid Pytel Aug 08 '14 at 09:56
  • I've figured out that in the method initResults(), which is provided with PostConstruct, the value results.size is null. Therefore chosenSize and chosenQuantity are also 0. How can I get past this error? – user3548416 Aug 08 '14 at 10:03
  • How the size can be `null` when it is `int` type? – Dawid Pytel Aug 08 '14 at 10:12
  • Sorry, I meant 0. But that's the reason why the program throws the error. Do you have any plan of how to fix it? – user3548416 Aug 08 '14 at 10:30
  • Well, I have no idea but I'm pretty sure it has nothing to do with JSF, rather with your code. I assumed that `results` field is initialized only in `initResults` method. If it is initialized also in other places please put array initialization also there. Please also check whether `getPizzaSearch().getResults()` returns correct list. – Dawid Pytel Aug 08 '14 at 10:52
  • No, I have initialized the results list in my PizzaSearchBean, after the user searches for pizzas. With the injection I want to pass the list to PizzaResultsBean, in the initResults()-method. But somehow the size of the list there is 0. Maybe because I'm initializing the list in a PostConstruct? I don't know.. – user3548416 Aug 08 '14 at 11:25
  • Is the `results` list also empty? Or maybe `setResults` does nothing? Or `getResults()` just returns empty list. Frankly nothing I proposed changes how `results` is initialized so it should work as previously. Try adding `System.out.println(getResults())` to the end of `initResults`. – Dawid Pytel Aug 08 '14 at 12:12
  • Thank you for your very kind help, Dawid! I simply decided to replace the ui:repeat tag to c:forEach . Everything works proper now. Thanks for the time you invested in answering this detail response. – user3548416 Aug 08 '14 at 14:56