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: