I am creating a TO-DO list using Spring Boot, Spring Data, Thymeleaf. Can anyone tell me if my syntax is correct. This is my first application using spring boot. I am getting this error org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "" (index)
This is the index.html
<div th:if test="${mode=='MODE_HOME'}">
<div class="container" id ="homeDiv">
<div class="jumbotron text-center">
<h1>Welcome to Tasks Manager</h1>
</div>
</div>
</div>
<div th:if test="${mode=='MODE_TASKS'}">
<div class="container text-center" id ="tasksDiv">
<h3>My Tasks</h3>
<hr/>
<div class="table-responsive">
<table class="table table-striped table-bordered text-left">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Date</th>
<th>Finished</th>
</tr>
</thead>
<tbody>
<tr th:each="task: ${tasks}">
<td th:text="${task.id}"></td>
<td th:text="${task.name}"></td>
<td th:text="${task.description}"></td>
<td th:text="${task.dateCreated}"></td>
<td th:text="${task.finished}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div th:if test="${mode=='MODE_NEW' || mode=='MODE_UPDATE'}">
<div class="container text-center">
<h3>Manage Task</h3>
<hr/>
<form class="form-horizontal" method="POST" action="save-task">
<input type="hidden" name="id" value="${task.id}"/>
<div class="form-group">
<label class="control-label col-md-3">Name</label>
<div class="col-md-7">
<input type="text" class="form-control" name="name" placeholder="name" value="${task.name}"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3">Description</label>
<div class="col-md-7">
<input type="text" class="form-control" name="description" placeholder="name" value="${task.description}"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3">Finished</label>
<div class="col-md-7">
<input type="radio" class="col-sm-1" name="finished" placeholder="finished" value="true"/>
<div class="col-sm">Yes</div>
<input type="radio" class="col-sm-1" name="finished" placeholder="finished" value="false"/>
<div class="col-sm">No</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Save"/>
</div>
</div>
</form>
</div>
</div>
This is the MainController
#Controller
#RequestMapping("/")
public class MainController {
#Autowired
private TaskService taskService;
#RequestMapping(method = RequestMethod.GET)
public String home(Model model){
model.addAttribute("mode", "MODE_HOME");
return "index";
}
#RequestMapping(method = RequestMethod.GET, value = "/all-tasks")
public String allTasks(Model model){
model.addAttribute("tasks", taskService.findAll());
model.addAttribute("mode", "MODE_TASKS");
return "index";
}
#RequestMapping(method = RequestMethod.GET, value = "/new-task")
public String newTask(Model model){
//List<Task> taskList = taskService.findAll();
model.addAttribute("mode", "MODE_NEW");
return "index";
}
#RequestMapping(method = RequestMethod.GET, value = "/save-task")
#PostMapping("/save-task")
public String saveTask(#ModelAttribute Task task, Model model) {
taskService.save(task);
model.addAttribute("tasks", taskService.findAll());
model.addAttribute("mode", "MODE_TASKS");
return "index";
}
#RequestMapping(method = RequestMethod.GET, value = "/update-task")
public String updateTask(#RequestParam int id, Model model){
model.addAttribute("tasks", taskService.findTask(id));
model.addAttribute("mode", "MODE_UPDATE");
return "index";
}
#RequestMapping(method = RequestMethod.GET, value = "/delete-task")
public String deleteTask(#RequestParam (required = false) int id, Model model){
taskService.delete(id);
model.addAttribute("tasks", taskService.findAll());
model.addAttribute("mode", "MODE_TASKS");
return "index";
}
}
Related
I am new to Java and I'm currently working on a project with springboot and thymeleaf template engineer. I am having issues making an HTTP POST request with nested Set Collection.
My Java Classes are
User Class
import java.util.Set;
public class User {
private String name;
private String school;
private String bestSubject;
private Set<Work> work;
*//constructors, getters and setters*
}
Work Class
public class Work {
private String companyName;
private String faxNumber;
private String location;
*//constructors, getters and setters*
}
Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
#Controller
public class UserController {
#Autowired
private UserService userService;
#GetMapping(value = "/user-register")
public String showUser(Model model) {
User users = new User();
model.addAttribute("users", userService.getUsersList());
return "userPage";
}
#PostMapping(value = "/user-details")
public String createUser(#ModelAttribute("users") User user){
System.out.println(user);
return "userResult";
}
}
Views
userPage.html
<form action="#" th:action="#{/user-details}" method="post" th:object="${users}">
<div class="row">
<div class="col-4">
<label for="name" class="col-form-label">Full Name:</label>
<input type="text" id="name" th:field="*{name}" class="form-control">
</div>
<div class="col-4">
<label for="school" class="col-form-label">School:</label>
<input type="text" id="school" th:field="*{school}" class="form-control">
</div>
<div class="col-4">
<label for="bestSubject" class="col-form-label">Best Subject:</label>
<input type="text" id="bestSubject" th:field="*{bestSubject}" class="form-control">
</div>
</div>
<!--BUTTON TO ADD AND DELETE FIELDS-->
<div class="row mt-4 mb-1">
<div class="col d-flex justify-content-between">
<div onclick="createField()" class="btn btn-primary float-start">Create row</div>
<div onclick="deleteField()" class="btn btn-danger float-end">Delete row</div>
</div>
</div>
<!--FIELDS LABEL-->
<div class="row">
<div class="col-4">
<label for="companyName" class="col-form-label">Company Name:</label>
</div>
<div class="col-4">
<label for="faxNumber" class="col-form-label">Fax Number:</label>
</div>
<div class="col-4">
<label for="location" class="col-form-label">Location:</label>
</div>
</div>
<table class="table table-borderless mt-0" id="userTable">
<tbody id="tableBody">
<tr th:each="user : ${users.work}">
<td>
<input type="text" id="companyName" th:field="*{user.companyName}" class="form-control">
</td>
<td>
<input type="text" id="faxNumber" th:field="*{user.faxNumber}" class="form-control">
</td>
<td>
<input type="text" id="location" th:field="*{user.location}" class="form-control">
</td>
</tr>
</tbody>
</table>
<button type="submit" class="btn btn-warning">
SUBMIT
</button>
</form>
UserResult.html
<p>Full Name: <span th:text="${users.name}"></span></p>
<p>University: <span th:text="${users.school}"></span></p>
<p>Best Subject: <span th:text="${users.bestSubject}"></span></p>
<table class="table table-striped .table-hover mt-1">
<thead>
<tr>
<th scope="col">Company Name</th>
<th scope="col">Fax Number</th>
<th scope="col">Address</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users.work}">
<td th:text="${user.companyName}">
</td>
<td th:text="${user.faxNumber}">
</td>
<td th:text="${user.location}">
</td>
</tr>
</tbody>
</table>
My QUESTION IS
I keep getting User{name='...', school='...', bestSubject='...', work { **'null**' }}
Instead of
{
name: '...'
school: '...'
work: {
company_name: '...',
fax_number: '...',
location: '...'
}
}
How do I ensure my Set collection returns the inputed data from the fields instead of null datatype?
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" media="all" th:href="#{/css/bootstrap.min.css}">
<title>Home</title>
</head>
<body class="p-3 mb-2 bg-light text-black">
<div class="container">
<div id="logoutDiv">
<h1 th:text="${'Welcome ' + name}">Name</h1>
<form action="#" th:action="#{/logout}"method="POST">
<button type="submit" class="btn btn-secondary float-right">Logout</button>
</form>
</div>
<div id="contentDiv" style="clear: right;">
<nav style="clear: right;">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-item nav-link active" id="nav-files-tab" data-toggle="tab" href="#nav-files" role="tab" aria-controls="nav-files" aria-selected="true">Files</a>
<a class="nav-item nav-link" id="nav-notes-tab" data-toggle="tab" href="#nav-notes" role="tab" aria-controls="nav-notes" aria-selected="false">Notes</a>
<a class="nav-item nav-link" id="nav-credentials-tab" data-toggle="tab" href="#nav-credentials" role="tab" aria-controls="nav-credentials" aria-selected="false">Credentials</a>
</div>
</nav>Upload<
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-files" role="tabpanel" aria-labelledby="nav-files-tab">
<p th:text="${message}" th:if="${message ne null}" class="alert alert-primary"></p>
<form action="#" enctype="multipart/form-data" th:action="#{'/file/uploadFile'}" th:method="POST" >
<div class="container">
<div class="row" style="margin: 1em;">
<div class="col-sm-2">
<label for="fileUpload">Upload a New File:</label>
</div>
<div class="col-sm-6">
<input type="file" class="form-control-file" id="fileUpload" name="fileUpload">
</div>
<div class="col-sm-4">
<button type="submit" class="btn btn-dark">/button>
</div>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped" id="fileTable">
<thead>
<tr>
<th style="width: 20%" scope="col"></th>
<th style="width: 80%" scope="col">File Name</th>
</tr>
</thead>
<tbody>
<tr th:each="file : ${files}">
<td>
<a target="_blank" class="btn btn-success" th:href="#{/file/{filename}(fileName = ${file.filename})}">View</a>
<a class="btn btn-danger" th:href="#{/file/delete{filename}(filename = ${file.filename})}" >Delete</a>
</td>
<th scope="row" th:text="${file.filename}" ></th>
</tr>
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
<button style="margin: 0.25em;" type="button" class="btn btn-info float-right" onclick="showNoteModal()">
+ Add a New Note
</button>
<div class="table-responsive">
<table class="table table-striped" id="userTable">
<thead>
<tr>
<th style="width: 20%" scope="col"></th>
<th style="width: 20%" scope="col">Title</th>
<th style="width: 60%" scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr th:each="note : ${notes}">
<td>
<button id="edit-note" type="button" class="btn btn-success"
th:attr="data-id=${note.getNoteId},
data-title=${note.getNoteTitle},
data-description=${note.getNoteDescription}"
onclick="showNoteModal (this.getAttribute('data-id'),this.getAttribute('data-title'),this.getAttribute('data-description'))">Edit/View</button>
<a id="delete-note" class="btn btn-danger" th:href="#{/note/delete/{noteId}(noteId = ${note.getNoteId()})}">Delete</a>
</td>
<th id="notetitle" scope="row" th:text="${note.getNoteTitle()}">Example Title</th>
<td id="notedescription" th:text="${note.getNoteDescription()}">Example Description </td>
</tr>
</tbody>
</table>
</div>
<div class="modal fade" id="noteModal" tabindex="-1" role="dialog" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="noteModalLabel">Note</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
-Here is the error that I get-
<form action="#" method="POST" th:action="#{/note/add}" th:object="${Notes}">
<input type="hidden" name="noteId" id="note-id">
<div class="form-group">
<label for="note-title" class="col-form-label">Title</label>
<input type="text" name= "noteTitle" class="form-control" id="note-title" maxlength="20" required th:field="*{noteTitle}">
</div>
<div class="form-group">
<label for="note-description" class="col-form-label">Description</label>
<textarea class="form-control" name="noteDescription" id="note-description" rows="5" maxlength="1000" required th:field="*{noteDescription}"></textarea>
</div>
<button id="noteSubmit" type="submit" class="d-none"></button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="$('#noteSubmit').click();">Save changes</button>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="nav-credentials" role="tabpanel" aria-labelledby="nav-credentials-tab">
<button style="margin: 0.25em;" type="button" class="btn btn-info float-right" onclick="showCredentialModal()">
+ Add a New Credential
</button>
<div class="table-responsive">
<table class="table table-striped" th:object="${credentials}" id="credentialTable">
<thead>
<tr>
<th style="width: 20%" scope="col"></th>
<th style="width: 35%" scope="col">URL</th>
<th style="width: 20%" scope="col">Username</th>
<th style="width: 25%" scope="col">Password</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<button type="button" class="btn btn-success">Edit</button>
<a class="btn btn-danger">Delete</a>
</td>
<th scope="row">Example Credential URL</th>
<td>Example Credential Username</td>
<td>Example Credential Password</td>
</tr>
</tbody>
</table>
</div>
<div class="modal fade" id="credentialModal" tabindex="-1" role="dialog" aria-labelledby="credentialModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="credentialModalLabel">Credential</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form action="#" method="POST">
<input type="hidden" name="credentialId" id="credential-id">
<div class="form-group">
<label for="note-title" class="col-form-label">URL</label>
<input type="text" name= "url" class="form-control" id="credential-url" maxlength="100" required>
</div>
<div class="form-group">
<label for="note-title" class="col-form-label">Username</label>
<input type="text" name= "username" class="form-control" id="credential-username" maxlength="30" required>
</div>
<div class="form-group">
<label for="note-title" class="col-form-label">Password</label>
<input type="text" name= "password" class="form-control" id="credential-password" maxlength="30" required>
</div>
<button id="credentialSubmit" type="submit" class="d-none"></button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="$('#credentialSubmit').click();">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="#{/js/jquery-slim.min.js}"></script>
<script th:src="#{/js/popper.min.js}"></script>
<script th:src="#{/js/bootstrap.min.js}"></script>
<!--For opening the note modal-->
<script type="text/javascript">
// For opening the note modal
function showNoteModal(noteId, noteTitle, noteDescription) {
$('#note-id').val(noteId ? noteId : '');
$('#note-title').val(noteTitle ? noteTitle : '');
$('#note-description').val(noteDescription ? noteDescription : '');
$('#noteModal').modal('show');
}
// For opening the credentials modal
function showCredentialModal(credentialId, url, username, password) {
$('#credential-id').val(credentialId ? credentialId : '');
$('#credential-url').val(url ? url : '');
$('#credential-username').val(username ? username : '');
$('#credential-password').val(password ? password : '');
$('#credentialModal').modal('show');
}
</script>
</body>
</html>
-I get an error when trying to parse the Note Form into my Note Controller. I try to parse my model object Notes in the form in my HTML home form into the Controller but it can not except the fields from the form. My first error is this:
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "home" - line 109, col 148), Line 109 is the fields from the form
I also get a second error: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'Notes' available as request attribute-
--- Note Controller ---
#Controller
#RequestMapping("note")
public class NoteController {
private NoteService noteServices;
private UserService userService;
public NoteController(NoteService noteServices, UserService userService) {
this.noteServices = noteServices;
this.userService = userService;
}
// Add an new note
#PostMapping("add")
public String addNote(#ModelAttribute(value="Notes")Notes Notes, Authentication authentication, Model model) throws IOException {
String userName = authentication.getName();
User user = userService.getUser(userName);
Integer userid = user.getId();
if(Notes.getNoteId() == null){
noteServices.addNote(Notes,userid);
}else{
noteServices.editNote(Notes);
}
return "result";
}
#GetMapping("/delete/{noteId:.+}")
public String deleteNote(#PathVariable Integer noteId, Authentication authentication, RedirectAttributes redirectAttributes){
noteServices.deleteNote(noteId);
redirectAttributes.addFlashAttribute("deleteNoteSuccess","Note deleted successfully.");
return "redirect:/result";
}
private Integer getUserId(Authentication authentication) {
String userName = authentication.getName();
User user = userService.getUser(userName);
return user.getId();
}
}
---
--- POJO Model ---
public class Notes {
private Integer noteId;
private String noteTitle;
private String noteDescription;
private Integer userId;
public Notes(String noteTitle, String noteDescription, Integer userId) {
this.noteTitle = noteTitle;
this.noteDescription = noteDescription;
this.userId = userId;
}
public Integer getNoteId() {
return noteId;
}
public void setNoteId(Integer noteId) {
this.noteId = noteId;
}
public String getNoteTitle() {
return noteTitle;
}
public void setNoteTitle(String noteTitle) {
this.noteTitle = noteTitle;
}
public String getNoteDescription() {
return noteDescription;
}
public void setNoteDescription(String noteDescription) {
this.noteDescription = noteDescription;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
I have this page where a teacher can invite a student to the platform. This works just fine, but now I would like to implement multiple invites on the same page. In short, there now is a form with the possibility to invite 1 student at a time, but I would like to expand this to 5 students in one post method.
This is the page to invite 1 student:
<div class="container">
<div class="wrapper">
<form class="form-activate" th:action="#{/invite}" method="post" th:object="${user}">
<h2 class="form-activate-heading" th:text="#{invite.title}">Nodig een student uit</h2>
<p th:text="#{invite.email}">Vul hier het e-mailadres in van de student die je wil uitnodigen:</p>
<div class="form-group">
<input type="text" name="email" id="email" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-secondary" value="Invite" th:attr="value=#{invite.enter}"/>
</div>
</div>
</form>
</div>
Now I tried modifying this page for 2 users:
<div class="container">
<div class="wrapper">
<form class="form-activate" th:action="#{/invite}" method="post">
<h2 class="form-activate-heading" th:text="#{invite.title}">Nodig een student uit</h2>
<p th:text="#{invite.email}">Vul hier het e-mailadres in van de student die je wil uitnodigen:</p>
<div class="form-group" th:object="${user}">
<input type="text" name="email" th:field="*{email}" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="form-group" th:object="${user}">
<input type="text" name="email" th:field="*{email}" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-secondary" value="Invite" th:attr="value=#{invite.enter}"/>
</div>
</div>
</form>
</div>
Then in my post method I tried to do it like this:
#RequestMapping(value="/invite", method = RequestMethod.POST)
public ModelAndView SendInvite(ModelAndView modelAndView, #ModelAttribute User user1, #ModelAttribute User user2, BindingResult bindingResult, RedirectAttributes redirectAttributes, HttpServletRequest request){
List<User> students = new ArrayList<>();
students.add(user1);
students.add(user2);
for (User user : students) {
But the page is not even loading anymore, here is my get method:
#RequestMapping(value="/invite", method = RequestMethod.GET)
public ModelAndView showInvitePage(ModelAndView modelAndView, #ModelAttribute User user){
return modelAndView;
}
Which gives me the following error:
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' (teacher/invite:20)] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
at org.springframework.web.servlet.support.BindStatus.(BindStatus.java:144)
Is it even possible what I'm trying to do ?
EDIT: I'm trying to implement to solution Aleksandrs has given me, but Spring throws an error:
#RequestMapping(value="/invite", method = RequestMethod.GET)
public ModelAndView showInvitePage(ModelAndView modelAndView){
List<String> emailList = new ArrayList<>();
modelAndView.addObject(emailList);
return modelAndView;
}
#RequestMapping(value="/invite", method = RequestMethod.POST)
public ModelAndView SendInvite(ModelAndView modelAndView, List<String> emailList){
System.out.println(emailList.get(0));
Eventhough I have instantiated the emailList in the Get method, I get the following error:
nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.List]: Specified class is an interface] with root cause
org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.List]: Specified class is an interface
EDIT2:
Finally got it working, thanks to Aleksandrs for the help! Solution:
#RequestMapping(value="/teacher/invite", method = RequestMethod.GET)
public ModelAndView showInvitePage(ModelAndView modelAndView){
logger.info("Entered showInvitePage function");
List<String> email = new ArrayList<>();
modelAndView.addObject("email", email);
return modelAndView;
}
#RequestMapping(value="/invite", method = RequestMethod.POST)
public ModelAndView SendInvite(ModelAndView modelAndView, #AuthenticationPrincipal User currentUser, #RequestParam List<String> email){
email.forEach(address -> {
// send the invites
});
And the invite page:
<form class="form-activate" th:action="#{/invite}" method="post" >
<p th:text="#{invite.email}">Vul hier het e-mailadres in van de student die je wil uitnodigen:</p>
</div>
<div class="form-group">
<input type="text" name="email" id="email" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="form-group">
<input type="text" name="email" id="email2" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="form-group">
<input type="text" name="email" id="email3" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-secondary" value="Invite" th:attr="value=#{invite.enter}"/>
</div>
</div>
</form>
As Pandian said the "user" binded twice but in this case you can use simple array to pass list of String into controller. Because the actual user must have many fields which isn't present in your template. So can recoment use something like this in template:
<form class="form-activate" th:action="#{/invite}" method="post">
<h2 class="form-activate-heading" th:text="#{invite.title}">Nodig een student uit</h2>
<p th:text="#{invite.email}">Vul hier het e-mailadres in van de student die je wil uitnodigen:</p>
<div class="form-group">
<input type="text" name="email[]" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="form-group">
<input type="text" name="email[]" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-secondary" value="Invite"
th:attr="value=#{invite.enter}"/>
</div>
</div>
</form>
And in the controller:
#RequestMapping(value = "/invite", method = RequestMethod.POST)
public ModelAndView SendInvite(List<String> emailAddressList) {
// some stuff here
}
EDIT:
So, I'm not sure that my JavaScript code works but in your case HTML must be likethis:
<form class="form-activate" th:action="#{/invite}" method="post" id="myForm">
<h2 class="form-activate-heading" th:text="#{invite.title}">Nodig een student uit</h2>
<p th:text="#{invite.email}">Vul hier het e-mailadres in van de student die je wil uitnodigen:</p>
<div class="form-group">
<input type="text" name="email[]" id="email" class="form-control input-lg" data-validation="email"
placeholder="Email" tabindex="2" th:attr="placeholder=#{general.email}"/>
<span style="cursor: pointer;"
onclick="myForm.innerHTML = myForm.innerHTML + '' + this.parentNode.cloneNode(true);">+</span>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-secondary" value="Invite"
th:attr="value=#{invite.enter}"/>
</div>
</div>
</form>
And in controller I'll try to do like that:
#RequestMapping(value = "/invite", method = RequestMethod.POST)
public ModelAndView SendInvite(ModelAndView modelAndView, #RequestParam List<String> email) {
email.stream()
.forEach(address -> {
User u = userService.findByEmail(address);
if (u == null) {
// send email here for current user;
}
});
// some other stuff
}
I can try to run it on my pc if you can tell me some test password.
Issues : same model "user" binded twice on same element with name "email"
Below Code working fine for me
<form action="#" th:action="#{/accessSite}" method="post">
<div th:object="${userVO}">
<table>
<tr>
<td>Name:</td>
<td><input type="text" th:field="*{name}" /></td>
<td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name
Error</td>
</tr>
</table>
</div>
<div th:object="${employeeVO}">
<table>
<tr>
<td>Name:</td>
<td><input type="text" th:field="*{empname}" /></td>
<td th:if="${#fields.hasErrors('empname')}" th:errors="*{empname}">Name
Error</td>
</tr>
</table>
</div>
Submit
#RequestMapping("/accessSite")
public String accessSite( UserVO userVO, EmployeeVO employeeVO,BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "form";
}
return "result";
}
On my view, I generate input "username", select with attribute multiple=multiple, which has name "rolesss".
My issue is, that if I send such form via post, my controller should convert roles to list, but i get only list containing single element.
For example:
I send post, with values:
username:MyUser
_csrf:aef50238-92cf-48df-86a4-cb6e2b8f62c9
rolesss:USER
rolesss:ADMIN
In debug mode in my controller I see values:
roless: "USER"
username: "MyUser"
"ADMIN" did just disappear.
My controller looks like:
#Controller
#RequestMapping("/user-management")
public class UserManagementController {
#RequestMapping("")
public ModelAndView home() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("pages/user-management");
return modelAndView;
}
#RequestMapping(value = "", method = RequestMethod.POST)
public ModelAndView changeRoles(#ModelAttribute("username") String username,#ModelAttribute("rolesss") List<String> rolesss) {
return null;
}
}
My view I merged 2 thymeleaf fragments into 1, in my code #user-roles-form is in separate fragment, but I think that it should not change anything:
<th:block layout:fragment="main-content">
<section>
<h2 class="section-title no-margin-top">Role Management</h2>
<div class="form-group">
<div class="panel panel-primary">
<div class="panel-heading">Změna rolí uživatele</div>
<div class="panel-body">
<form class="form-horizontal" role="form" action="/user-management" method="post">
<div class="form-group">
<label for="user-name" class="col-sm-2 control-label">Uživatel</label>
<div class="col-sm-10">
<input id="user-name" class="autocomplete form-control" type="text"
placeholder="Jméno uživatele" name="username"/>
</div>
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}"/>
</div>
<div id="user-roles-form" th:fragment="roles(roles)" xmlns:th="http://www.thymeleaf.org">
<div class="form-group">
<label for="user-roles" class="col-sm-2 control-label">Uživatelovy role</label>
<div class="col-sm-10">
<select multiple="multiple" class="form-control" id="user-roles" name="rolesss">
<th:block th:each="role : ${roles}">
<option th:value="${role.userRole.role}" th:text="${role.userRole.role}" th:selected="${role.selected}"></option>
</th:block>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-ar btn-primary">Uložit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
</th:block>
try using like below in your controller
in your HTML or JSP
<form modelAttribute="your_model_name"></form>
if you are using model attribute then use #ModelAttribute
otherwise, use #RequestParam("username")
In your case
#RequestMapping(value = "", method = RequestMethod.POST)
public ModelAndView changeRoles(#RequestParam("username") String username,#RequestParam("rolesss") List<String> rolesss) {
..........
.........
return null;
}
I need to bind an object with a list of objects as an attribute. It is a static list and it is created and partially filled in the Controller. It is displayed correctly in the view, however the value in the input field is not set when the form is sent to the controller. I did some research and found several similar questions, however none of the proposed solutions worked for me.
edit.jsp
<c:forEach items="${priceConfigurationForm.priceList}" var="price" varStatus="priceStatus">
<tr>
<td>
<spring:bind path="priceConfigurationForm.priceList[${priceStatus.index}].country">
${price.country}
<input type="hidden" name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>"/>
</spring:bind>
</td>
<td>
<spring:bind path="priceConfigurationForm.priceList[${priceStatus.index}].amount">
<input type="text" name="price" value="${price.amount}"/>
<input type="hidden"
name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>"/>
</spring:bind>
</td>
<td>
<spring:bind path="priceConfigurationForm.priceList[${priceStatus.index}].currency">
${price.currency}
<input type="hidden" name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>"/>
</spring:bind>
</td>
</tr>
</c:forEach>
Abstract from Controller populating the list:
#RequestMapping(value = "/price/create", method = RequestMethod.GET)
public String toCreatePriceConfiguratioView(Model model) {
log.info("::createPriceConfiguration: {}");
final PriceConfigurationForm priceConfigurationForm = new PriceConfigurationForm();
final List<PriceConfigurationForm.Price> prices = new ArrayList<>();
for (Country country : Country.values()) {
PriceConfigurationForm.Price price = new PriceConfigurationForm.Price();
price.setAmount(100);
price.setCountry(country.getCountryCode());
price.setCurrency(country.getCurrency());
prices.add(price);
}
priceConfigurationForm.setPriceList(prices);
model.addAttribute("priceConfigurationForm", priceConfigurationForm);
return "/secured/sources/onboarding/price/edit";
}
Abstract from Controller for storing the list:
#RequestMapping(value = "/price/create", method = RequestMethod.POST)
public String createPriceConfiguration(Model model, #ModelAttribute("priceConfigurationForm") #Valid PriceConfigurationForm priceConfigurationForm, BindingResult bindingResult, RedirectAttributes attributes) {
log.info("::createPriceConfiguration {}", priceConfigurationForm);
// TODO: Validate, before create
/* transform form to domain object */
PriceConfiguration configuration = PriceConfigurationForm.toPriceConfiguration(priceConfigurationForm);
onboardingApi.createPriceConfiguration(configuration);
attributes.addFlashAttribute("message", success("price configuration saved", priceConfigurationForm.getName()));
return "/secured/sources/onboarding/price/index";
}
Object:
#Data
public class PriceConfigurationForm {
private String name;
private String description;
private List<Price> priceList;
private Map<String, Long> countryToPriceMap;
public static PriceConfiguration toPriceConfiguration(PriceConfigurationForm form) {
final PriceConfiguration pc = new PriceConfiguration();
pc.setName(form.getName());
pc.setDescription(form.getDescription());
final Map<String, Long> prices = form.getPriceList().stream().collect(toMap(Price::getCountry, Price::getAmount));
pc.setCountryToPriceMap(prices);
return pc;
}
#Data
public static class Price {
private String country;
private long amount;
private String currency;
}
}
This is example of how I sucessfully parsed nested objects in one project :
<c:forEach var="block" items="${newplaceform.blocks}" varStatus="counter">
<fieldset data-block-order="${counter.index}" data-block-index="${counter.index}">
<c:if test='${block.type=="quizSingleAnswer"}'>
<legend><s:message code='blocks.type.quizSingleAnswer'/> <a class="glyphicon glyphicon-move move-block pull-right" href="#"></a><a data-remove="block" class="glyphicon glyphicon-remove pull-right" href="#"></a><a data-reordering="up" class="glyphicon glyphicon-chevron-up pull-right" href="#"></a><a data-reordering="down" class="glyphicon glyphicon-chevron-down pull-right" href="#"></a></legend>
<div class="form-group production-hide">
<label class="col-sm-3 control-label">id:</label>
<div class="col-sm-9"><input type="text" name="blocks[${counter.index}].id" value="${block.id}" data-row="blockId" data-disabled class="form-control"/></div>
</div>
<div class="form-group production-hide">
<label class="col-sm-3 control-label">type:</label>
<div class="col-sm-9"><input type="text" name="blocks[${counter.index}].type" value="${block.type}" data-disabled class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"><s:message code='blocks.description'/>:</label>
<div class="col-sm-9"><textarea class="form-control" name="blocks[${counter.index}].description" data-description rows="3">${block.description}</textarea></div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="col-sm-6 control-label">
<label class="inner-header"><s:message code='blocks.answers'/>:</label>
</div>
<div class="col-sm-6">
<div class="btn-group m-t-9">
<a class="btn btn-primary btn-xs" href="#" data-add-answer data-add-answer-block-index="${counter.index}"><i class="glyphicon glyphicon-plus"></i> <s:message code="blocks.add-answer" /></a>
</div>
</div>
</div>
</div>
<div class="row quiz-answers" data-count-answer="${block.answers.size()}">
<c:forEach var="answer" items="${block.answers}" varStatus="counterInner">
<div class="col-sm-6">
<fieldset>
<legend>
<div class="bootstrap-center">
<span><s:message code="blocks.answerNo"/> ${counterInner.index+1}</span>
<a data-remove="answer" class="glyphicon glyphicon-remove pull-right" href="#"></a>
</div>
</legend>
<div class="form-group production-hide">
<label class="col-sm-6 control-label">id:</label>
<div class="col-sm-6"><input type="text" name="blocks[${counter.index}].answers[${counterInner.index}].id" value="${answer.id}" data-row="answerId" data-disabled class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-6 control-label"><s:message code="blocks.answer"/>:</label>
<div class="col-sm-6"><input type="text" name="blocks[${counter.index}].answers[${counterInner.index}].text" value="${answer.text}" class="form-control"/></div>
</div>
<div class="form-group">
<div class="col-sm-6 col-sm-offset-6">
<div class="checkbox">
<label>
<input type="checkbox" name="blocks[${counter.index}].answers[${counterInner.index}].right" value="true" ${answer.checked()}/>
<s:message code="blocks.right"/>
</label>
</div>
</div>
</div>
</fieldset>
</div>
</c:forEach>
</div>