I have a form for adding products written in HTML and thymeleaf.
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
Below the form, there is a button that adds two input fields to the form every time it's pressed. New input fields are the same as those two input fields above. The idea is that the user can enter data for as many products as he wants using the same form. For example, after pressing the button once the form will look like that:
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
The thing is I'd like to create ArrayList of class ProductForm using the values from input fields and then pass it to my controller using #ModelAttribute.
public class ProductForm{
private String name;
private String label;
//getters and setters
}
Then created a class that wraps ProductForm into ArrayList
public class ProductFormArray {
ArrayList<ProductForm> forms;
//getters and setters
}
And a Controller
#Controller
#RequestMapping(value = "/products")
public class CreateAccountControllerTemporary {
#RequestMapping(value = "/get", method = RequestMethod.POST)
public String createAccount(#ModelAttribute(name = "form")ProductFormArray form){
//some code
}}
My problem is that I can't figure out how to add objects to form ArrayList using values from input fields? Is that even possible? How should I change my HTML file?
It is certainly possible, I explain this on pages 361 to 389 in my book Taming Thymeleaf.
You can check out the sources of the book for free at https://github.com/wimdeblauwe/taming-thymeleaf-sources/tree/main/chapter16
It is hard to summarize 30 pages into a stackoverflow answer, but briefly, check out:
CreateTeamFormData.java: This is similar to your ProductFormArray class. I do use an array instead of an ArrayList.
public class CreateTeamFormData {
#NotBlank
#Size(max = 100)
private String name;
#NotNull
private UserId coachId;
#NotNull
#Size(min = 1)
#Valid
private TeamPlayerFormData[] players;
TeamPlayerFormData.java: This is similar to your ProductForm class.
public class TeamPlayerFormData {
#NotNull
private UserId playerId;
#NotNull
private PlayerPosition position;
TeamController.java: This the controller that uses the CreateTeamFormData.
#GetMapping("/create")
#Secured("ROLE_ADMIN")
public String createTeamForm(Model model) {
model.addAttribute("team", new CreateTeamFormData());
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values()); //<.>
return "teams/edit";
}
#PostMapping("/create")
#Secured("ROLE_ADMIN")
public String doCreateTeam(#Valid #ModelAttribute("team") CreateTeamFormData formData,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("editMode", EditMode.CREATE);
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values());
return "teams/edit";
}
service.createTeam(formData.toParameters());
return "redirect:/teams";
}
edit.html -> This is the Thymeleaf template. Note that I am using a Thymeleaf fragment edit-teamplayer-fragment for the part of the form that repeats itself (So the name and label fields in your case)
<h3>Players</h3>
<div class="col-span-6 ml-4">
<div id="teamplayer-forms"
th:data-teamplayers-count="${team.players.length}"> <!--.-->
<th:block th:each="player, iter : ${team.players}">
<div th:replace="teams/edit-teamplayer-fragment :: teamplayer-form(index=${iter.index}, teamObjectName='team')"></div>
<!--.-->
</th:block>
</div>
<div class="mt-4">
<a href="#"
class="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
id="add-extra-teamplayer-form-button"
th:text="#{team.player.add.extra}"
#click="addExtraTeamPlayerForm()"
></a> <!--.-->
</div>
</div>
edit-teamplayer-fragment.html: Here is the important part where you need to keep track of the index for each fragment:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
lang="en">
<!-- tag::main[] -->
<div th:fragment="teamplayer-form"
class="col-span-6 flex items-stretch"
th:id="${'teamplayer-form-section-' + __${index}__}"
th:object="${__${teamObjectName}__}"> <!--.-->
<!-- end::main[] -->
<div class="grid grid-cols-1 row-gap-6 col-gap-4 sm:grid-cols-6">
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].playerId}">
<option th:each="user : ${users}"
th:text="${user.userName.fullName}"
th:value="${user.id.asString()}">
</select>
</div>
</div>
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].position}">
<option th:each="position : ${positions}"
th:text="#{'PlayerPosition.' + ${position}}"
th:value="${position}">
</select>
</div>
</div>
<!-- tag::delete[] -->
<div class="ml-1 sm:col-span-2 flex items-center text-green-600 hover:text-green-900">
<div class="h-6 w-6">
<svg th:replace="trash"></svg>
</div>
<a href="#"
class="ml-1"
th:text="#{team.player.remove}"
x-data
th:attr="data-formindex=__${index}__"
#click="removeTeamPlayerForm($el.dataset.formindex)"> <!--.-->
</a>
</div>
<!-- end::delete[] -->
</div>
</div>
</html>
Related
I'm unable to fetch the values from Controller(Home.java) to View (home_view.jsp) in Spring Boot.
home.jsp
<body>
<div class="container mt-10">
<form action="processHome" method="post">
<div class="form-group">
<label for="emp_Id">Enter Employee Id</label>
<input type="number" placeholder="Enter Id" name="id">
</div>
<div class="form-group">
<label for="Name">Enter Employee Name</label>
<input type="text" placeholder="Enter Name" name="name">
</div>
<div class="form-group">
<label for="Password">Enter Employee Password</label>
<input type="text" placeholder="Enter Password" name="password">
</div>
<button type="submit">Submit</button>
</form>
Home.java
#Controller
public class Home {
#RequestMapping("home")
public String index() {
return "home";
}
#RequestMapping(path="processHome",method=RequestMethod.POST)
public String homeForm(#ModelAttribute Employee emp, Model model) {
System.out.println(emp); // Here in 'emp' I'm getting the values in my console
return "home_view";
}
}
Employee.java
public class Employee {
private int id;
private String name;
private String password;
//getters & setters
//toString()
}
home_view.jsp
<body>
Model : ${emp.id} ${emp.name} ${emp.password}
</body>
OUTPUT
I am successfully redirected to the home_view.jsp but unable to fetch data. As in my console, I'm getting the values from the View and when I am using ModelAndView then also I am getting those values but when I'm using #ModelAttribute then only data isn't fetching into the view.
I've got the data on views but really not sure that Model object name is creating such differences
public String homeForm(#ModelAttribute Employee employee, Model model)
I am creating an Spring Boot App, in which the user can create licences. The user can type the name, purchaseDate, renewalDate and expirationDate.
My problem is, that when I try to save the data, it returns null for the name, the purchaseDate, renewalDate and expirationDate.
I absolutely don't know what to do anymore, please help. The "// Comment" values in the Model:Licence are those who get null when executing the post.
Model: Licence
#Entity
#Table(name = "TBL_LICENCES")
public class Licence {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String licenceName; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String purchaseDate; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String renewalDate; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String expirationDate; // --> null after the post
public Licence() {}
public Licence(Long id, String licenceName, String purchaseDate, String renewalDate, String expirationDate) {
Id = id;
this.licenceName = licenceName;
this.purchaseDate = purchaseDate;
this.renewalDate = renewalDate;
this.expirationDate = expirationDate;
}
// Getter and Setter
}
Model: LicenceRepository
#Repository
public interface LicenceRepository extends CrudRepository<Licence, Long> { }
Service: LicenceService
#Service
public class LicenceService {
#Autowired
private LicenceRepository licenceRepository;
public List<Licence> getLicences() {
return (List<Licence>) licenceRepository.findAll();
}
public Optional<Licence> getLicenceById(Long id) {
return licenceRepository.findById(id);
}
public void addLicence(Licence licence) {
licenceRepository.save(licence);
}
public void updateLicence(Licence licence) {
licenceRepository.save(licence);
}
public void deleteLicenceById(Long id) {
licenceRepository.deleteById(id);
}
}
Controller: LicenceController
#Controller
public class LicenceController {
#Autowired
private LicenceService licenceService;
#GetMapping("/licences")
public String getLicences(Model model) {
model.addAttribute("licences", licenceService.getLicences());
return "licences";
}
#GetMapping("/onelicence")
#ResponseBody
public Optional<Licence> getLicenceByID(Long id, Model model) {
model.addAttribute("onelicence", licenceService.getLicenceById(id));
return licenceService.getLicenceById(id);
}
#RequestMapping(value="/save", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.GET})
public String updateLicence(Licence licence) {
licenceService.updateLicence(licence);
return "redirect:/licences";
}
**// Is performed, when clicking "New Licence > Save"**
#RequestMapping(value="/addNew", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.GET})
public String addLicence(Licence licence) {
licenceService.addLicence(licence);
return "redirect:/licences";
}
#RequestMapping(value="/delete", method = {RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.GET})
public String deleteLicence(Long id) {
licenceService.deleteLicenceById(id);
return "redirect:/licences";
}
}
licences.html
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/html"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<script type ="text/javascript" src="webjars/jquery/3.4.1/jquery.min.js"></script>
<script type ="text/javascript" src="webjars/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<link href="webjars/bootstrap/4.4.1/css/bootstrap.css" rel="stylesheet"/>
<title>Licence</title>
</head>
<body>
<div class = "container">
<h2>Licences</h2>
<table class = "table table-striped">
<thead>
<tr>
<td>ID</td>
<td>Name</td>
<td>Kaufdatum</td>
<td>Erneuerungsdatum:</td>
<td>Auslaufdatum:</td>
</tr>
</thead>
<tbody>
<tr th:each = "licence: ${licences}">
<td th:text="${licence.id}">ID</td>
<td th:text="${licence.licenceName}">Name</td>
<td th:text="${licence.purchaseDate}">Kaufdatum</td>
<td th:text="${licence.renewalDate}">Erneuerungsdatum</td>
<td th:text="${licence.expirationDate}">Auslaufdatum</td>
<td><a class="btn btn-warning">Edit</a></td>
<td><a class="btn btn-danger">Delete</a></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addModal" data-whatever="#mdo">New Licence</button>
</div>
// Bootstrap Varying Modal Content
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<form th:action="#{/addNew}" method ="post">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">New Licence</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name" class="col-form-label">Lizenz Name</label>
<input type="text" class="form-control" id="licenceName" name="name">
</div>
<div class="form-group">
<label for="purchase" class="col-form-label">purchaseDate</label>
<input type="date" class="form-control" id="purchaseDate" name="purchase">
</div>
<div class="form-group">
<label for="renewalAdd" class="col-form-label">renewalDate</label>
<input type="date" class="form-control" id="renewalAdd" name="renewal">
</div>
<div class="form-group">
<label for="expirationAdd" class="col-form-label">expirationDate</label>
<input type="date" class="form-control" id="expirationAdd" name="expiration">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
</body>
</html>
schema.sql
DROP TABLE IF EXISTS TBL_LICENCES;
CREATE TABLE TBL_LICENCES (
id INT AUTO_INCREMENT PRIMARY KEY,
licence_name VARCHAR(250),
purchase_date VARCHAR(250),
renewal_date VARCHAR(250),
expiration_date VARCHAR(250)
);
data.sql --> This works and the data is shown.
INSERT INTO
TBL_LICENCES (licence_name, purchase_date, renewal_date, expiration_date)
VALUES
('Test1', '2020-01-31', '2020-06-31', '2020-12-31'),
('Test', '2021-01-31', '2021-06-31', '2021-12-31');
Properties: application.properties
spring.h2.console.enabled=true
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:<dbLicences>
spring.jpa.hibernate.ddl-auto=update
The name attribute of the input control should match the name of the fields in the Licence class. Currently your id attribute is matching the name of the fields, but when a form is submitted it uses the name attribute to build the request parameters.
Update your HTML to match something like:
<input type="text" class="form-control" id="licenceName" name="licenceName" />
Fix the name for other fields and you will have the Licence object populated with data from the form.
Also, I see that
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String licenceName;
licenceName is annotated with #DateTimeFormat. I guess this is a mistake, please correct that as well.
I have tried to solve the problem since 3 days now, but I can´t.
Actually I want to update a row, but instead of the update the .merge() makes a insert.
The Id is autogenerated from the mySql database.
This is the first button to call the formular, with the id from the prior persisted Report:
<a th:href="#{|/reports/updateForm/${reportId}|}">
<button class="button btn-default btn-xs pull-right" type="button" th:title="#{report.formEdit}">
<i class="fa fa-edit fa-fw"></i></button></a>
This is the aspect which should call the view updateformular:
#RequestMapping(value = "/updateForm/{id}", produces = "text/html", method = RequestMethod.GET)
public String ReportController.updateForm(#PathVariable("id") long id, Model model) {
CRMReport newCRMReport = CRMReport.findCRMReport(id);
model.addAttribute("newCRMReport", newCRMReport);
return "reports/update";
}
Excerpt of view update:
<form action="#" class="form" role="form"
th:object="${newCRMReport}" th:action="#{/reports/update/}"
th:method="put">
<div class="modal-body col-lg-6 form-left">
<div class="panel panel-info">
<div class="panel-heading">
<i th:text="#{report.info}"></i>
</div>
</div>
//Example of the send data fields
<div class="form-group" th:classappend="${#fields.hasErrors('projectState')} ? error">
<label for="projectState" th:text="#{report.projectstatus}"></label>
<select class="form-control" id="projectState" th:field="*{projectState}"
th:size="${pros.length}" multiple="multiple">
<option th:each="pro : ${pros}" th:value="${{pro}}" th:field="*{projectState}" th:text="${pro}"></option>
</select>
</div>
<div class="form-group">
<div>
<input type="hidden" th:field="*{id}" class="form-control" id="id"/>
</div>
</div>
<div class="modal-footer col-lg-12">
<button name="action" value="cancel" type="submit"
class="btn btn-default pull-right">
<i class="fa fa-times fa-fw"></i>
<i th:text="' '+#{report.cancel}"></i>
</button>
<button name="action" value="save" type="submit"
class="btn btn-primary pull-right">
<i class="fa fa-save fa-fw"></i>
<i th:text="' '+#{report.show}"></i>
</button>
</div>
The last thing shoud be an merge(update) of the previus send object newCRMReport:
#RequestMapping(value = "/update", produces = "text/html", method = RequestMethod.PUT, params = "action=save")
public String ReportController.updateReport(#Valid #ModelAttribute("newCRMReport") CRMReport newCRMReport,
BindingResult result, Model model, SessionStatus status) {
// ID will be x, from the prior object
System.out.println("id before: " + newCRMReport.getId());
status.setComplete();
newCRMReport = newCRMReport.merge();
// ID will be x+1, from the new object
System.out.println("id then: " + newCRMReport.getId());
return "redirect:/reports/list/" + newCRMReport.getId();
}
This is the object to update:
#RooJavaBean
#RooToString
#RooJpaActiveRecord(table = "crm_report")
public class CRMReport{
private String projectState;
private String sector;
private String location;
private String client;
private String company;
#JoinColumn
#ManyToOne
private CRMUser responsible;
private String relevance;
private String volumeFrom;
private String volumeTo;
private String chance;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss")
private Date dateFrom;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss")
private Date dateTo;
private String timeSpanFrom;
private String timeSpanTo;
#JoinColumn
#ManyToOne
private CRMUser createdBy;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date createdAt;
}
Can you please help me? Do you know why it performs an insert instead of update it?
Greetings
Loopek (:
I think the problem might be related to this block:
<div class="form-group">
<div>
<input type="hidden" th:field="*{id}" class="form-control" id="id" th:value="${{reportId}}"/>
<span class="help-inline" th:errors="*{id}">[error]</span>
</div>
</div>
First of all, for a hidden input field you won't show any validation error messages, so remove everything but the hidden input field itself.
Next, you are making an association between the input field and the id property of the newCRMReport object instance using the th:field="*{id}" attribute. But you are using also the th:value="${{reportId}} attribute, with a value (reportId) which I think is not available in the view model. That will end with a null or empty value in the id field.
Try replacing the previous block with the following one. Then check if the generated HTML is valid and the hidden field contains the id value.
<input type="hidden" th:field="*{id}" />
Thank you all for your comments and answers. With your annotations I found out the answer.
I have now added the version, with the type:"hidden" to the update view.
<div class="form-group">
<div>
<input type="hidden" th:field="*{version}"
class="form-control" id="version"/>
</div>
</div>
The merge then performs an update.
I'm creating a simple CRUD in Spring Boot+Thymeleaf and all other CRUD on application work well but this one. When I submit the form, the object comes with empty attributes to controller.
In the following, the code:
Artist.java
#Entity
public class Artist {
#Id
#GeneratedValue
private Long id;
#Column(nullable=false, length = 80)
private String artistName;
<getters and setters>
ArtistController.java
#Controller
public class ArtistController {
#Autowired
ArtistRepository artistRepository;
#GetMapping("/artist/add")
public String addArtist(Model model) {
model.addAttribute("artist", new Artist());
return "artist/form_artist";
}
#PostMapping("/artist/save")
public String artistSave(#ModelAttribute(name="artist") Artist artist) {
artistRepository.save(artist);
return "redirect:/artist";
}
}
And now, the form:
<form action="#" th:action="#{/artist/save}" th:object="${artist}" method="post" class="form-horizontal">
<div class="form-group">
<label for="id" >HobbyOn id:</label>
<input type="text" id="id" th:field="*{id}" readonly="readonly" class="form-control" placeholder="hobbyOn ID" />
</div>
<div class="form-group">
<label for="artistName" >Artist Name:</label>
<input type="text" th:field="*{artistName}" class="form-control" required="required" />
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" value="Submit" />
</div>
</form>
I can't see any difference from examples in internet and in my own code. Any help will be very welcomed.
I have this form and want to process it:
<form id="myForm" class="form-horizontal" action="/user" method="post">
<fieldset>
<div class="control-group">
<!-- Text input-->
<label class="control-label" for="input01">Email:</label>
<div class="controls">
<input name="email" placeholder="email" class="input-xlarge" type="text"
value="<%=?????????">
</div>
</div>
<div class="control-group">
<!-- Text input-->
<label class="control-label" for="input01">Password:</label>
<div class="controls">
<input name="password" placeholder="password" class="input-xlarge" type="text"
value="<%=request.getParameter("password")%>">
</div>
</div>
</fieldset>
</form>
</div>
<div class="modal-footer">
Close
<input class="btn btn-primary" type='button' value='Save Changes'
onclick='document.forms["myForm"].submit();'>
</div>
</div>
However, I have a bean method with two parameters and I tried to handle this by using:
public void insert(String email, String password) {
User entry = new User(email, password);
PersistenceManager pm = PMF.get().getPersistenceManager();
pm.makePersistent(entry);
}
My question is, how to connect the Bean properly with the form, which uses two parameters?
Have you created the bean java file to connect those two? A functional bean file consists of the following three parts:
A constructor working as initialization
Getters
Setters
In the following lines, you will find a plain example concerning a form based on the one you posted above. Remember the old trick of System.out.println's to check everything you are writing is right.
Bean file , let's call it userData.java
package blabla;
import java.io.Serializable;
public class UserData implements Serializable {
private String email;
private String password;
//1.Constructor
public UserData()
{
email="";
password="";
}
//2.Getters
public String getEmail(){
return email;
}
public String getPassword(){
return password;
}
//3.Setters - Caution: we use different variables here
public void setEmail(String eml)
{
this.email=eml;
}
public void setPassword(String pswd)
{
this.password=pswd;
} }
On the other hand, the .jsp file should resemble like this
index.jsp
<%--Blablabla headers and stuff, I will only write the things needing to be added--%>
<jsp:useBean id="entry" class="packagePath.UserData" scope="application"/>
<%--For analytic info about the parameters of the jsp call above, check: http://www.javatpoint.com/jsp-useBean-action--%>
<jsp:setProperty name="entry" property="*"/>
<%--Code to invoke all the setters of the bean--%>
<p><jsp:getProperty name="entry" property="email"/></p>
<p><jsp:getProperty name="entry" property="password"/></p>
<%--The two lines are not obligatory to use; you will need them only if you wan tto print the results on the page--%>
<%-- rest of the code%-->