How to post a list to controller in Thymeleaf - spring-boot

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.

Related

How to handle repository exception in the controller - spring mvc

i'm using spring boot and spring MVC. I'm creating a simple form(CRUD)
here is the code:
#Document
public class User
{
#Id
private ObjectId id;
#Indexed(unique=true)
#NotNull
#Size(min=2, max=30)
private String username;
#NotNull
#Size(min=2, max=30)
private String password;
private List<String> roles;
...
Controller:
#Controller
#RequestMapping("/admin")
public class AdminController {
...
/**
* NEW USER (POST)
*/
#RequestMapping(value = "new", method = RequestMethod.POST)
public ModelAndView newUser(#Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return new ModelAndView("/admin/new");
}
user.setRoles(Arrays.asList(Constants.ROLE_ADMIN));
ur.save(user);
return new ModelAndView("redirect:/admin");
}
/**
* NEW USER (VIEW)
*/
#RequestMapping(value = "new", method = RequestMethod.GET)
public ModelAndView newUser(User user) {
ModelAndView mv = new ModelAndView("admin/new");
return mv;
}
...
}
and the View:
<form name="new" th:action="#{/admin/new}" th:object="${user}" method="post">
<table>
<tr th:if="${error != null}">
<td colspan="4">
<span th:text="${error}"></span>
</td>
</tr>
<tr>
<td>Name:</td>
<td><input type="text" name="username" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" autocomplete="off"/></td>
<td width="10"/>
<td th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></td>
</tr>
<tr>
<td><button type="submit">Create</button></td>
</tr>
</table>
</form>
and it works, if I put an username bigger than 30 character.
But if I got and exception from the repository, for example:
DuplicateKey from the mongodb repository, didn't work.
So i tried to put this code in the controller:
#ExceptionHandler(Exception.class)
public ModelAndView handleCustomException(Exception ex) {
ModelAndView model = new ModelAndView("admin/new");
model.addObject("error", ex.getMessage());
return model;
}
It handle all the exceptions, but in this moment I don't have the "User" or "BindingResult" and when it try to render gets this error:
2015-09-22 13:36:55.498 ERROR 6208 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('username')" (admin/new:21)] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
What I'm doing wrong?
How should I handle this kind of exception?
There is a way to send the USER to ExceptionHandler?
Thanks.
Because you don't have to return admin/new from the error handler. The view is expecting the User object that you are correctly populating in normal controllers. In case of an error this object simply isn't there. So, return another view from the error controller: ModelAndView model = new ModelAndView("admin/error"); for example.
Anyhow, this is not a solution. You need to catch exceptions. In this situation it is always better to have an intermediate layer called Service. In service you can catch any repository exceptions and decide what to do about them.

How to bind input elements to an arraylist element in Spring MVC?

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.

Thymeleaf ModelAttribute List

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>

Spring MVC instantiates a new Bean instead of reusing the old one in the Model (#ModelAttribute)

I think my problem is rather simple, but it's been 2 days and I can't figure it out:
I am new to Spring MVC and I am trying to implement a simple #Controller that handles a form.
GET request: I add a new PortfolioBean attribute to the Model.
POST request: I expect to receive a #ModelAttribute with the same PortfolioBean.
#Controller
public class FormController {
#RequestMapping(value = "/form", method = RequestMethod.GET)
public String getForm(Model model) {
PortfolioBean portfolio = new PortfolioBean();
model.addAttribute("portfolio", portfolio);
return "index";
}
#RequestMapping(value = "/form", method = RequestMethod.POST)
public String postForm(#ModelAttribute("portfolio") PortfolioBean portfolio) {
System.out.println("Received portfolio: " + portfolio.getId());
return "showMessage";
}
}
Here is my JSP view:
...
<form:form action="form" commandName="portfolio" method="post">
Name : <form:input path="name" />
Nick Name : <form:input path="nickName" />
Age : <form:input path="age" />
Mobile : <form:input path="mobNum" />
<input type="submit" />
</form:form>
And here is my PortfolioBean:
public class PortfolioBean {
private String name;
private String nickName;
private int age;
private String mobNum;
private static int count = 0;
private int id;
public PortfolioBean() {
count++;
id = count;
System.out.println("NEW BEAN: " + id);
}
// setters & getters
}
As you can see, I added a static count variable to assign incremental IDs, and a println("NEW BEAN!") on the constructor.
My problem is that when I POST the form, I don't receive my original Bean object, instead Spring instantiates a new one, but I want my old Bean :(
Log:
GET /form
NEW BEAN: 1
POST /form
NEW BEAN: 2
Received portfolio: 2
Model attribute only exist in the context of one request. Towards the end of handling the request, the DispatcherServlet adds all the attributes to the HttpServletRequest attributes.
In your first request, you add a Model attribute and it becomes available for use in your jsp.
In your second request, because of the #ModelAttribute, Spring will try to create an instance from your request's request parameters. This will be a completely different instance as the previous one no longer exists.
If you want to reference the old object, you need to store it in a context that spans multiple requests. You can use HttpSession attributes for that purpose, either directly or through flash attributes. You might want to look into RedirectAttributes and #SessionAttributes.

how to read the already existing bean in jstl|form?

I have simple form with SpringMVC, I want retrieve my already had bean by pbid. the problem is the server side I can get the already setting bean, but the jsp side it always get new bean. Can I use the #ModelAttribute("productbean") to received some parameters to get the bean store in my server-side? how to do it? The jstl|form seem always get new form
<form:form method="post" modelAttribute="productbean" action="">
<table>
</tr>
<tr>
<td width="118"><form:label for="name" path="name" > Production Name:</form:label></td>
<td colspan="2"><form:hidden path="pbid"/><form:input path="name" type="text" size="50" /></td>
</tr>
...
My controler is like:
#RequestMapping(value="/createproduct", method=RequestMethod.GET)
public String getProduct(HttpServletRequest req, Model model,
#RequestParam(value = "pbid", required = false, defaultValue = "") String spbid) throws MalformedURLException {
UUID pbid;
if(spbid.isEmpty())pbid=UUID.randomUUID();
else pbid=UUID.fromString(spbid);
ProductBean tmp;
if(!products.containsKey(pbid)){
tmp=createbean();
pbid=tmp.getPbid();
System.err.println("============new productbean===============\n");
}else{
tmp=products.get(pbid);
System.err.println(tmp.getMpf().size());
System.err.println(tmp.getMpf().printFileNameList());
}
.....
#ModelAttribute("productbean")
public ProductBean createbean(){
ProductBean productbean=new ProductBean(context.getRealPath(filepath));
products.put(productbean.assignID(), productbean);
return productbean;
}
Add session attribute annotation over class
#SessionAttributes("productbean")
#controller
public class test() {
}

Resources