My Student class has list 'Objects'.
public class Student {
private List<Objects> list;
...
}
public class Objects {
private long id;
private String context;
...
}
#GetMapping("signup")
public String showSignUpForm(Model model) {
List<Objects> objectsList = new ArrayList<>();
objectsList.add(new Objects(1, "Math"));
objectsList.add(new Objects(2, "Phys"));
objectsList.add(new Objects(3, "Chem"));
objectsList.add(new Objects(4, "Geom"));
model.addAttribute("objects", objectsList);
model.addAttribute("student", new Student());
return "add-student";
}
#PostMapping("add")
public String addStudent(Student student, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-student";
}
return "redirect:list";
}
<form action="#" th:action="#{/students/add}" th:object="${student}" method="post">
<div class="group">
<div th:each="obj : ${objects}">
<input id="check" type="checkbox" th:field="*{list}" th:value="${obj}"/>
<label for="check" th:text="${obj.getContext()}"></label>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-primary" value="Submit">
</div>
</div>
</form>
So I want to add selected objects to student.list but I get this error:
Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property 'list'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'example.Objects' for property 'list[0]': no matching editors or conversion strategy found
How to do it right?
First of all, if you want to work with list you have to do it by index. So if you want to bind a list you have to do like below.
#GetMapping("signup")
public String showSignUpForm(Model model) {
List<Objects> objectsList = new ArrayList<>();
objectsList.add(new Objects(1, "Math"));
objectsList.add(new Objects(2, "Phys"));
objectsList.add(new Objects(3, "Chem"));
objectsList.add(new Objects(4, "Geom"));
List<Objects> emptyList = new ArrayList<>();
Student student = new Student();
student.addList(emptyList);
model.addAttribute("objects", objectsList);
model.addAttribute("student", student);
return "add-student";
}
<form action="#" th:action="#{/students/add}" th:object="${student}" method="post">
<div class="group">
<div th:each="obj,iterator : ${objects}">
<input id="check" type="text" th:field="*{list[${iterator.index}].id}" th:value="${obj.id}"/>
<input type="text" th:field="*{list[${iterator.index}].context}" th:value="${obj.context}"/>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-primary" value="Submit">
</div>
</div>
</form>
Secondly, if you want to do select and add then you have to use javascript. You have to track the index of the list and bind the value separately (id and context) in the correct index. You have to also consider to remove object from the list for unselection.
Related
I familiarize myself with Thymeleaf.
I am trying to get data from a POST-mapping in the controller.
But I got null variables when I call employee.getName().
I looked a lot online but can't find a solution.
My controller class
#GetMapping
public String getEmployees(Model model) {
model.addAttribute("newEmployee", new Employee());
return "employees";
}
#PostMapping
public String addEmployee(#ModelAttribute("newEmployee") Employee employee) {
System.out.println(employee); // return object
System.out.println(employee.getFirstName()); // return null
return "employees";
}
HTML form with Thymeleaf
<div class="modal-body">
<form th:action="#{/employees}" th:object="${newEmployee}" method="post">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-form-label">First Name <span class="text-danger">*</span></label>
<input th:field="*{firstName}" class="form-control" type="text">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-form-label">Last Name</label>
<input th:field="*{lastName}" class="form-control" type="text">
</div>
</div>
See tutorials:
Wim Deblauwe: Form handling with Thymeleaf
Baeldung: Binding a List in Thymeleaf
#GetMapping("/all")
public String showAll(Model model) {
model.addAttribute("employees", employeeService.findAll());
return "employees/all";
}
#GetMapping("/add")
public String createForm(Model model) {
model.addAttribute("newEmployee", new Employee()); // the form-data object as blank employee to be filled
return "employees/form"; // the page containing the form
}
#PostMapping
public String addEmployee(#ModelAttribute("newEmployee") Employee employee) {
System.out.println(employee); // return object
System.out.println(employee.getFirstName()); // return null if either not set in HTML-form or not mapped from HTML-form to POST correctly
// save new employee or add to a repository
employeeService.save(newEmployee);
model.addAttribute("employees", employeeService.findAll());
return "redirect:/employees/all"; // use a redirect
}
Client-Server interaction:
First in your browser you should navigate to the endpoint adding the form-data-object, i.e. GET /employees/add.
Then the form-page should load and you will have a blank object newEmployee to bind all the input-fields to for submitting.
After submitting the form via the submit-click, a POST /employees is sent. The server/controller should receive the form-data as model-attribute in employee parameter and can save it.
The controller-method redirects back to the list-endpoint (same as navigating in browser to) GET /employees/all
<h1>Create employee</h1>
<form th:object="${newEmployee}"
th:action="#{/employees}"
method="post">
<input type="text" name="firstName" id="firstName" th:field="*{firstName}" />
<input type="text" name="lastName" id="lastName" th:field="*{lastName}" />
<button type="submit">Submit new employee</button>
</form>
The th:object attribute refers to the key under which we put our Employee instance in the model (newEmployee in this example). The th:action has the URL for the #PostMapping method. Finally, the method attribute is set to "post" since we want to use the HTTP POST method.
Using th:field=*{…}, we can setup a two-way binding between the HTML input and the field in our form data object.
I need sone help to send the List produtos as parameter to my controller.
<div class="panel-body">
<div th:each="produto : ${produtos}" class="form-group" id="produtos">
<label th:text="${produto.nome}" class="col-sm-2 control-label">
</label>
<div class="col-sm-1">
<input type="text" class="form-control js-number" id="quantPedido"
th:field="*{produto.quantPedido}"/>
</div>
</div>
</div>
My Controller
#RequestMapping(method = RequestMethod.POST)
public String salvar(#Validated Pedido pedido, List<Produto> produtos, Errors errors, RedirectAttributes attributes) {
}
Thanks so Much!
You need to warp yours products list into some bean, then send that bean to the html and fill it. Example:
Bean:
public class Pedido{
#Getter #Setter
private List<Product> products = new ArrayList();
//extra attributes
}
In your controller:
ModelAndView mav = new ModelAndView("yourView");
Pedido pedido = new Pedido();
mav.addObject("pedido", pedido);
In your html:
<form th:object="${pedido}" th:action="#{/savePedido}" method="POST">
//...
<div th:each="produto : ${produtos}" class="form-group" id="produtos">
<label th:text="${produto.nome}" class="col-sm-2 control-label">
</label>
<div class="col-sm-1">
<input class="form-control js-number" id="quantPedido" th:field="*{products[__${produtoStat.index}__].quantPedido}"/>
</div>
</div>
//...
</form>
I have a project Spring Boot with Thymeleaf as front-end, I need to create an object from a form in view, this Object is complex:
#Entity
#Table(name = "tasks", catalog = "explorerrh")
public class Tasks implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idtasks;
private Employee employee;
private String taskName;
private String taskDescrption;
private Date taskTime;
private String statut;
As you see the bean Tasks has an attribute name Employee.
Now I want to create a form to create a "Task"
So in controller I pass the bean Tasks as an empty bean and bean Employee like this:
#RequestMapping(value = "/dashboard.html", method = RequestMethod.POST)
public ModelAndView loginUser(#ModelAttribute Login login, Model model) {
Employee employee = employeeService.
getEmployeeByMail(login.getMailAddress());
ModelAndView mav = new ModelAndView("dashboard");
mav.addObject("employee", employee);
mav.addObject("date", mediumDateFormat.format(date));
mav.addObject("task", new Tasks());
return mav;
}
Then:
In view Thymeleaf :
<form role="form" action="/addTask" th:action="#{/addTask}" th:object="${task}" method="POST">
<div class="form-group">
<label for="taskTitle"><span class="glyphicon glyphicon-user"></span> Task Title</label>
<input type = "text" class = "form-control" th:field="*{taskName}" id = "taskTitle" placeholder = "Enter Task title"/>
</div>
<div class="form-group">
<label for="taskTitle"><span class="glyphicon glyphicon-user"></span>employee</label>
<input type="text" class="form-control"
th:field="*{employee}" th:value="${employee}" id="employeeTask"/>
</div>
<div class="form-group">
<label for="taskDescription">
<span class="glyphicon glyphicon-eye-open"></span> Task Description</label>
<textarea name="taskDescription" th:field="*{taskDescrption}" class="form-control" rows="5" id="taskDescription"></textarea>
</div>
<div class="well">
<div id="datetimepicker1" class="input-append date">
<input data-format="dd/MM/yyyy hh:mm:ss"
name="taskDateTime" th:field="*{taskTime}" type="text"/>
<span class="add-on">
<i data-time-icon="icon-time" data-date-icon="icon-calendar">
</i>
</span>
</div>
</div>
<div class="form-group">
<label for="taskStatut"><span class="glyphicon glyphicon-user"></span> Task statut</label>
<input type = "text" class = "form-control" th:field="*{taskStatut}" id = "taskStatut" placeholder = "Enter Task statut"/>
</div>
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-off"></span> add task</button>
</form>
Finally I implement a controller to intercept this form:
public ModelAndView addTask(#ModelAttribute Tasks tasks) {
ModelAndView mav = new ModelAndView("dashboard");
Employee employee = employeeService.
getEmployeeByIdemployee(String.valueOf(
tasks.getEmployee().getIdemployee()));
tasks.setStatut("Open");
tasks.setEmployee(employee);
tasksService.addTask(tasks);
return mav;
}
I have this error when sending the form:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Apr 13 23:47:19 GMT+01:00 2016
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='tasks'. Error count: 2
The error said you that object can't be saved due to validation errors. Also it said that you have 2 validation errors.
In your form th:object named task, so rename it in submit method. Also you need to add BindingResult after bean to handle errors :
public ModelAndView addTask(#ModelAttribute("task") Tasks tasks, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
//errors handling
}
}
Here's my controller
#RequestMapping(value = "/save",method = RequestMethod.POST)
public ModelAndView saveUserAccount(#ModelAttribute("account") UserAccountForm userAccount, BindingResult result){
AddUserValidator userValidator = new AddUserValidator();
userValidator.validate(userAccount,result);
boolean hasErrors = result.hasErrors();
if(hasErrors){
return render(ADD_USER_VIEW)
.addAttr("ACCOUNT_ROLES", Arrays.asList(AccountRole.values()))
.addAttr("TENANTS",tenantInfoService.getAll())
.addAttr("errors",result)
.addAttr("account",userAccount).toMav();
}
return render(ADD_USER_VIEW)
.addAttr("ACCOUNT_ROLES", Arrays.asList(AccountRole.values()))
.addAttr("TENANTS",tenantInfoService.getAll())
.addAttr("account",new UserAccountForm())
.toMav();
}
Here's the render library That I have created.
public class RenderMavBuilder {
private final ModelAndView mav;
public static RenderMavBuilder render(String viewname){
RenderMavBuilder target = new RenderMavBuilder(viewname);
return target;
}
public RenderMavBuilder addAttr(String attrName, Object value){
mav.addObject(attrName, value);
return this;
}
public RenderMavBuilder addAttr(Object value){
mav.addObject(value);
return this;
}
public RenderMavBuilder addAttrs(Map<String , ?> attrs){
mav.addAllObjects(attrs);
return this;
}
private RenderMavBuilder(String viewName){
this.mav = new ModelAndView(viewName);
}
public ModelAndView toMav(){
return mav;
}
}
Here's my validator
Here's my form.
<div class="col-md-6 centered">
<form:errors path="*" />
<form:form commandName="account" method="post" action="${pageContext.request.contextPath}/user/save">
<!-- Username -->
<div class="col-xs-12 form-group">
<label class="control-label">Username</label>
<form:input path="username" type="text" class="form-control" />
</div>
<!-- Password -->
<div class="col-xs-12 form-group">
<label class="control-label">Password</label>
<form:password path="password" class="form-control"/>
</div>
<!-- Password -->
<div class="col-xs-12 form-group">
<label class="control-label">Password</label>
<form:password path="retypedPassword" class="form-control"/>
</div>
<!-- First Name -->
<div class="col-xs-12 form-group">
<label class="control-label">First Name</label>
<form:input path="firstName" type="text" class="form-control"/>
</div>
<!-- First Name -->
<div class="col-xs-12 form-group">
<label class="control-label">Last Name</label>
<form:input path="lastName" type="text" class="form-control"/>
</div>
<!-- User Role -->
<div class="col-xs-12 form-group">
<label class="control-label">User Role</label>
<form:select path="accountRole" class="form-control">
<form:options items="${ACCOUNT_ROLES}"/>
</form:select>
</div>
<!-- Branch Designation -->
<div class="col-xs-12 form-group">
<label class="control-label">Designated Branch</label>
<select path="tenantId" items="${TENANTS}" class="form-control">
<c:forEach var="branch" items="${TENANTS}">
<option value="${branch.id}">${branch.tenantDescription}</option>
</c:forEach>
</select>
</div>
<!-- Button -->
<div class="col-md-12 form-group">
<button class="form-control btn btn-primary submit-button" type="submit">Save New User <i class="fa fa-check-square-o"></i></button>
</div>
</form:form>
On my controler the binding result has errors. however, the error is not being displayed on the view, what am I missing?
The problem is the way you are constructing your ModelAndView, you should use the Errors object to construct it. Use Errors.getModel() to obtain the underlying Map that represents the model and use that, next to the view name, to construct the ModelAndView.
Next to that for your other model attributes you should simply add #ModelAttribute annotated methods to retrieve those. I would also suggest adding a GET based method which you redirect to after successful submit.
Your helper class seems also rather pointless as doing it simply inside your controller would simplify your code (imho).
Your controller could look something along these lines.
#RequestMapping(value = "/save",method = RequestMethod.POST)
public ModelAndView saveUserAccount(#ModelAttribute("account") UserAccountForm userAccount, BindingResult result){
AddUserValidator userValidator = new AddUserValidator();
userValidator.validate(userAccount,result);
boolean hasErrors = result.hasErrors();
return new ModelAndView(hasErrors ? ADD_USER_VIEW, "redirect:/add", errors.getModel());
}
#RequestMapping(value="/add")
public String addUserAccount(Model model) {
model.addObject("account", new UserAccountForm());
return ADD_USER_VIEW;
}
#ModelAttribute
public void referenceData(Model model) {
model.addObject("ACCOUNT_ROLES", Arrays.asList(AccountRole.values()));
model.addObject("TENANTS",tenantInfoService.getAll());
}
Following are my POJO, Action, and JSP page.
For single instance of Expense I am getting proper input.
I would like to use ModelDriven to fetch multiple instance of Expense from JSP page.
For that I have created multiple <div class="Expense">...</div>, but I dont know what changes should I made in Action class.
How to achieve this?
Pojo Class:
public class Expense implements java.io.Serializable {
private Long id;
private Client client;
private String param;
private BigDecimal value;
private Date dt;
private Date adddate;
//getter and setter
}
Action Class:
public class ExpenxeAction extends ActionSupport implements ModelDriven<Expense> {
Expense e = new Expense();
ExpenseDAO dao = new ExpenseDAO();
private LoginCheck lc = null;
private List<Expense> expenseList = new ArrayList<Expense>();
public String insertExpense() {
dao.insert(e);
return SUCCESS;
}
#Override
public Expense getModel() {
return e;
}
}
JSP page:
<div class="Expense">
<label>Expense Type</label>
<input type="text" name="param" id="param"/>
<label>Amount</label>
<input type="text" name="value" class="form-control" id="value"/>
<label>Date</label>
<input type="text" name="dt" class="form-control" id="dt"/>
</div>
You should create a getter for expenseList
public List<Expense> getExpenseList() {
return expenseList;
}
in the JSP use indexed property names
<s:iterator value="expenseList" status="stat">
<div class="Expense">
<label>Expense Type</label>
<s:textfield name="expenseList[%{#stat.index}].param" id="param"/>
<label>Amount</label>
<s:textfield name="expenseList[%{#stat.index}].value" class="form-control" id="value"/>
<label>Date</label>
<s:textfield name="expenseList[%{#stat.index}].dt" class="form-control" id="dt"/>
</div>
</s:iterator>
To fetch array list of Expense objects in action class you need to make the following changes to your code.
First you have to change ModelDriven<Expense> to ModelDriven<List<Expense>> and remove Expense e = new Expense();, if you wish. So, finally your action class should look like this.
public class ExpenxeAction extends ActionSupport implements ModelDriven<List<Expense>>, Preparable {
ExpenseDAO dao = new ExpenseDAO();
private LoginCheck lc = null;
private List<Expense> expenseList = new ArrayList<Expense>();
public String insertExpense() {
for (Expense e : expenseList) { //loop to iterate over each elements
dao.insert(e);
}
return SUCCESS;
}
public List<Expense> getExpenseList() {
return expenseList;
}
public void setExpenseList(List<Expense> expenseList) {
this.expenseList = expenseList;
}
#Override
public void prepare() throws Exception {
expenseList = new ArrayList<Expense>();
}
#Override
public List<Expense> getModel() {
return expenseList;
}
}
Now, you have to make changes to your jsp page.
<div class="Expense">
<label>Expense Type</label>
<input type="text" name="model[0].param" id="param"/>
<label>Amount</label>
<input type="text" name="model[0].value" class="form-control" id="value"/>
<label>Date</label>
<input type="text" name="model[0].dt" class="form-control" id="dt"/>
<label>Expense Type</label>
<input type="text" name="model[1].param" id="param"/>
<label>Amount</label>
<input type="text" name="model[1].value" class="form-control" id="value"/>
<label>Date</label>
<input type="text" name="model[1].dt" class="form-control" id="dt"/>
<label>Expense Type</label>
<input type="text" name="model[2].param" id="param"/>
<label>Amount</label>
<input type="text" name="model[2].value" class="form-control" id="value"/>
<label>Date</label>
<input type="text" name="model[2].dt" class="form-control" id="dt"/>
</div>
You will notice that in the jsp code we have mentioned
model[index].<pojo_member_variable_name> instead of specifying of
lstExp[index].<pojo_member_variable_name>
This is because now we want struts2 to set the values directly into these member variables. So we have mentioned model[index].<pojo__member_variable_name> so that struts2 should automatically set the value into the list. Here in the above code we are creating list of 3 Expense Object. If you need to insert more values then just increment the index and set the values and remaining will be done by struts2 for you.