Spring MVC + Thymeleaf divs on the page are not generated - spring

I am new to Spring and I am trying to make web application using Spring MVC and Thymeleaf. I have the page with the form to create custom pizza and corresponding controller. User can enter the name of the pizza so I added input validation. Before I enter invalid name everything is ok, but then when I return a new view of the page, divs with my checkboxes do not appear, they just don't generate. It seems like my Model after refreshing is empty but I don't understand how I could debug this, my shortcuts in intellij idea just don't work.
CreatePizzaController:
#Slf4j
#Controller
#RequestMapping("/create")
public class CreatePizzaController {
#GetMapping
public String showCreationForm(Model model){
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("CLS22", "Classic base 22cm", Ingredient.Type.BASIC),
new Ingredient("CLS30", "Classic base 30cm", Ingredient.Type.BASIC),
new Ingredient("PEPE", "Pepperoni", Ingredient.Type.MEAT),
new Ingredient("HAM", "Ham", Ingredient.Type.MEAT),
new Ingredient("CHMPG", "Champignons", Ingredient.Type.VEGGIES),
new Ingredient("CUCU", "Cucumbers", Ingredient.Type.VEGGIES),
new Ingredient("PARM", "Parmesan cheese", Ingredient.Type.CHEESE),
new Ingredient("MOZ", "Mozzarella cheese", Ingredient.Type.CHEESE),
new Ingredient("BARBS", "Barbecue sauce", Ingredient.Type.SAUCE),
new Ingredient("TOMAS", "Tomato sauce", Ingredient.Type.SAUCE)
);
Ingredient.Type[] types = Ingredient.Type.values();
for(Ingredient.Type type: types){
model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
}
model.addAttribute("create", new Pizza());
return "create";
}
#PostMapping
public String processCreation(#Valid #ModelAttribute("create") Pizza pizza, BindingResult result){
if(result.hasErrors()){
return "create";
}
//Save creation...
log.info("Processing creation: " + pizza);
return "redirect:/orders/current";
}
private List<Ingredient> filterByType(List<Ingredient> ingredients, Ingredient.Type type) {
List<Ingredient> filtered = new ArrayList<>();
for (Ingredient ingredient:ingredients){
if(ingredient.getType() == type){
filtered.add(ingredient);
}
}
return filtered;
}
}
create.html:
<!DOCTYPE html>
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" th:href="#{/styles/style.css}" />
</head>
<body>
<h3>Create your pizza:</h3>
<img th:src="#{/images/pizza_logo.jpg}" width="200" height="200"/>
<form method="post" th:object="${create}">
<div class="grid">
<div class="ingredient-group" id="basics">
<h3>Choose the basic: </h3>
<div th:each="ingredient : ${basic}">
<input name="ingredients" type="radio" checked th:value="${ingredient.id}"/>
<span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="meat">
<h3>Choose meat: </h3>
<div th:each="ingredient : ${meat}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Choose vegetables: </h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauce">
<h3>Choose sauce: </h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheese">
<h3>Choose cheese: </h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div class="submit-button">
<h3>Name your pizza:</h3>
<input class="submit-field" type="text" th:field="*{name}" />
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Error</span>
<br/>
<button>Submit your pizza</button>
</div>
</form>
</body>
</html>
page before invalid input
pafe after invalid input
full code on github

Your problem is here:
#PostMapping
public String processCreation(#Valid #ModelAttribute("create") Pizza pizza, BindingResult result){
if(result.hasErrors()){
return "create";
}
//Save creation...
log.info("Processing creation: " + pizza);
return "redirect:/orders/current";
}
You need to add your model attributes again before returning "create". Thymeleaf isn't incorrect, the model is simply missing attributes and can't populate the checkboxes. Something like this should work:
#GetMapping
public String showCreationForm(Model model){
addCreationFormModelAttributes(model, new Pizza());
return "create";
}
#PostMapping
public String processCreation(Model model, #Valid #ModelAttribute("create") Pizza pizza, BindingResult result){
if(result.hasErrors()){
addCreationFormModelAttributes(model, pizza);
return "create";
}
//Save creation...
log.info("Processing creation: " + pizza);
return "redirect:/orders/current";
}
private static addCreationFormModelAttributes(Model model, Pizza pizza) {
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("CLS22", "Classic base 22cm", Ingredient.Type.BASIC),
new Ingredient("CLS30", "Classic base 30cm", Ingredient.Type.BASIC),
new Ingredient("PEPE", "Pepperoni", Ingredient.Type.MEAT),
new Ingredient("HAM", "Ham", Ingredient.Type.MEAT),
new Ingredient("CHMPG", "Champignons", Ingredient.Type.VEGGIES),
new Ingredient("CUCU", "Cucumbers", Ingredient.Type.VEGGIES),
new Ingredient("PARM", "Parmesan cheese", Ingredient.Type.CHEESE),
new Ingredient("MOZ", "Mozzarella cheese", Ingredient.Type.CHEESE),
new Ingredient("BARBS", "Barbecue sauce", Ingredient.Type.SAUCE),
new Ingredient("TOMAS", "Tomato sauce", Ingredient.Type.SAUCE)
);
Ingredient.Type[] types = Ingredient.Type.values();
for(Ingredient.Type type: types){
model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
}
model.addAttribute("create", pizza);
}

Related

Spring: Mapping two POST fields to three RESULT fields

In the post "Hello World" phase of my Spring journey. Creating an app that POSTS two fields which RESULT in three fields.
My controller has three fields. Two are populated on POST, and all three should populate the RESULT fields.
The Controller:
#Slf4j
#Controller
public class GreetController {
#GetMapping("/greeting")
public String greetingForm(Model model) {
model.addAttribute("greeting", new Greeting());
}
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting, Model model) {
model.addAttribute("greeting", greeting);
}
}
The Model:
public class Greeting {
//fields correspond to greeting.html
private long id;
private String content;
private String numbah;
public String getNumbah() { return numbah; }
public void setNumbah() {
this.numbah = SomeFunctions.functionOne(this.getContent());
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getContent() { return content; }
public void setContent(String content) { this.content }
}
The View:
<!DOCTYPE HTML>
this is greeting.html
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
</html>
Note:
The ${greeting.numbah} field, as configured above, is resulting in null. I had a log statement in that function that never returned anything, indicating that the function in the model is never firing. However, when the view was configured like this...
<body>
<h1>Form</h1>
<!--th.fields correspond to fields in th.object (ie greeting.java)-->
<form action="#" th:action="#{/greeting}" th:object="${greeting}" method="post">
<p>Id: <input type="text" th:field="*{id}" /></p>
<p>Message: <input type="text" th:field="*{content}" /></p>
<p>Some Number: <input type="text" th:field="*{numbah}"></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
<h1>Result</h1>
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<p th:text="'numbah: ' + ${greeting.numbah}" />
Submit another message
</body>
...the field was populating, so the function itself....functions.
Long short, what I'm attempting to do here is take id and content, do someFunction(content)=numbah and return id, content, and numbah.
Thank you in advance.
It always gets down to something simple, doesn't it?
There was a typo in the setNumbah constructor in the model.
This is why I hardly ever post on sites like this: because you almost always figure out what you did wrong if you just sleep on it and have another look.
Thanks for checking this out and apologies that it's not a "high quality" problem/answer. Will delete if that's the protocol for a self-answered question.

Why has my unobtrusive ajax stopped updating my partial view in ASP.NET Core 3.1?

I'm using unobtrusive ajax inside a kendo window to update a form once it's been filled in. The form should update the Id's of the DOM elements dynamically and also show the relative data inside the form as returned from the controller.
Let's start, here is the window element that houses the partial view which is a form:
#model Requirement
#{
int localId = Model.Id;
}
<div class="window-content">
<form method="post" data-ajax-url="/Home/Process_Requirement" data-ajax="true" data-ajax-method="post" data-ajax-loading="#spinner" data-ajax-update="#update-form-requirement" data-ajax-success="form_requirement_success">
<div id="update-form-requirement">
<partial name="_Form_Requirement" />
</div><!--/update-panel-->
<div class="window-footer">
<div class="container">
<div class="row no-gutters">
<div class="col">
<button type="submit" class="input-submit float-right">Save</button>
</div>
</div>
</div>
</div>
</form>
</div>
_Form_Requirement
#model Requirement
#{
int localId = Model.Id;
}
<div class="tab-content">
<div class="tab-pane fade show active" id="form-requirement-basic-#localId" role="tabpanel" aria-labelledby="requirement-tab">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" readonly />
<div class="container">
<div class="row">
<div class="col">
<div class="form-group">
<label>Vessel Type</label>
<kendo-dropdownlist name="VesselType"
for="VesselType"
id="VesselType_#localId"
datatextfield="TypeName"
datavaluefield="Id"
min-length="3"
style="width: 100%"
value-primitive="true"
option-label="Select vessel type"
footer-template="<button class='dropdown-button k-icon k-i-plus-outline' data-object-title='Add vessel type' data-object-function='_Window_Vessel_Type' onclick='open_data_window(this, event)'></button>"
filter="FilterType.Contains">
<datasource type="DataSourceTagHelperType.Ajax" page-size="80">
<transport>
<read url="/Vessel/ReadVesselTypes" />
</transport>
</datasource>
<popup-animation>
<open duration="300" effects="fadeIn" />
<close duration="300" effects="fadeOut" />
</popup-animation>
</kendo-dropdownlist>
</div>
</div>
</div>
</div>
</div>
</div>
When the form is submitted the controller action does the following:
/Home/Process_Requirement
public IActionResult Process_Requirement(Requirement model)
{
//Define the partial view we want
string modal = "_Form_Requirement";
//If the Id is 0 then create a new requirement
if (model.Id == 0)
{
if (ModelState.IsValid)
{
try {
_requirementService.InsertRequirement(model);
var getData = _requirementService.GetRequirement(model.Id);
return PartialView(modal, getData);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
else
{
try
{
_requirementService.UpdateRequirement(model);
return PartialView(modal, model);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
return PartialView(modal);
}
So, when you first open a window to create a new requirement the localId is 0 because you're not editing one, that's expected behavior.
When you then fill in all the details and submit the form, the data is posted to the database and saved correctly but the model doesn't seem to update with the new data, for example the input for the Id remains as 0:
<input type="text" id="Id_#localId" name="Id" asp-for="Id" readonly />
yet the id for the input updates to the newly created id number. Why is this happening and can I do anything about it?
you have a bug. fix the last return
return PartialView(modal,model);
Is the Id primary key? If so, it is not recommend to change the primary key, you should add a new column to be the key.
the data is posted to the database and saved correctly but the model doesn't seem to update with the new data
This could be caused by the concurrency issues. I can't see your update code now, but you can
try to change your update code like this:
public async Task<IActionResult> Process_Requirement(Requirement model)
{
string modal = "_Form_Requirement";
if (model.Id == 0)
{
//....
}
else //to update
{
await TryUpdateModelAsync<Requirement>(_context.Requirements.FirstOrDefault(x => x.RId ==model.RId),"",c => c.Id, c => c.CreatedUser);
_context.SaveChanges();
return PartialView(modal, model);
}
return PartialView(modal);
}
My Model:
public class Requirement
{
[Key]
public int RId { get; set; } //new column
public int Id { get; set; }
public string CreatedUser { get; set; }
}
And in partial, pass the key with a hidden field:
<input type="text" asp-for="RId" hidden/>
<input type="text" id="Id_#localId" name="Id" asp-for="Id" />
<input type="text" id="CreatedUser_#localId" asp-for="CreatedUser" />
Result:

How pass extra param from html to controller

Spring Boot 2.5, Thymeleaf
I need when click submit to pass object Product and additional extra param (quantity)
html template:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${appName}">Category template title</title>
<link th:href="#{/public/style.css}" rel="stylesheet"/>
<meta charset="UTF-8"/>
</head>
<body>
<div class="container">
<h3 th:text="*{title}"/>
<form method="post" action="#" th:object="${product}" th:action="#{/product}">
<input type="hidden" id="id" th:field="*{id}"/>
<input type="text" placeholder="Name" id="name" th:field="*{name}" th:disabled="${isView}"/>
<input type="hidden" id="created" th:field="*{created}"/>
<textarea placeholder="Description" rows="5" id="description"
th:field="*{description}" th:disabled="${isView}"></textarea>
<input type="number" placeholder="Price" id="price" th:field="*{price}" th:disabled="${isView}"/>
<input type="text" placeholder="Currency" id="currency" th:field="*{currency}" th:disabled="${isView}"/>
<input type="text" placeholder="Images URL(separate by comma)" id="images" th:field="*{images}" th:disabled="${isView}"/>
<input th:type="${isView} ? hidden : submit" value="Submit"/>
</form>
</div>
</body>
</html>
and here my controller:
#RequestMapping("cart/add")
public String addProduct(Model model) {
logger.info("addProduct");
model.addAttribute("isAdd", true);
model.addAttribute("product", new Product());
model.addAttribute("title", "Add Product");
model.addAttribute("viewMode", ViewMode.ADD);
return "product";
}
#PostMapping(value = "/product")
public String submitProduct(Product product, Model model) {
logger.info("submitProduct = " + product);
if (product.getId() == 0) { // add category
product.setCreated(new Date());
} else { // update category
product.setUpdated(new Date());
}
return "redirect:/cart";
}
So when click button Submit call submitProduct with fill object Product. But I need to pass extra param (as second param in method submitProduct) - quantity.
How I can pass this extra int param from html to controller?
One option is to access the value directly from the request parameters.
Assuming the quantity value is available in the form as an input field, with a name of quantity (looks like it is not there at the moment), then you can alter your controller to use this:
import org.springframework.web.bind.annotation.RequestParam;
And then change the relevant method signature to something like this:
public String submitProduct(Product product, Model model,
#RequestParam(name = "quantity") String quantity) {...}
(Field validation of some kind would also be needed, I assume.)

Pass a list of user defined object from input field using spring form handling

I have an Employee class that looks like this.
public class Employee {
private String Name;
private List<Address> address;
*****Getters and Setters****
}
And my Address class looks like this
public class Address {
private int addressid;
private Employee employee;
#NotNull(message="Field Cannot be Empty")
private String description;
*****Getters and Setters****
}
I want to bind List of addresses (Employee can have more than one address - 1:M) to the employee class with the data that is parsed through the form. Which looks like this.
I have 3 input address fields, one of the sample input fields look like this...
<div class="form-group">
<div class="row">
<label class="col-sm-3" for="exampleInputEmail1">Address
1</label>
<div class="col-sm-7">
<form:input class="form-control" placeholder="" path="" />
<form:errors path="" cssClass="error" />
</div>
</div>
</div>
Note that I have cut down many unnecessary form fields to demonstrate the problem more clearly. Please help me bind the list of input fields with the relevant class. Any advice for this design is also welcome.
You can use Thymeleaf for this as below.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>form|data</title>
</head>
<body>
<div class="starter-template">
<form th:action="#{employeeData}" method="post" th:object="${employee}">
<input th:field="*{name}" type="text" th:name="name" name="name" placeholder="name" class="input u-one-third js-get-form-data"/>
<th:block th:each="add,addStat:*{address}">
<input th:field="*{address[__${addStat.index}__].description}" type="text" th:name="add.description" name="description" placeholder="description" class="input u-one-third js-get-form-data"/>
</th:block>
<input type="submit" value="SEND"/>
</form>
</div>
</body>
</html>
Controller.
#RequestMapping(value = "/formData", method = RequestMethod.GET)
public String formData(Map<String, Object> model) {
Employee employee = new Employee();
Address address = new Address();
address.setDescription("TEST");
address.setEmployee(employee);
employee.getAddress().add(address);
model.put("employee",employee);
return "formData";
}
#RequestMapping(value = "/employeeData", method = RequestMethod.POST)
public void employeeData(#Valid Employee employeeData, BindingResult errors) {
System.out.println(employeeData.getName());
}
Please find the working commit here.

Spring Boot ModelAndView - cannot update value to Model

I am trying to create a form with 3 fields within the class LoginForm: usuario, senha, msgLogin.
I receive the fields usuário and senha from the input boxes and then I try to redirect to the same page with the same fields plus the field MsgLogin. But I've not been able to update the msgLogin field and I don't understand why.
These are the code:
HTML:
<form id="frmLogin" class="form col-md-12" action="#" th:action="#{/login}" th:object="${loginForm}" method="post">
<div class="form-group">
<label for="usuario" class="col-md-1">Usuário: </label>
<input type="text" id="usuario" placeholder="Email" th:field="*{usuario}"/>
</div>
<div class="form-group">
<label for="senha" class="col-md-1">Senha: </label>
<input type="password" id="senha" placeholder="senha" th:field="*{senha}"/>
</div>
<div class="row">
<button id="entrar">Entrar</button>
</div>
<div class="row">
<div id="msgLogin"></div>
<p th:text="${loginForm.msgLogin}" />
</div>
</form>
The Controller:
#RequestMapping("/")
public String init(#ModelAttribute LoginForm loginForm) {
Logger.getAnonymousLogger().info("Tela Inicial.");
return "home";
}
#PostMapping("/login")
public ModelAndView entrar(LoginForm loginForm) throws IOException {
Logger.getAnonymousLogger().info("Entrando no Internet Banking.");
service.login(usuario, senha);
ModelMap model = new ModelMap();
loginForm.setMsgLogin("I want to update that value!");
model.addAttribute("loginForm", loginForm);
return new ModelAndView("redirect:/", model);
}
Class using Lombok:
#Getter
#Setter
public class LoginForm {
private String usuario;
private String senha;
private String msgLogin;
}
A redirect send a 302 HTTP status back to the browser to resubmit using a new url, so it resubmits the same two fields to the new url. There's no expectation of payload data in the 302 response.
In your Controller, change the last line in your entrar method to: return new ModelAndView("/login", model);

Resources