1

I have a <h:dataTable> displaying a product catalog in Proudcts.xhtml:

<h:form name="ViewProductsManagedBean">
    <h:dataTable var="product" value="#{ViewProductsManagedBean.productsList}">
        <h:column>
            <h:outputText value="#{product.productid}" />
        </h:column>
        <h:column>
            <h:outputText value="#{product.itemcode}" />
        </h:column>
        <h:column>
            <h:outputText value="#{product.itemdescription}" />
        </h:column>
        <h:column>
            <h:outputText value="#{product.unitprice}" />
        </h:column>
        <h:column>
            <h:selectOneMenu value="#{ViewProductsManagedBean.quantityPurchased}" required="true">
                <f:selectItem itemValue="1" itemLabel="1" />
                <f:selectItem itemValue="2" itemLabel="2" />
                <f:selectItem itemValue="3" itemLabel="3" />
                <f:selectItem itemValue="4" itemLabel="4" />
                <f:selectItem itemValue="5" itemLabel="5"/>
            </h:selectOneMenu>
        </h:column>
        <h:column>
            <h:commandButton action="#{ViewProductsManagedBean.addItemToCart(product)}" value="Add to basket" />
        </h:column>
    </h:dataTable>
</h:form>

With this managed bean:

@ManagedBean(name="ViewProductsManagedBean")
@SessionScoped
public class ViewProductsManagedBean {

    private double unitprice;
    private String itemdescription;
    private String itemcode;
    private int quantityPurchased;
    private String result;

    @EJB
    ProductLocal productFacadeBean;

    @EJB
    CartFacade cartFunctions;

    private List<ProductEntity> productsList = new ArrayList<>();
    private List<StockEntity> stocksList = new ArrayList<>();
    private ProductEntity product;

    @PostConstruct
    private void init(){
        setProductsList();
        product = new ProductEntity();
    }

    public void addItemToCart(ProductEntity product) {
        int quantity=this.quantityPurchased;
        cartFunctions.addItemToCart(product, quantity);
        System.out.println(product.toString());         
    }

    // getters+setters
}

The problem is with the <h:selectOneMenu> to select the quantity. No matter what value is selected, the managed bean always receives a value of 1 for quantity, EXCEPT when the quantity is changed in the last item of the product catalog, in which case the quantity for ALL the items change to the value selected for the last item in the catalog, and the correct quantity is sent to the managed bean.

How is this caused and how can I solve it?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555

3 Answers3

3

Here,

<h:selectOneMenu value="#{ViewProductsManagedBean.quantityPurchased}">

You're basically binding the value of all input fields to one and same bean property. So, when the form gets submitted, every iteration of the table will override the bean property everytime with the submitted value of the current iteration round until you end up getting the value of the last row. If you have placed a debug breakpoint on the setter method, you should have noticed that.

This is definitely not right. You need to associate the input value with the currently iterated object. The simplest but naive way would be to directly associate it with the object:

<h:selectOneMenu value="#{product.quantityPurchased}">

This only tight-couples in your particular case the "quantity" model with the "product" model. It's functionally reasonable to keep them separated. A more proper solution is then to map the input value with currently iterated object as key (provided that the object has a proper equals() and hashCode() implementation, obviously):

<h:selectOneMenu value="#{ViewProductsManagedBean.quantitiesPurchased[product]}" converter="javax.faces.Integer">

With:

private Map<ProductEntity, Integer> quantitiesPurchased = new HashMap<>();

Regardless of the approach, in the action method, just iterate over it to collect them all.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thank you so much. I am still very much a novice when it comes to Java EE and this is my MSc project. Your answers have always helped me to understand these concepts better. So, referring to your "better" method, I'd have to override equals() and workout a new Hashcode for each product? Please can you elaborate a bit more? Also, is it "bad" to use the first - naive- approach? – Lalin Pethiyagoda Jan 23 '15 at 09:28
  • Just let your IDE generate those methods based on the `id` field. Or follow the basic example in http://stackoverflow.com/questions/17343032/implement-converters-for-entities-with-java-generics/17343582#17343582 The first approach is bad because this way the quantity is tight coupled to the product itself and thus basically shared among all users of this product. The quantity should be coupled to the user (the current view/session) instead of the product. The product should then only be used as key (of the map). – BalusC Jan 23 '15 at 09:49
  • Thank you. The first approach, would not work for me as I do not have quantityPurchased as part of ProductEntity. I am using the product details and quantityPurchased (which is a field in the managed bean) to eventually persist the product and quantityPurchased into the OrderItems table. So with the second approach, all the products and their associated quantitiesPurchased are stored in the HashMap? and the HashMap is iterated to locate a specific product and the quantity the user wishes to purchase? Am I right here? – Lalin Pethiyagoda Jan 23 '15 at 10:02
  • That's correct. Note that I updated the answer to convert and store the quantity as `Integer` instead of `String`. – BalusC Jan 23 '15 at 10:18
1

Each line of your h:datatable references the same variable ViewProductsManagedBean.quantityPurchased. If the form is submitted, the value of ViewProductsManagedBean.quantityPurchased will be set for each line again and again. That's why the value of the last row defines the final state of quantityPurchased and this is the value you get in your method.

Matt Handy
  • 29,855
  • 2
  • 89
  • 112
  • i'm having the same issue...i used map, still didn't solve the issue. about the ajax solution, can you please post an example? – snabel Sep 17 '15 at 10:59
-1

Agree with Matt. Change your behaviour to be Ajax bt adding and set to execute the @this and quantity and render the cart. That way it will only process that row, not the whole table.

Also I would change the action yo actionlistener.

  • I dont get why my answer is down voted, shouldn't converting to AJAX led behaviour work in this scenario. Sorry I am newbie to StackOverflow answering did I do something wrong? I wanted to comment on Matt but it didnt allow me. – yohanfernando Jan 23 '15 at 12:21
  • I have no idea, and not sure how to find, perhaps @BalusC? – yohanfernando Jan 23 '15 at 13:28
  • It's more likely, that answers should be answers and not comment as answer. A answer should stand for it self. That explains the downvote, in my opinion. Also you are missing some code tags. – alexander Aug 06 '15 at 13:39