From the controller, I have returned an object containing a list of objects. I want to display these list of objects in the dropdown with no pre-selection(or default value i.e. "Select Dish"), but dropdown is shown with pre-selected last value in the list.
Controller:
#GetMapping(path = "/createOrder")
public ModelAndView displayOrder(OrderFormDetails order) {
ModelAndView mav = new ModelAndView();
mav.addObject("order", orderService.displayOrder());
mav.setViewName("createOrder");
return mav;
}
Model:
public class OrderFormDetails {
#NotEmpty(message = "*Please provide your name")
private String name;
#NotEmpty(message = "*Please provide your address")
private String address;
private List < Dish > dishes;
View:
<select class="form-control" th:field="*{dishes}" id="dropOperator">
<option value="" selected="selected">Sélect dish</option>
<option th:each="dish, itemStat : *{dishes}" th:value="*{dishes[__${itemStat.index}__].id}" th:text="*{dishes[__${itemStat.index}__].title}">
</option>
</select>
I have tried multiple tricks, but none of them worked. Thanks...
Usually you don't want to mix possible options and selected option together within one field (as you apparently did). All you need to do is to decouple those things. Possible steps may be helpful:
Since OrderFormDetails acts like your form backing bean, it should contain a placeholder for selected value (Dish.id) instead of List with possible dishes. Change your OrderFormDetails to the following:
public class OrderFormDetails {
#NotEmpty(message = "*Please provide your name")
private String name;
#NotEmpty(message = "*Please provide your address")
private String address;
private T selectedDish;
// getters, setters, rest of the class omitted
...where T is the type assigned to Dish.id field.
Possible (selectable) dishes should be provided separately as a model attribute. Add following line to your displayOrder method:
mav.addObject("dishes", getDishes());
...where getDishes() returns List<Dish> containing all dishes being an option for an user.
Adjust your view to work with updated approach:
<select class="form-control" th:field="*{selectedDish}" id="dropOperator">
<option value="" selected="selected">Sélect dish</option>
<option th:each="dish : ${dishes}" th:value="${dish.id}" th:text="${dish.title}">
</option>
</select>
That's all. Such an approach is also shown in documentation - th:field on <select> level refers to form backing bean's field, whereas <option> elements are created out of separate collection provided as model attribute.
Related
I hope you can help me with a problem I have.
I need to create a form where I can create a CourseForm object that has a list of courses with id and name.
From the create button I pass a list of available courses and the object where I want the courses selected in the form to be stored.
In turn, I have a multi select in the form that uses the CourseForm object and its courses attribute, which is a list of courses.
The list is printed correctly in the view but when retrieving the selected courses it returns a null value
Create Controller
model.addAttribute("courseForm", courseForm);
model.addAttribute("coursesList", coursesList);
Form
<form th:action="#{/courses/save}" th:object="${courseForm}" method="POST">
<select class="selectpicker" th:field="*{courses}" multiple="multiple">
<option th:each="course: ${coursesList}"
th:value="${course.courseId}" th:text="${course.courseName}">
</select>
CourseForm entity
#Data
public class CourseForm {
private List<CourseIndividual> courses;
}
CourseIndividual entity
#Data
public class CourseIndividual {
private Integer courseId;
private String courseName;
}
Save Controller
#PostMapping("/courses/save")
public ModelAndView saveAnnouncementCourses(
Model model,
#RequestParam("save_option") String saveOption,
#ModelAttribute("courseForm") CourseForm courseForm
I have 2 Entity classes the "Menu" which only has one field called "name" and second Entity - "Ingredients" which has 2 fields - "ingredientName" and "ingredientDescription". Database Structure
I'm creating a simple CRUD web-app , but the update method instead of updating the Entity , it inserts new values in the DB. I checked and when user clicks on the update on specified menu, the first entity's id and its ingredients id's as well are predifined. Im new to spring boot and thymeleaf and Don't really know how to work with JPA when you have more than 1 entity.
Menu entity :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#Column(name = "name")
private String name;
// Mapping To second table
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "menu_ingredient",
joinColumns = #JoinColumn(name = "menu_id"),
inverseJoinColumns = #JoinColumn(name = "ingredient_id"))
private List<Ingredients> ingredient = new ArrayList<>();
//Getters/Setters/Constructors/ToString
Ingredients entity :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "ingredient")
private String ingredientName;
#Column(name = "description")
private String ingredientDescription;
//Getters/Setters/Constructors/ToString
Controller(Only the update methods) :
#GetMapping("/edit/{id}")
public String edit(#PathVariable(name = "id")String id, Model model){
Optional<Menu> menu = menuRepository.findById(id);
List<Ingredients> ingredients = menu.get().getIngredient();
for (Ingredients ing : ingredients){
System.out.println(ing);
}
model.addAttribute("ingredients", ingredients);
model.addAttribute("newMenu",menu);
return "edit-page";
}
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu){
menuRepository.save(menu);
return "redirect:/recipeList";
}
edit-page.html :
<form action = "#" th:action="#{/postEditMenu}" th:object="${newMenu}" method="post">
<p>Menu Name: <br><input type="text" th:field="*{name}"></p>
<div id="wrapper" th:each="ing: ${ingredients}">
<label for="ingredientName"></label>
<p>Ingredient Name: <br><input th:value="${ing.ingredientName}" id="ingredientName" type="text" name="ingName"></p>
<label for="ingredientDescription"></label>
<p>Ingredient Description:</p> <textarea id="ingredientDescription" type="text" th:text="${ing.ingredientDescription}" name="ingDesc"></textarea>
</div>
<br>
<input type="button" id="more_fields" onclick="add_fields();" value="Add More" />
<br>
<input type="submit" th:value="Submit">
</form>
FIX I actually figured it out with the help of below answers. Here's the code :
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu,
#RequestParam String ingName,
#RequestParam String ingDesc){
String[] ingNameSplit = ingName.split(",");
String[] ingDescSplit = ingDesc.split(",");
Menu menuToUpdate = menuRepository.getOne(menu.getId());
List<Ingredients> newIngredientList = menuToUpdate.getIngredient();
newIngredientList.clear();
for(int a = 0, b = 0; a<ingNameSplit.length; b++, a++){
newIngredientList.add(new Ingredients(ingNameSplit[a], ingDescSplit[b]));
}
menuToUpdate.setIngredient(newIngredientList);
menuRepository.save(menuToUpdate);
return "redirect:/recipeList";
}
So, First I added hidden "id" fields to each of the items required , like this :
<input type="text" th:field = "*{id}" hidden>
and
<input type="text" th:value = "${ing.id}" hidden>
Then, in the postEditMenu method, I added #RequestParam String ingName, and #RequestParam String ingDesc to get the input of new items from thymeleaf, then I split that String and add it to String[] array with String[] ingNameSplit = ingName.split(",") Because the input would be one big comma separated String and not array[] . Then I get the menu which user wants to update - Menu menuToUpdate = menuRepository.getOne(menu.getId()); The menu.getId() isn't null because I set hidden "id" fields in thymeleaf. Then I get the Ingredients of this menu - List<Ingredients> newIngredientList = menuToUpdate.getIngredient(); because the list would already be filled with existed ingredients I clear that list and add new ingredients which user will fill the form with -
for(int a = 0, b = 0; a<ingNameSplit.length; b++, a++){
newIngredientList.add(new Ingredients(ingNameSplit[a], ingDescSplit[b]));
}
after that I set this newIngredientsList and save the menu itself to the db -
menuToUpdate.setIngredient(newIngredientList);
menuRepository.save(menuToUpdate);
Thanks for all the help guys :)
At this point:
#PostMapping("/postEditMenu")
public String postEdit(#ModelAttribute("newMenu")Menu menu){
menuRepository.save(menu);
return "redirect:/recipeList";
}
You receive menu from edit-page.html and it has no id, that is why it always creates new records in database.
To edit the desired menu, you would need to have it's id before.
You can create endpoint for obtaining list of menus and display them with edit button next to each menu in html site. Then if user clicks edit button redirect him to your edit form, but this time you can pass menu's id.
First you need to fetch Menu entity from database by id using getOne and then you can update it.
Edit your code in postEdit method as follows:
Fetch Menu entity:
Menu menuToUpdate = menuRepository.getOne(menu.getId());
Update attributes:
menuToUpdate.setName(menu.getName());
Save entity:
menuRepository.save(menuToUpdate);
Add a hidden id field to hold the menu's id, and add hidden id fields for each ingredient.
I have a form to set values for bean. This form has a List, every address consist of street, city, zip. How to set value of street for example to street ?
Piece of code
//List
List<Address> addressList ; //with getter and setter
//Address POJO with getters and setters
private String city;
private String zipCode;
private String street;
//JSP
<form:form id="form" commandName="form" acceptCharset="UTF-8">
<c:forEach items="${form.addressList}" var="ad">
<input value="${ad.street}" id="addressList"name="addressList.street" type="text" />
</c:forEach>
</form:form>
$.ajax({
type : "POST",
url : url,
data : $('#form').serialize(),
contentType : "application/x-www-form-urlencoded;charset=UTF-8",
I'm sending form by ajax to controller.
With List of Strings I'm getting in controller expected results, but with this POJO I'm getting null value.
How to solve this problem ?
You addressList is not a part of Form and you are reading it from form object.
<c:forEach items="${form.addressList}" var="ad">
It should be
<c:forEach items="${addressList}" var="ad">
I have problem with adding/updating records with relations. Could please some advice how it should work?
I have two entities: Question and Category:
public class Question {
#Id
#GeneratedValue
private Long questionId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category")
private Category category;
and
#Entity
#Table(name = "category")
public class Category {
#Id
#GeneratedValue
private Long categoryId;
private String name;
I have some list of categories and I would like to add new Question with selected Category. So in my QuestionController I have add method:
#RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView add() {
ModelAndView mav = new ModelAndView("question/add");
mav.addObject("question", new Question());
mav.addObject("categoryList", categoryService.getAll());
return mav;
}
and form:
<form:form modelAttribute="question" method="POST" >
Name: <form:input path="name" value="${ques.name}" />
Category: <form:select path="category" items="${categoryList}" />
<input type="submit" value="Add" />
</form:form>
Everything looks good for now (I can fill question name and select category). But I don't know how add POST method should work
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String added(#ModelAttribute Question question, BindingResult bindingResult) {
}
When I try to use above method I have error: Failed to convert property value of type 'java.lang.String' to required type model.Category
I've tried to look for similar problem but I coudln't find anything.. So if someone can help/advice or show link to similar issue I would be grateful!
Cheers!
You need to provide code for Spring that tells it how to convert the string value from the web page back into a Category object. This is done by either:
Adding a PropertyEditor to the DataBinder.
Creating a Converter.
It is a bad practice using hibernate objects to map the form items. There are two solutions
Add another property private transient String categoryString; to the 'Question' class. and map the UI category to this <form:select path="categoryString" items="${categoryList}" />
That way you can avoid the error.
Do not use the hibernate mapping classes for mapping the form items, use POJOs for doing this. and later somewhere in your application map this simple pojo elements onto the hibernate entity.
try changing this line:
<form:select path="category" items="${categoryList}" />
to:
<form:select path="category.categoryId" items="${categoryList}" itemLabel="name" itemValue="categoryId"/>
Then in the added method (post method), retrieve the Category object back from hibernate and set back on question object before saving:
Category selectedCategory = yourHibernateService.getCategoryById(question.getCategory().getCategoryId());
question.setCategory(selectedCategory);
Thanks to some great articles here, I've been able to build a <h:selectOneMenu /> with selectItems containing objects. After providing a custom FacesConverter and fixing the missing equals()/hashcode() methods, I am able to use it to change the property of the backing bean and write it out to the DB.
The only weird thing is that all HTML <option /> elements of the <select />-element are checked="checked"! In other words: the <h:selectOneMenu /> does not reflect the current value of the bound property!
Details:
Store.java
#Entity
public class Store {
private Long id;
private String name;
#ManyToOne
private Category category;
// getters, setters, equals, hashcode
}
Category.java
#Entity
public class Category {
private Long id;
private String name;
// getters, setters, equals, hashcode
}
editStore.xhtml
<h:form>
....
<h:selectOneMenu value="#{backingBean.store.category}" id="category">
<f:selectItems value="#{backingBean.categorySelectItems}" />
</h:selectOneMenu>
....
</h:form>
BackingBean.java
public class BackingBean {
private Store store;
// inject data-access-facades via #EJB
// Constructor
// getters, setters
public List<SelectItem> getCategorySelectItems
List<SelectItem> items = new ArrayList<SelectItem>();
for (Category cat : categoryFacade.findAll() ) {
items.add(new SelectItem(cat, cat.getName()));
}
return items;
}
// action methods
}
I leave out listing the Category-Converter (it converts between the object and its ID).
The HTML this creates is:
<select id="category" name="category" size="1">
<option value="251" selected="selected">Kosmetik</option>
<option value="222" selected="selected">Sportwaren</option>
</select>
Obviously, store.category can only contain one item... why are both option-elements "selected"? No matter, what category is assigned to the store, the HTML always "selects" all option-elements.
How does JSF now, which SelectItem should be selected?
It's almost certain that the problem is in the equals(..) method, which returns true for all compared objects. Test this, and let your IDE generate the method (together with hashCode())