How can I do calculation based on other fields in thymeleaf? - spring

I am working with Spring boot, Spring-data, ThymeLeaf. I have some fields. "Passenger Name", "Age", "Source", "Destination", "No of tickets", "Ticket Price", "Discount".
The following html code:
<div class="form-group has-error has-feedback" data-z="1b5278b0" id="passengerName1" data-th-classappend="${#fields.hasErrors('passengerName')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="passengerName" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_passengerName}">passengerName</label>
<div class="col-md-3">
<input id="passengerName" name="passengerName" data-th-value="*{{passengerName}}" type="text" class="form-control inputmask" placeholder="passengerName" data-th-placeholder="#{label_ticketbooking_passengerName}" data-toggle="tooltip" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="1b5278b0" id="age-field" data-th-classappend="${#fields.hasErrors('age')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="age" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_age}">age</label>
<div class="col-md-3">
<input id="age" name="age" data-th-value="*{{age}}" type="text" class="form-control inputmask" placeholder="age" data-th-placeholder="#{label_ticketbooking_age}" data-toggle="tooltip" data-inputmask-alias="numeric" data-inputmask-digits="0" min="1" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="1b5278b0" id="source1" data-th-classappend="${#fields.hasErrors('source')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="source" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_source}">source</label>
<div class="col-md-3">
<input id="source" name="source" data-th-value="*{{source}}" type="text" class="form-control inputmask" placeholder="source" data-th-placeholder="#{label_ticketbooking_source}" data-toggle="tooltip" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="1b5278b0" id="destination1" data-th-classappend="${#fields.hasErrors('destination')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="destination" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_destination}">destination</label>
<div class="col-md-3">
<input id="destination" name="destination" data-th-value="*{{destination}}" type="text" class="form-control inputmask" placeholder="destination" data-th-placeholder="#{label_ticketbooking_destination}" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="1b5278b0" id="noOfTickets-field" data-th-classappend="${#fields.hasErrors('noOfTickets')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="noOfTickets" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_noOfTickets}">noOfTickets</label>
<div class="col-md-3">
<input id="noOfTickets" name="noOfTickets" data-th-value="*{{noOfTickets}}" type="text" class="form-control inputmask" placeholder="noOfTickets" data-th-placeholder="#{label_ticketbooking_noOfTickets}" data-toggle="tooltip" aria-describedby="noOfTicketsStatus" data-inputmask-alias="numeric" data-inputmask-digits="0" min="1" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="ed99c550" id="ticketPrice-field" data-th-classappend="${#fields.hasErrors('ticketPrice')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="ticketPrice" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_ticketPrice}">ticketPrice</label>
<div class="col-md-3">
<input id="ticketPrice" name="ticketPrice" data-th-value="*{{ticketPrice}}" type="text" class="form-control inputmask" placeholder="ticketPrice" data-th-placeholder="#{label_ticketbooking_ticketPrice}" data-toggle="tooltip" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="d1a1d590" id="ticketdiscount-field" data-th-classappend="${#fields.hasErrors('ticketDiscount')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="ticketDiscount" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_ticketdiscount}">ticketDiscount</label>
<div class="col-md-3">
<input id="ticketDiscount" name="ticketDiscount" data-th-value="*{{ticketDiscount}}" type="text" class="form-control inputmask" placeholder="ticketDiscount" data-th-placeholder="#{label_ticketbooking_ticketdiscount}" data-toggle="tooltip" data-inputmask-alias="numeric" data-inputmask-digits="0" />
</div>
</div>
<div class="form-group has-error has-feedback" data-z="ed99c550" id="totalPrice-field" data-th-classappend="${#fields.hasErrors('totalPrice')}? 'has-error has-feedback'" data-th-class="form-group">
<label for="totalPrice" class="col-md-3 control-label" data-th-text="#{label_ticketbooking_totalPrice}">totalPrice</label>
<div class="col-md-3">
<input id="totalPrice" name="totalPrice" data-th-value="*{{totalPrice}}" type="text" class="form-control inputmask" placeholder="totalPrice" data-th-placeholder="#{label_ticketbooking_totalPrice}" data-toggle="tooltip" />
</div>
</div>
Here totalPrice should be calculated based on totalPrice = (noOfTickets * ticketPrice ) - ticketDiscount
Note: ticketDiscount may applicable or not. If applicable need to minus else no need to subtract it.
How can I achieve this?

There are several things you should take into account and several approaches you can use. Lets simplify things a little bit and suppose you have
Form DTO
#Data
public class TestDto {
private int ticketPrice;
private int noOfTickets;
private int ticketDiscount;
}
and Controller
#Controller
public class TestController {
#RequestMapping(name = "/", method = RequestMethod.GET)
public ModelAndView get() {
TestDto dto = new TestDto();
dto.setNoOfTickets(10);
dto.setTicketPrice(12);
return new ModelAndView("main", "dto", dto);
}
#RequestMapping(name = "/", method = RequestMethod.POST)
public String post(#ModelAttribute("dto") TestDto dto) {
System.out.println(dto);// can process input values
return "main";
}
}
Important. I assume you haveth:object="${dto}" in you form. If you don't, then just use dto.fieldName instead of fieldName like dto.ticketPrice instead of ticketPrice and $ instead of *
Option 1. Use Thymeleaf syntax. totalPrice will change after each form submit (POST request)
<form action="/" th:object="${dto}" method="post">
<input type="number" th:id="ticketPrice" th:field="*{ticketPrice}"/>
<input type="number" th:id="noOfTickets" th:field="*{noOfTickets}"/>
<input type="number" th:id="ticketDiscount" th:field="*{ticketDiscount}"/>
<span th:text="*{noOfTickets * ticketPrice - (ticketDiscount != 0 ? ticketDiscount: 0)}"
th:id="totalPrice"/>
<input type="submit" value="Subscribe!"/>
</form>
Option 2. Calculate value in java code POST changes to the server is also required to update the total value. Simple case can just use method with all the calculations in your DTO. This option works only if you have all the info for your calculations in this DTO
public class TestDto {
// ... same code as before
public int getTotalPrice() {
return noOfTickets * ticketPrice - (ticketDiscount != 0 ? ticketDiscount: 0);
}
}
This is easy to use just like any other field in your dto
<span th:text="*{totalPrice}"></span>
<span th:text="${dto.totalPrice}"></span>
<span th:text="*{getTotalPrice()}"></span>
If you need some extra info for your calculations, you probably can use service as suggested by #mrtasln. And for our simple case it can look like:
#Service("myService")
public class MyServices {
// Option 1
public int calculateTotal(MyDto dto){
return dto.getNoOfTickets() * dto.getTicketPrice() - (dto.getTicketDiscount() != 0 ? dto.getTicketDiscount(): 0);
}
// Option 2
public int calculateTotal2(int noOfTickets, int ticketPrice, int ticketDiscount){
return noOfTickets * ticketPrice - (ticketDiscount != 0 ? ticketDiscount: 0);
}
}
And xml part can be something like one of:
<span th:id="totalPriceFromService"
th:text="${#myService.calculateTotal(dto)}"></span>
<span th:id="totalPriceFromService2"
th:text="*{#myService.calculateTotal2(ticketPrice, noOfTickets, ticketDiscount)}"></span>
<span th:id="totalPriceFromService2"
th:with="tp=*{ticketPrice},nt=*{noOfTickets},td=*{ticketDiscount}"
th:text="${#myService.calculateTotal2(tp, nt, td)}"></span>
Option 3. The Javascript way is the only dynamic option to calculate changes. No need to perform POST to update the total value.
You can use some library to help you there, but simple case should
Define some JavaScript function like calculateTotal()
Put oninput="calculateTotal()" attribute on each input field you want to listen
Something like this:
<form action="/" th:object="${dto}" method="post">
<input type="number" th:id="ticketPrice" th:field="*{ticketPrice}" oninput="calculateTotal()"/>
<input type="number" th:id="noOfTickets" th:field="*{noOfTickets}" oninput="calculateTotal()"/>
<input type="number" th:id="ticketDiscount" th:field="*{ticketDiscount}" disabled="disabled"/>
<span th:id="totalPriceJS"></span>
<input type="submit" value="Subscribe!"/>
</form>
<script type="text/javascript">
function calculateTotal() {
var price = document.getElementById("ticketPrice").value;
var quantity = document.getElementById("noOfTickets").value;
var discount = document.getElementById("ticketDiscount").value;
var totalInput = document.getElementById("totalPriceJS");
//do all the calculations here
var total = price * quantity
if (discount) total -= discount;
totalInput.innerHTML = total
}
calculateTotal(); // don't forget to call this function on the first run
</script>

Try this calculation:
<div th: "${ticketDiscount !=null} ? result=${noOfTickets * totalPrice - ticketDiscount } : result=${noOfTickets * totalPrice}">
<span th:text="${result}"></span>
</div>
Here I am checking the value of ticketdiscount is null or not.

In Spring , you can do it with using service.Example code below :
Service class :
#Component("calculateService")
public class CalculateService {
public Integer calculateTotalPrice(Integer noOfTickets ,Integer ticketPrice, Integer ticketDiscount ){
return (noOfTickets * ticketPrice ) - ticketDiscount;
}
}
Html File :
<span th:text="${#calculateService.calculateTotalPrice(noOfTickets ,ticketPrice ,ticketDiscount)}"></span>

Related

Model attribute inconsistent after form submission

I'm still trying to figure out what's happening on the backend here. Working with Spring Boot/Thymeleaf. I have a form template that updates a model attribute object on save. I'm trying to use the updated values on the backend, however it's inconsistent among my functions and I'm not sure why.
Thymeleaf template
<div layout:fragment="content" class="container">
<form action="#" th:action="#{/utility/exportbadges}" th:object="${badgeExport}" method="post">
<div class="form-group col-md-6">
<label class="col-form-label-sm">Badge type</label>
<select th:field="*{type}" class="form-control">
<option th:each="badgeType : ${T(org.myorg.myproject.model.badge.BadgeType).values()}"
th:value="${badgeType}"
th:text="${badgeType}">
</option>
</select>
</div>
<div class="form-group col-md-3">
<label class="col-form-label-sm">Use background?</label>
<input type="checkbox" th:field="*{background}" class="form-control">
</div>
<div class="form-group col-md-3">
<label class="col-form-label-sm">Mark preprinted?</label>
<input type="checkbox" th:field="*{preprinted}" class="form-control">
</div>
<div class="form-group col-md-3">
<label class="col-form-label-sm">Save to path (/tmp default):</label>
<input type="text" th:field="*{saveDir}" class="form-control" placeholder="/tmp">
</div>
<div class="form-group col-md-12 mt-2">
<div class="col-sm-10">
<input class="btn btn-primary" id="save" type="submit" value="Export" />
<input class="btn btn-secondary" type="reset" value="Reset" />
</div>
</div>
</form>
</div>
#RequestMapping(value = "/utility/exportbadges")
public String exportBadges(Model model) {
final BadgeExport badgeExport = new BadgeExport();
model.addAttribute("badgeExport", badgeExport);
return "utility/exportbadges";
}
POST method. The object is correct in this function. Any field that's edited in the form above reflects in this function. However, on redirect, the object is as if it only has default instantiation/has been unedited.
#RequestMapping(value = "/utility/exportbadges", method = RequestMethod.POST)
public String exportBadgeFlow(Model model,
#ModelAttribute("badgeExport") final BadgeExport badgeExport) {
log.info("BadgeExport badge type: {}", badgeExport.getType());
log.info("BadgeExport save dir pre export: {}", badgeExport.getSaveDir());
switch(badgeExport.getType()) {
case "Attendee":
log.error("Attendee export not yet implemented");
break;
case "Vip":
return "redirect:exportbadges/vip-badges.pdf";
case "Specialty":
return "redirect:exportbadges/specialty-badges.pdf";
case "Staff":
return "redirect:exportbadges/staff-badges.pdf";
case "Guest":
return "redirect:exportbadges/guest-badges.pdf";
}
return "redirect:exportbadges";
}
Redirect function. Badge type will be null and saveDir will be /tmp as default instead of updated value in form.
#RequestMapping(value = "/utility/exportbadges/vip-badges.pdf")
public ResponseEntity<String> getAllVipBadgePdf(#ModelAttribute("badgeExport") final BadgeExport badgeExport) throws IOException {
log.info("BadgeExport badge type: {}", badgeExport.getType());
log.info("BadgeExport save dir during export: {}", badgeExport.getSaveDir());
}

Get checkbox status in livewire

I have a form that has 2 checkboxes in this form. When I submit the information I want to test what the value of the first checkbox is. I use ‍‍‍‍‍‍`dd () but it shows me the value of Null. What is the problem? How can I solve this?
this is view:
<div class="form-group">
<div class="custom-control custom-switch">
<input class="custom-control-input" id="deposit" type="checkbox"> <label class="custom-control-label" for="deposit">Deposit</label>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input class="custom-control-input" id="withdraw" type="checkbox"> <label class="custom-control-label" for="withdraw">withdraw</label>
</div>
</div>
</div>
<button class="btn btn-round btn-outline-primary"><span class="w-100px">create</span></button>
and this is component:
<?php
namespace App\Http\Livewire\Backend\Currency;
use Livewire\Component;
class Networks extends Component
{
public $deposit;
public $withdraw;
public function addNetwork()
{
dd($this->deposit1);
}
public function render()
{
return view('livewire.backend.currency.networks');
}
}
You bind to the checkboxes like you would any other public property, using wire:model.
<div class="form-group">
<div class="custom-control custom-switch">
<!-- bind to $deposit -->
<input wire:model="deposit"
class="custom-control-input"
id="deposit"
type="checkbox">
<label class="custom-control-label" for="deposit">Deposit</label>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<!--bind to $withdraw -->
<input wire:model="withdraw"
class="custom-control-input"
id="withdraw"
type="checkbox">
<label class="custom-control-label" for="withdraw">withdraw</label>
</div>
</div>
</div>
Now when the form is submitted, if deposit or withdraw is checked, their value will be true.
Something you might want to do is give $deposit and $withdraw a default value of false as otherwise their initial values will be null.
public $deposit = false;
public $withdraw = false;

How can I hide and display part of the form on a condition?

I want to use the same form to perform two different operations, and hide and display part of the form on a condition. Let's say if the text of the submit button is "SAVE" admissionNumber input field will not display but if the button text of the submit button is "UPDATE" admissionNumber input field will display. Below is the code sample
registerForm.html the same for being used to register and update student information
<div layout:fragment="content" class="container mySpace">
<form method="post" th:object="${student}" th:action="#{/register}">
<div class="card cardspace">
<h5 class="card-header" th:text="${chead}"></h5>
<div class="card card-body">
<div class="form-row">
<div class="col-9">
<div class="row">
i want to be able hide admissionNumber field if the btname is save and display it if the btname changes to update
<div class="form-group col" th:if="${btnname} == 'Submit'">
<label for="admissionNumber">Admission Number</label> <input
type="text" class="form-control" id="admissionNumber"
th:field="*{admissionNumber}">
<div class="text text-danger"
th:if="${#fields.hasErrors('admissionNumber')}"
th:errors="*{admissionNumber}"></div>
</div>
<div class="form-group col" th:unless="${btnname} == 'Update'">
<label for="admissionNumber">Admission Number</label> <input
type="text" class="form-control" id="admissionNumber"
th:field="*{admissionNumber}">
<div class="text text-danger"
th:if="${#fields.hasErrors('admissionNumber')}"
th:errors="*{admissionNumber}"></div>
</div>
<div class="form-group col">
<label for="firstName">First Name</label> <input type="text"
class="form-control" id="firstName" th:field="*{firstName}">
<div class="text text-danger"
th:if="${#fields.hasErrors('firstName')}"
th:errors="*{firstName}"></div>
</div>
<div class="form-group col">
<label for="lastName">Last Name</label> <input type="text"
class="form-control" id="lastName" th:field="*{lastName}">
<div class="text text-danger"
th:if="${#fields.hasErrors('lastName')}"
th:errors="*{lastName}"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" th:text="${btnname}"></button>
</form>
student controller class
#Controller
public class StudentController {
#Autowired
private StudentServices studentServices;
displays the registration form
#GetMapping("/register")
public String showStudentRegistrationForm(Model model ) {
model.addAttribute("student", new Student());
model.addAttribute("chead", "Student Enrollment");// for card-header title h5 tag
model.addAttribute("btnname", "Submit"); // for button text
return "views/studentRegistrationForm";
}
displays the edit or update form
#GetMapping("/editStudent")
public String showStudentRegistrationEditForm(Model model ) {
model.addAttribute("student", new Student());
model.addAttribute("chead", "Edit Student Enrollment");
model.addAttribute("btnname", "Update");
return "views/studentRegistrationForm";
}
}
what you want is th:if tag, you got it almost right but whole wxpression needs to be in brackets th:if="${btnname == 'Submit'}"

ASP.NET Core MVC app : I want to show the same view after submit and render the same data but having problems

I am new to ASP.NET Core MVC but I do have some experience working with ASP.NET webforms. I am creating an ASP.NET Core MVC project for practice in this I have a controller which has [http post] Create and Http Get Create action methods.
When user is on the Create page after entering the data user can click the submit button and once the data is saved, the view for http post Create() is render (reloading same view) and I am trying to show the data that user has filled prior to submit but data is not binding to the input controls. I am passing the same model to the view which was sent to be saved. During the debug and I am able to see the expected value under the Locals window in Visual Studio.
I want to understand what I am doing wrong or what changes I should be doing in order to work. If I need to take the different approach then why this the approach (mentioned below in code) I am taking is not correct?
Below is the code and explanation in the comments.
[HttpGet]
public async Task<IActionResult> Create()
{
// I am creating a viewModel object because wanted to include the logic for List<users> for dropdown.
var createView = await _chloramine.ChloramineSaveViewModel();
return View(createView);
}
[HttpPost]
// At page submit this Action method is called and data is saved.
public async Task<IActionResult> Create(ChloramineLogEditSaveViewModel clEditSaveViewModel)
{
_chloramine.CreateChloramineLog(clEditSaveViewModel);
// After data is saved I am adding a user list to the model because I was getting Object null error.
clEditSaveViewModel.User = await _chloramine.GetUser();
// Passing the same model object which was received in order to show what was filled or selected by the user.
return View(clEditSaveViewModel);
}
Below is the Create view html.
#model EquipmentMVC.Models.ChloramineLogEditSaveViewModel
#{
ViewData["Title"] = "Create";
}
<hr />
<div>
<form asp-action="Create">
<div class="form-group row">
#foreach (var item in Model.User)
{
#Html.HiddenFor(model => item)
}
<label asp-for="TimeDue" class="col-sm-2 col-form-label control-label"></label>
<div class="col-sm-8">
<input asp-for="TimeDue" value="" class="form-control" />
</div>
</div>
<div class="form-group row">
<label asp-for="PostPrimaryCarbanTankTp1" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="PostPrimaryCarbanTankTp1" />
</label>
</div>
</div>
</div>
<div class="form-group row">
<label asp-for="Comments" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<input asp-for="Comments" class="form-control" />
</div>
</div>
<div class="form-group row">
<label asp-for="IsCompleted" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<div class="form-check">
<input class="form-check-input" asp-for="IsCompleted" />
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Technician</label>
<div class="col-sm-8">
<select asp-for="Selected" asp-items=#(new SelectList(Model.User,"Id","Name"))>
<option value="">Select...</option>
</select>
#*<span asp-validation-for="Selected" class="text-danger"></span>*#
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">RN</label>
<div class="col-sm-8">
<select asp-for="RnSelected" asp-items=#(new SelectList(Model.User,"Id","Name"))>
<option value="">Select...</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<input type="submit" value="Create" class="btn btn-secondary" />
</div>
</div>
<div><span asp-validation-for="TimeDue" class="text-danger"></span></div>
<div><span asp-validation-for="Comments" class="text-danger"></span></div>
<div><span asp-validation-for="IsCompleted" class="text-danger"></span></div>
</form>
</div>
I tested your code and found that it runs very well in my project.
Except for TimeDue, the field is not successfully bound, the rest of the fields are bound successfully.
I don’t know if you have the same problem. The TimeDue is not successful binding is because you set the Value in the view.
Please delete the value in this field in the view:
<input asp-for="TimeDue" class="form-control" />
Result:
By the way,your User is null only because your User is not successfully bound, you can modify your loop code as follows:
#for (int i = 0; i < Model.User.Count(); i++)
{
<input type="hidden" name="User[#i].Id" value=#Model.User[i].Id />
<input type="hidden" name="User[#i].Name" value=#Model.User[i].Name />
}
Then in your Create action:
[HttpPost]
public IActionResult Create(ChloramineLogEditSaveViewModel clEditSaveViewModel)
{
return View(clEditSaveViewModel);
}
Result:
Edit:
My codes:
Controller:
public IActionResult Create()
{
var model = new ChloramineLogEditSaveViewModel
{
Id = 1,
Comments = "aaaa",
PostPrimaryCarbanTankTp1 = "fjsdgk",
TimeDue = "bbbbbb",
IsCompleted=true,
RnSelected="gggg",
Selected="sgs",
User=new List<User>
{
new User{
Id=1,
Name="aa"
},
new User
{
Id=2,
Name="bb"
}
}
};
return View(model);
}
[HttpPost]
public IActionResult Create(ChloramineLogEditSaveViewModel clEditSaveViewModel)
{
return View(clEditSaveViewModel);
}
View:
<form asp-action="Create">
<div class="form-group row">
#for (int i = 0; i < Model.User.Count(); i++)
{
<input type="hidden" name="User[#i].Id" value=#Model.User[i].Id />
<input type="hidden" name="User[#i].Name" value=#Model.User[i].Name />
}
<label asp-for="TimeDue" class="col-sm-2 col-form-label control-label"></label>
<div class="col-sm-8">
<input asp-for="TimeDue" class="form-control" />
</div>
</div>
<div class="form-group row">
<label asp-for="PostPrimaryCarbanTankTp1" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="PostPrimaryCarbanTankTp1" />
</label>
</div>
</div>
</div>
<div class="form-group row">
<label asp-for="Comments" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<input asp-for="Comments" class="form-control" />
</div>
</div>
<div class="form-group row">
<label asp-for="IsCompleted" class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<div class="form-check">
<input class="form-check-input" asp-for="IsCompleted" />
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Technician</label>
<div class="col-sm-8">
<select asp-for="Selected" asp-items=#(new SelectList(Model.User,"Id","Name"))>
<option value="">Select...</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">RN</label>
<div class="col-sm-8">
<select asp-for="RnSelected" asp-items=#(new SelectList(Model.User,"Id","Name"))>
<option value="">Select...</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label"></label>
<div class="col-sm-8">
<input type="submit" value="Create" class="btn btn-secondary" />
</div>
</div>
<div><span asp-validation-for="TimeDue" class="text-danger"></span></div>
<div><span asp-validation-for="Comments" class="text-danger"></span></div>
<div><span asp-validation-for="IsCompleted" class="text-danger"></span></div>
</form>

How to validate a form with thymeleaf and spring?

I simply want to validate a form data and display an error message. Validating email format, password size and password matching. What would be the best way to do that?
Here's what I've tried
My controller
#PostMapping("/{customerId}/createUser")
public String signIn(#PathVariable(value = "customerId", required = false) Long customerId,
#ModelAttribute(name = "user") #Valid Users user, RedirectAttributes redirectAttributes,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "customerNewUser";
}
// Encrypt password
user.setPassword(encoder.encode(user.getPassword()));
user.setCustomerId(customerId);
user.setEventDescription("User Admin creation");
try {
Users returnedUser = userService.save(user);
List<Authorities> authorities = new ArrayList<Authorities>();
Authorities auth = new Authorities(new AuthoritiesPK(returnedUser.getId(), "ROLE_CLI_ADM"), returnedUser,
"ROLE_CLI_ADM");
authorities.add(auth);
returnedUser.setAuthorities(authorities);
returnedUser.setEventDescription("Add Admin role");
for (int i = 0; i < returnedUser.getAuthorities().size(); i++) {
authorityService.save(returnedUser.getAuthorities().get(i));
}
userService.save(returnedUser);
} catch (WebExchangeBindException webe) {
StringBuilder errorBuilder = new StringBuilder();
for (ObjectError error : webe.getAllErrors()) {
errorBuilder.append(messageSource.getMessage(error.getCode(), null, Locale.getDefault())).append("\n");
}
redirectAttributes.addFlashAttribute("signInError", errorBuilder.toString());
}
return "redirect:/customers/?id=" + customerId;
}
My form in my customerNewUser html file
<form
th:action="#{${user?.id} ? '/customers/' + ${customer.id} + '/createUser/' + ${user.id} : '/customers/' + ${customer.id} + '/createUser/'}"
th:object="${user}" action="#" method="post">
<div class="alert alert-danger" th:if="${#fields.hasAnyErrors()}">
<div th:each="detailedError : ${#fields.detailedErrors()}">
<span th:text="${detailedError.message}"></span>
</div>
</div>
<div class="form-group">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{firstname} + ':'">Firstname:</label>
</div>
<div class="col-md-4">
<input type="text" id="firstname" name="firstname"
th:value="${user?.firstname} ? ${user?.firstname} : ''"
class="form-control" th:placeholder="#{firstname}"
required="required" autofocus="autofocus" th:field="*{firstname}"/>
<label th:style="'color: red;'" class="text-right label-control" th:if="${#fields.hasErrors('firstname')}" th:errors="*{firstname}" th:text="#{passwordError}">Password Error</label>
</div>
</div>
</div>
<div class="form-group">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{surname} + ':'">Surname:</label>
</div>
<div class="col-md-4">
<input type="text" id="surname" name="surname"
th:value="${user?.surname} ? ${user?.surname} : ''"
class="form-control" th:placeholder="#{surname}"
autofocus="autofocus" />
</div>
</div>
</div>
<div class="form-group">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{email} + ':'">Email:</label>
</div>
<div class="col-md-4">
<input th:placeholder="#{email}" required="required"
autofocus="autofocus" id="email" class="form-control"
type="text" th:value="${user?.email} ? ${user?.email} : ''"
name="email" th:field="*{email}"/>
<label th:style="'color: red;'" class="text-right label-control" th:if="${#fields.hasErrors('email')}" th:errors="*{email}" th:text="#{emailError}">Email Error</label>
</div>
</div>
</div>
<div class="form-group" th:if="!${user?.id}">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{password} + ':'">Password:</label>
</div>
<div class="col-md-4">
<input type="password" id="password" name="password"
class="form-control" th:placeholder="#{password}"
required="required" autofocus="autofocus" th:field="*{password}"/>
<label th:style="'color: red;'" class="text-right label-control" th:if="${#fields.hasErrors('password')}" th:errors="*{password}" th:text="#{passwordError}">Password Error</label>
</div>
</div>
</div>
<div class="form-group" th:if="!${user?.id}">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{passwordConfirmation} + ':'">Password confirmation:</label>
</div>
<div class="col-md-4">
<input type="password" id="matchPassword" name="matchPassword"
class="form-control" th:placeholder="#{password}"
required="required" autofocus="autofocus" th:field="*{matchPassword}"/>
<label th:style="'color: red;'" class="text-right label-control" th:if="${#fields.hasErrors('matchPassword')}" th:errors="*{matchPassword}" th:text="#{matchPassword}">Match Password Error</label>
</div>
</div>
</div>
<div class="form-group" th:if="${user?.id}">
<div class="form-row">
<div class="col-md-5">
<label class="text-right label-control"
th:text="#{userStatus} + ':'">Status:</label>
</div>
<div class="col-md-3">
<select class="form-control form-control" id="userStatus"
name="userStatus">
<option
th:each="userStatus : ${T(br.com.macrosul.stetho.entity.UserStatus).values()}"
th:text="${userStatus}" th:value="${userStatus}"
th:selected="${user.userStatus} eq ${userStatus}"></option>
</select>
</div>
</div>
</div>
<div class="form-group">
<p class="error-control" th:text="${signInError}"></p>
</div>
<input type="submit" class="btn btn-md btn-block"
value="Sign in"
th:value="${user?.id} ? #{updateUser} : #{signIn}" />
</form>
And here is the stacktrace when I try to force a password size error ->
#Size(min=6)
#Column(nullable = false)
private String password;
Field error in object 'user' on field 'password': rejected value [12345]; codes [Size.user.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.password,password]; arguments []; default message [password],2147483647,6]; default message [tamanho deve estar entre 6 e 2147483647]
I want a simple validation. If there's a simpler way to indicate the user the data are invalid I would appreciate for any help or advice!
It is a little bit late, but you need to put your BindingResult next to your #valid attribute so instead of:
#ModelAttribute(name = "user") #Valid Users user, RedirectAttributes redirectAttributes, BindingResult bindingResult
you should write:
#ModelAttribute(name = "user") #Valid Users user, BindingResult bindingResult, RedirectAttributes redirectAttributes
You need a validator like below. Its not the exact code what you need but sufficient enough and pretty close to what you need to get you going.
#Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "useremail", "NotEmpty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userfirstname", "NotEmpty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userlastname", "NotEmpty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "useraddress", "NotEmpty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty");
if (user.getPassword().length() < 8 || user.getPassword().length() > 32) {
errors.rejectValue("password", "Size.userForm.password");
}
if (!user.getPasswordConfirm().equals(user.getPassword())) {
errors.rejectValue("passwordConfirm", "Diff.userForm.passwordConfirm");
}
}
For more details you can take a look at here, here and here.
You can put a div in the html that only exists if there are errors on the validation. Then you show each error in a loop:
<div th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}">
<span th:text="${err}"></span>
</p>
</div>
This way, when you return to this view there will be errors on your fields so this code will be shown.

Resources