0

I'm new to JSF2. Using JPA, I have a bidirectional OneToMany relationship between, say, dishes and ingredients.

The application has a view allowing to add a dish with at least one ingredient where the user can add additional ingredients via Javascript, which is basically just copying the HTML element representing an ingredient.

A screenshot to make it more clear what I mean - initially there is only one ingredient but the user can add/remove one ingredient at a time by clicking on the respective button:

enter image description here

The initial ingredient I just create upfront in @PostConstruct in the backing bean:

@Named
@Produces
private Dish addDish;

@PostConstruct
public void init() {
    this.addDish = new Dish();
    final Ingredient ingredient = new Ingredient();
    ingredient.setDish(this.addDish);
    this.addDish.getIngredients().add(ingredient);
}

public String add() {
    databaseService.persist(addDish);
    return OUTCOME;
}

Submitting the dish with the initial ingredient works fine, it is properly persisted to the database.

But now I am wondering how I can know how many ingredients are actually in the form submitted by the user and where would I then create the appropriate number of Ingredient instances and add them to the Dish instance?

Torsten Römer
  • 3,834
  • 4
  • 40
  • 53
  • 1
    you need map your ingridients to List ingridients and then use for each and set dish on each iteration – user3127896 Jun 09 '14 at 04:19
  • Thanks, makes sense to me. But still I wouldn't know how to populate the List when there are more ingredients in the view than in the list. I tried with a producer method and it actually gets called for each ingredient in the view but I don't know how to get the index. Similar question: http://stackoverflow.com/questions/18628169/creating-input-fields-dynamically-in-jsf-2-0-and-linking-it-to-a-backing-bean – Torsten Römer Jun 09 '14 at 12:44
  • Can't totally understand your question unless you provide the view you're interested in. I don't know if you want to use a single form to add only an ingredient and later on reset it or you want the user to be able to add multiple ingredients at one time. Please, be more specific in that. – Aritz Jun 10 '14 at 06:48
  • I added a screenshot to make it more clear - the user can add/remove one ingredient at a time by clicking on the respective button. – Torsten Römer Jun 10 '14 at 11:12
  • 2
    I don't want to be a jerk, but dishes to ingredients is a clear ManyToMany example, unless you want to claim that there can be only one rice dish in the world ;) – Gimby Jun 10 '14 at 13:12
  • Fully agree, but since this is just a fun/learning app I decided to do like that. Could be a fun next step to implement ManyToMany with a join table and some autocomplete textfield for looking up existing ingredients :-) – Torsten Römer Jun 10 '14 at 22:48

2 Answers2

1

In order to provide a quickoff example of what you want, I'll provide you some base code. Supposing you want to create the dish itself and all the related ingredients, you should declare a one-to-many relation in JPA and set the cascade mode to all. That way, you'll be able to save each of the related ingredients when you save the dish itself.

Having said that, let's go with the JSF related code, which allows you to add-remove an ingredient and save the dish:

newDish.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<h:head />
<h:body>
    <h:messages />
    <h:form id="form" prependId="true">
        <h:panelGrid columns="1">
            Preparation:
            <h:inputTextarea value="#{newDishBean.newDish.preparation}"
                required="true" />
        </h:panelGrid>
        <h:dataTable value="#{newDishBean.newDish.ingredients}"
            var="ingredient" id="ingredients">
            <h:column>
                <h:inputText value="#{ingredient.amount}" required="true" />
            </h:column>
            <h:column>
                <h:inputText value="#{ingredient.unit}" required="true" />
            </h:column>
            <h:column>
                <h:inputText value="#{ingredient.name}" required="true" />
            </h:column>
        </h:dataTable>
        <h:commandButton value="Add Ingredient">
            <!-- Sends the whole form (execute="@form") into the POST request in order to keep the model updated -->
            <f:ajax listener="#{newDishBean.addIngredient}" render="ingredients"
                execute="@form" />
        </h:commandButton>
        <h:commandButton value="Remove Ingredient">
            <!-- Here we don't need to send extra info. Just the button component (@this) is executed -->
            <f:ajax listener="#{newDishBean.removeIngredient}"
                render="ingredients" />
        </h:commandButton>
        <!-- Non-ajax request. The whole form is sent to the server -->
        <h:commandButton value="Create dish"
            action="#{newDishBean.createDish}" />
    </h:form>
</h:body>
</html>

NewDishBean.java:

@ManagedBean
@ViewScoped
public class NewDishBean {

    public class Dish {

        private String preparation;

        private List<Ingredient> ingredients = new ArrayList<Ingredient>();

        public List<Ingredient> getIngredients() {
            return ingredients;
        }

        public String getPreparation() {
            return preparation;
        }

        public void setIngredients(List<Ingredient> ingredients) {
            this.ingredients = ingredients;
        }

        public void setPreparation(String preparation) {
            this.preparation = preparation;
        }

        @Override
        public String toString() {
            return "Dish [preparation=" + preparation + ", ingredients="
                    + ingredients + "]";
        }

    }

    public class Ingredient {

        private Dish dish;

        private int amount;

        private String unit;

        private String name;

        public int getAmount() {
            return amount;
        }

        public Dish getDish() {
            return dish;
        }

        public String getName() {
            return name;
        }

        public String getUnit() {
            return unit;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        public void setDish(Dish dish) {
            this.dish = dish;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void setUnit(String unit) {
            this.unit = unit;
        }

        @Override
        public String toString() {
            return "Ingredient [amount=" + amount + ", unit=" + unit
                    + ", name=" + name + "]";
        }
    }

    private Dish newDish = new Dish();

    public void addIngredient() {
        Ingredient i = new Ingredient();
        i.setDish(newDish);
        newDish.ingredients.add(i);
    }

    public void createDish() {
        // Do your JPA stuff
        System.out.println(newDish + " created!");
    }

    public Dish getNewDish() {
        return newDish;
    }

    public void removeIngredient() {
        if (!newDish.ingredients.isEmpty()) {
            newDish.ingredients.remove(newDish.ingredients.size() - 1);
        }
    }

}

Basically what you do is to create a new dish when the bean itself is created. Each time you click on "Add ingredient", an ingredient is added to the new dish and displayed in the table.

That's the JSF standard way to achieve what you want. Being a stateful framework, JSF tends to update it's state (which can be kept at server or client side) for every change you make in the model. Of course you could add a row to the table for a new ingredient using Javascript, but that would involve having to update this state by yourself.

See also:

Community
  • 1
  • 1
Aritz
  • 30,971
  • 16
  • 136
  • 217
  • Thanks a lot for the effort you made to provide this great answer! As I stated in my own answer I have implemented the solution mentioned there which is very similar to yours, but your answer is so nice complete plus the reasoning about JSF's statefulness so that's the accepted answer :-) – Torsten Römer Jun 10 '14 at 12:58
  • Just want to mention here that instead of submitting the whole form with `execute="@form"`, only the relevant components can be executed with `execute="@this ingredients"` which can reduce the amount of data sent to the server and back to the client. – Torsten Römer Jun 19 '14 at 14:08
0

I eventually ditched the approach in my question and went for the solution suggested by the accepted answer to a similar question.

It feels kinda hacky to update the markup generated by JSF and the solution using AJAX is very straightforward and seems to be much more the JSF way.

Only pity is that a request to the server is made for something that could be done entirely client-side.

Note: If you like me don't want AJAX to render the whole form but just a <div> in that elements are added dynamically, wrap that part in a <h:panelGroup id="render-by-ajax" layout="block">.

Community
  • 1
  • 1
Torsten Römer
  • 3,834
  • 4
  • 40
  • 53