I try to have something like a List as a ModelAttribute with Thymeleaf, but I do not get it to work. I read this page http://www.bincsoft.com/blog/thymeleaf-and-lists-in-forms/ but my code fails.
here are my files:
container class
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
public class WagerateForm {
#Valid
private List<WagerateUi> wagerateuilist;
public WagerateForm() {
wagerateuilist = new ArrayList<WagerateUi>();
}
public WagerateForm(List<WagerateUi> wagerateuilist) {
this.wagerateuilist = wagerateuilist;
}
//getter , setter omitted
}
my DTO for ui
public class WagerateUi {
//getter , setter omitted
private boolean standard;
private Long value;
}
controller
#Named
#RequestMapping("/wagerate")
public class WagerateController {
#RequestMapping(value="")
public String wagerate(#ModelAttribute("wagerateform") WagerateForm wagerateForm,
BindingResult bindingResult) {
List<WagerateUi> wagerateUiList = wagerateForm.getWagerateuilist();
System.out.println(wagerateUiList.size());
//dummy items
for (int i = 0; i < 10; i++) {
WagerateUi temp = new WagerateUi();
temp.setValue(100L);
wagerateUiList.add(temp);
}
return "wagerate";
}
thymeleaf html
<form id="wagerateform" class="form-horizontal" role="form" action="#" th:object="${wagerateform}" th:action="#{/wagerate}" method="post">
<div th:each="wagerateui, iterStat : ${wagerateform.wagerateuilist}">
<input th:field="*{wagerateform[__${iterStat.index}__].value}" class="form-control">
</div>
<button type="submit">Test</button>
</form>
error message
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'wagerateform[0]' of bean class [WagerateForm]: Bean property 'wagerateform[0]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:725)
at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperImpl.java:571)
at org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath(BeanWrapperImpl.java:548)
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:714)
at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99)
at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:229)
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:348)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:288)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:260)
The size of the list in the controller is always 0. I cant see whats wrong, i tried different ways, but I think my Thymeleaf/Spring EL is wrong.
I got it working so far with:
<input th:value="${wagerateform.wagerateuilist[__${iterStat.index}__].value}" class="form-control">
Your wagerateform is not a List, the List is the wagerateuilist. With the th:each you fill the wagerateui object with elements while your looping through your List.
<div th:each="wagerateui : ${wagerateform.wagerateuilist}">
<input th:field="${wagerateui.value}" class="form-control">
</div>
Related
I'm rather new to Thymeleaf, so this maybe a newbie mistake. And I've been looking everywhere online for an answer and haven't found one. So sorry if this is really basic.
Basically I'm using Thymeleaf with SpringBoot 2.6.7 and I want to populate a object using a form. Something I've been able to do in the past, but this time for the first time the object I want to populate contains a list of other objects. And it's getting quite tricky.
My html looks like this :
<form action="#" th:action="#{/character}" th:object="${input}" method="post">
<div th:each="i : ${#numbers.sequence(0, input.attributes.size - 1)}">
<div th:object="${input.attributes[i]}">
<input type="range" min="1" th:max="*{maxValue}" th:field="*{value}"> <!-- this doesn't work -->
<p th:text="*{name} + ' = ' + *{value}"></p> <!-- this works -->
</div>
</div>
</form>
The error I get is java.lang.NumberFormatException: For input string: "i". So I'm guessing there's an issue with the parsing of attributes[i] when processing th:field.
I've tried to change the loop to <div th:each="attribute : ${input.attributes}"> (and the associated th:object) but that just made it worse, got the Neither BindingResult nor plain target object for bean name 'attribute' available as request attribute error message.
If it's any help, here is my controller :
#GetMapping("character")
public String startingForm(Model model) {
model.addAttribute("input", new FormInput());
return "character";
}
#PostMapping("character")
public String processingForm(#ModelAttribute FormInput input, BindingResult bindingResult, Model model)
throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
if(bindingResult.hasErrors()){
log.error("something went wrong");
}
// Other stuff
return "character";
}
My input class
#Getter
public class FormInput{
protected List<Attribute> attributes;
public FormInput(){
attributes= new ArrayList<>();
for (AttributeEnum ae : AttributeEnum.values()) {
attributes.add(new Attribute(ae.getName(), ae));
}
}
}
And the Attribute class
#Getter
public class Attribute {
protected String name;
protected AttributeEnum typeAttribute;
protected Integer value = 1;
protected Integer maxValue = 5;
public Attribute(String n, AttributeEnum ta){
name = n;
typeAttribute = ta;
}
public boolean setValue(Integer v){
if (v > maxValue ) return false;
value = v;
return true;
}
}
Does someone know a fix to this issue?
Just found the solution. So I'm posting the answer is anyone has the same issue.
<div th:each="i : ${#numbers.sequence(0, input.attributes.size - 1)}">
<input type="range" min="0" th:max="${input.attributes[i].maxValue}" th:field="${input.attributes[__${i}__].value}"> <!-- this works -->
</div>
Just had to replace attributes[i] with attributes[__${i}__].
I am new to Thymeleaf,Maybe this is a simple question.Please help me.Thanks.
Controller code:
#Controller
public class TestController {
#GetMapping(value = "/")
public String testget(Map<String, Object> model) {
TestBean bean = new TestBean();
List<Person> list = new ArrayList<>();
for (int index = 1; index < 5; index++) {
Person p = new Person();
p.setId(index);
p.setName("name" + index);
list.add(p);
}
model.put("allList", "nothing");
bean.setList(list);
model.put("testbean", bean);
return "NewFile";
}
#PostMapping(value = "/")
public String testpost(Map<String, Object> model//
, #ModelAttribute(name = "testbean") TestBean bean) {
List<Person> list = bean.getList();
model.put("bean", bean);
model.put("allList", list.toString());
return "NewFile";
}}
simple mapper and a Person bean:
#Data
public class TestBean {
private List<Person> list;
}
#Data
public class Person {
private int id;
private String name;
}
HTML code :
<form action="#" th:action="#{/}" th:object="${testbean}" method="post">
<p th:text="'ALL : ' + ${allList}"></p>
<table>
<tr th:each="person : ${testbean.list}">
<td>Id:<input type="text" th:value="${person.id}"
/></td>
<td>name: <input type="text" th:value="${person.name}" /></td>
</tr>
</table>
<input type="submit" value="submit" />
</form>
I want put list to page and change properties to refresh.
but i don't know how to add th:field tag, I try to add
**th:field="*{testbean.list[__${index}__].id}"**
but it failed with :
Invalid property 'testbean' of bean class [com.TestBean]
UPDATE1
And i tried
th:field="*{list[__${index}__].id}"
I got a error Where I added th:field
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring4.processor.attr.SpringInputGeneralFieldAttrProcessor' (NewFile:14)] with root cause
java.lang.NumberFormatException: For input string: "null"
My questions is what i can do something that i can get a List in controller.
Invalid property 'testbean' of bean class [com.TestBean]
Use th:field="*{list[__${index}__].id} pattern without testbean definition. If you use * notation then it will reference to parent object.
java.lang.NumberFormatException: For input string: "null"
Add any variable like "rowStat" to the loop for keeping status index:
th:each="person,rowStat : ${testbean.list}"
and access it like th:field="*{list[__${rowStat.index}__].id}. So index will not be null and no problem converting the string to int.
This is how I do it on page that lists some search results - table with checkboxes. Code itself is stripped only to relevant lines. places.content is a collection of Place objects.
<form th:action="#{selected-places}" method="POST" >
<tr th:each="place,i : ${places.content}">
<td><input name="places" th:value="${place.id}" type="checkbox"></td>
</tr>
</form>
And the controller side
#PostMapping(value = "/selected-places", params = "action=delete")
public String deletePlaces(#RequestParam Place[] places, RedirectAttributesModelMap model) {
....
}
As you can see, despite fact that form is sending only list of IDs, spring will autohydrate model entities. You can always Place[] places to collection of integers and it still will work.
I'm skilling in form validation with spring boot and thymeleaf and i have problem: i can't do validation in form with two #ModelAttribute fields. The example like form validation om spring official site works correctly, but when I added two #model Attribute in post i get only error at webpage and no hints at form like in spring example.
Controller class:
#Controller
public class MyController {
#Autowired
InstructorRepository instructorRepository;
#Autowired
DetailRepository detailRepository;
#GetMapping("/index")
public String mainController(){
return "index";
}
#GetMapping("/add")
public String addInstructorForm(Model model){
model.addAttribute("instructor", new Instructor());
model.addAttribute("detail", new InstructorDetail());
return "addInstructor";
}
#PostMapping("/add")
public String submitForm(#Valid #ModelAttribute Instructor instructor, #ModelAttribute InstructorDetail instructorDetail, BindingResult bindingResult1){
/* if (bindingResult.hasErrors()) {
return "instructorsList";
}
instructor.setInstructorDetail(instructorDetail);
instructorRepository.save(instructor);*/
if (bindingResult1.hasErrors()) {
return "addInstructor";
}
return "redirect:/instructorsList";
}
#GetMapping("/instructorsList")
public String getList(Model model){
Map map = new HashMap<>();
List list = new ArrayList<Instructor>();
list = instructorRepository.findAll();
List resultList = new ArrayList();
for (int i = 0; i < list.size(); i++) {
Instructor instructor = (Instructor)list.get(i);
InstructorDetail detail = detailRepository.getInstructorDetailById(instructor.getId());
InstructorAndDetail iid = new InstructorAndDetail(instructor, detail);
resultList.add(iid);
}
model.addAttribute("instructors", resultList);
return "instructorsList";
}
}
html form snippet:
<form action="#" data-th-action="#{/add}" data-th-object="${instructor}" method="post">
<div class="form-group">
<label for="1">First name</label>
<input class="form-control" id="1" type="text" data-th-field="${instructor.firstName}" placeholder="John"/>
<div data-th-if="${#fields.hasErrors('firstName')}" data-th-errors="${instructor.firstName}">name error</div>
</div>
There was next problem: then I add entity to thymeleaf form I passed only 2 fields (or one field after), but there was 3 fields with
#NotNull
#Size(min=2, max=30)
So when I commented them in my code the single field validation begin to work :).
If you stucked at the same problem check that all you fields in class that marked #Valid annotation are mirrored in your form.
(or have default valid values? UPD: dont work with valid defaults if they have no form mirroring)
How to bind input elements to an arraylist element in Spring MVC?
The view model:
public class AssigneesViewModel {
private int evaluatorId;
private int evaluatedId;
private String evaluatorName;
private String evalueatedName;
//getters and setters
}
The model attribute:
public class AssignEvaluationForm{
private ArrayList<AssigneesViewModel> options;
public ArrayList<AssigneesViewModel> getOptions() {
return options;
}
public void setOptions(ArrayList<AssigneesViewModel> options) {
this.options = options;
}
}
Controller
#RequestMapping(value="addAssignment", method = RequestMethod.GET)
public String addAssignment(Model model){
model.addAttribute("addAssignment", new AssignEvaluationForm());
return "addAssignment";
}
Then in the jsp i have 4 hidden inputs which represent the fields for the evaluatedId, evaluatorId, evaluatorName, evaluatedName -> options[0].
How i am going to write the jsp code to map those inputs with an element of the arrayList?
Update:
<form:form commandName="addAssignment" modelAttribute="addAssignment" id="addAssignment" method="POST">
//..........
<c:forEach items="${addAssignment.options}" var="option" varStatus="vs">
<div id="assigneesOptions" >
<form:input path="addAssignment.options[${vs.index}].evaluatedId" value="1"></form:input>
</div>
</c:forEach>
//..............
</form:form>
With this update i get the following error:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'options[]' available as request attribute
<form:input path="addAssignment.options[${vs.index}].evaluatedId" value="1"></form:input>
Instead of this addAssignment.options[${vs.index}].evaluatedId
Use this -> option.evaluatedId
Or you might reach value with arraylist get -> ${addAssignment.options.get(vs.index).evaluatedId} , try to turn out that ${} jstl's call curly brackets. BTW i'm not sure this last example work on path="" attribute.
// my form
public class myForm {
private double[] myField;
public double[] getMyField(){
return myField;
}
public void setMyField(double[] myField){
this.myField = myField;
}
}
// my jsp
...
...
<c:set var="i" value="0"/>
<c:forEach items="${myList}" var="data">
<form:input path="myField[${$i}]"/>
<c:set var="i">${i + 1}</c:set>
</c:forEach>
...
...
After spring render jsp generate this code ;
<input type="text" value="0.0" name="myField0" id="myField0"/>
<input type="text" value="0.0" name="myField1" id="myField1"/>
<input type="text" value="0.0" name="myField2" id="myField2"/>
...
...
Spring cant bind my form on controller , because form names not valid (myField0, myField1..) . If i change names with firebug (as myField[0], myField[1] etc.) initBinder works and i catch my form data on controller. How can i solve this?
Thanks.
Use a Collection in your form instead of an array :
public class myForm {
private Collection<Double> myField;
public Collection<Double> getMyField(){
return myField;
}
public void setMyField(Collection<Double> myField){
this.myField = myField;
}
}