I have built a form for login. I've read about validations with spring mvc following a tutorial.
The form is blank & doesn't pass values, however it doesn't show any error messages.
This is my code for the controller:
#Controller
public class LoginController {
#RequestMapping("/login")
public ModelAndView login() {
return new ModelAndView("login", "user", new UserModel());
}
#RequestMapping("/submitLogin")
public ModelAndView submitLogin(#Valid UserModel user, BindingResult result) {
ModelAndView model = new ModelAndView();
model.addObject("user", user);
model.setViewName(result.hasErrors() ? "login" : "index");
return model;
}
}
This is my model:
#Data
public class UserModel {
#NotBlank(message = "The field login name is required")
private String login;
#NotBlank(message = "The field password is required")
private String password;
}
And this is my form:
<mvc:form modelAttribute="user" class="col s12" action="/submitLogin" method="POST">
<div class="container login-form">
<div class="row">
<div class="input-field col s12">
<mvc:input path="login" id="userName" type="text" class="validate" />
<mvc:label path="login" for="userName">Login name</mvc:label>
</div>
</div>
<div class="row">
<mvc:errors path="login" cssStyle="color: #ff0000;"/>
</div>
<div class="row">
<div class="input-field col s12">
<mvc:input path="password" id="password" type="password" class="validate" />
<mvc:label path="password" for="password">Password</mvc:label>
</div>
</div>
<div class="row">
<mvc:errors path="password" cssStyle="color: #ff0000;"/>
</div>
<div class="row">
<input type="submit" class="btn waves-effect waves-light pulse green" value="Entrar"/>
</div>
</div>
</mvc:form>
Did I miss any configuration on spring boot or annotation?
Related
I'm just wanting to do a simple validation, if the html fields below are sent "null or empty" the user can't persist the data. How can I solve this problem?
below my code
HTML:
<div th:each="msgErrors : ${erros}" class="alert alert-danger alert-dismissible fade show" role="alert">
<span th:text="${msgErrors}"></span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form th:action="cadastrar" method="post" th:object="${fatura}">
<div class="row">
<div class="col-lg-4 col-sm-12">
<label for="cofins">COFINS:</label>
<input type="text" class="form-control" autocomplete="off" id="cofins" th:field="*{cofins}" placeholder="0,00 %">
</div>
<div class="col-lg-4 col-sm-12">
<label for="icms">ICMS:</label>
<input type="text" class="form-control" autocomplete="off" id="icms" th:field="*{icms}" placeholder="0,00 %">
</div>
</div>
<div class="row mt-4">
<button type="submit" class="btn btn-success mr-3"><i class="far fa-save"></i> SALVAR</button>
</div>
</form>
My Model Class:
#Entity
public class Fatura {
#NumberFormat(pattern = "#,###.##")
#Column(name = "cofins")
private BigDecimal cofins;
#NumberFormat(pattern = "#,###.##")
#Column(name = "icms")
private BigDecimal icms;
}
My Controller:
public ModelAndView cadastro(#Valid Fatura fatura, BindingResult br){
if(br.hasErrors()){
ModelAndView mv = new ModelAndView("cadastro/cadastro");
mv.addObject("fatura", fatura);
mv.addObject("listLojas", Lojas.values());
List<String> msg = new ArrayList<String>();
for(ObjectError objError : br.getAllErrors()){
msg.add(objError.getDefaultMessage());
}
mv.addObject("erros", msg);
return mv;
}
I need the user to not be allowed to send the fields without typing anything
There are two methods you can use,
NotNull bean validation
Constraint in your database for the fields as not nullable
For more info about bean validation check this.
Similar question is posted here-> Spring + Thymeleaf custom validation display but I could not figure out the solution so posting this new question.
I have a simple registration form having username,email, password and confirmPassword fields
<form action="#" th:action="#{/register}" th:object="${user}"
method=post>
<!--error detection start -->
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
<!--error detection ends -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-user"></i>
</span>
</div>
<input name="username" th:field="*{username}" class="form-control"
placeholder="User Name" type="text">
</div>
<div class="form-group input-group"
th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Name
Error</div>
<!-- form-group// -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-envelope"></i>
</span>
</div>
<input name="email" th:field="*{email}" class="form-control"
placeholder="Email address" type="email">
</div>
<div class="form-group input-group"
th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Name
Error</div>
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" th:field="*{password}"
placeholder="Create password" type="password">
</div>
<!-- form-group// -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" th:field="*{confirmPassword}"
placeholder="Repeat password" type="password">
<p class="error-message"
th:each="error: ${#fields.errors('user.confirmPassword')}"
th:text="${error}">Validation error</p>
</div>
<!-- form-group// -->
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">
Create Account</button>
</div>
<!-- form-group// -->
<p class="text-center">
Have an account? Log In
</p>
</form>
I added a custom validation which gets trigger when password and confirm password field do not match.
1.FieldsValueMatchValidator
public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> {
private String field;
private String fieldMatch;
public void initialize(FieldsValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch);
if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}
3.FieldsValueMatch
#Constraint(validatedBy = FieldsValueMatchValidator.class)
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface FieldsValueMatch {
String message() default "Fields values don't match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String field();
String fieldMatch();
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#interface List {
FieldsValueMatch[] value();
}
}
4.User.java
#FieldsValueMatch(field = "password", fieldMatch = "confirmPassword", message = "Passwords do not match!")
#Entity
public class User implements UserDetails
{
#NotBlank
private String password;
#Transient
#NotBlank
private String confirmPassword;
//other getters and setters
}
5.Controller code
#PostMapping("/register")
public String saveNonJsonData(#Valid #ModelAttribute("user") User theUser, BindingResult errors) {
if (errors.hasErrors()) {
return "register";
}
else
{
//successlogic
}
Custom validator is working fine and i can see the error message on the page using following code on thymeleaf page
<!--error detection start -->
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
<!--error detection ends -->
As mentioned here - Spring + Thymeleaf custom validation display the problem is custom validator returning an ObjectError for password field match validation and not a fieldError. Even though I tried solution provided I can't figure out how to get Thymeleaf to display my custom error.
UPDATE
Got one more answer here Displaying "Passwords don't match" custom annotation message and now i can see the error message using following code
<input class="form-control" th:field="*{confirmPassword}" placeholder="Repeat password" type="password">
<div class="form-group input-group" th:if="${#fields.hasErrors('global')}" th:errors="*{global}"></div>
My updated question if I have two more fields for example 'email' and 'confirmEmail' field then how this approach will work on thymeleaf page?
I am working on registration in spring boot with spring security and thymeleaf but for some reason the userForm.getUsername() gives null value during POST (see code below)
Person.java
#Entity#Table(name = "person")
public class Person {
#Id#Column(name = "username")
private String username;
#Column(name = "mobile")
private String mobile;
#JsonIgnore#Column(name = "password")
private String password;
#Transient#JsonIgnore
private String passwordConfirm;
#ManyToMany(cascade = CascadeType.ALL)#JoinTable(name = "bookandperson", joinColumns = #JoinColumn(name = "username"), inverseJoinColumns = #JoinColumn(name = "bookName"))
private List < Book > listOfBooks = new ArrayList < Book > ();
}
PersonController.java
#Controller
public class PersonController {
#Autowired
private UserService userService;
#Autowired
private SecurityService securityService;
#Autowired
private UserValidator userValidator;
#RequestMapping(value = "/registration", method = RequestMethod.GET)
public String registration(Model model, Principal user) {
if (user != null) {
return "redirect:/";
}
model.addAttribute("userForm", new Person());
return "registration";
}
#RequestMapping(value = "/registration", method = RequestMethod.POST)
public String registration(#ModelAttribute Person userForm, BindingResult bindingResult, Model model) {
System.out.println(userForm.getUsername()); //This is giving null value
userValidator.validate(userForm, bindingResult);
if (bindingResult.hasErrors()) {
System.out.println("binding result has errors");
return "registration";
}
userService.save(userForm);
securityService.autologin(userForm.getUsername(), userForm.getPasswordConfirm());
return "redirect:/";
}
registration.html
<head>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
</head>
<body>
<nav th:replace="common/navbar :: common-navbar"/>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6 col-md-offset-6">
<form th:action="#{/registration}" method="post">
<div class="form-group row">
<label for="inputUsername" class="col-sm-2 col-form-label">Username</label>
<div class="col-md-6">
<input type="text" class="form-control" th:field="${userForm.username}" id="inputUsername" value="" placeholder="Username">
</div>
</div>
<div class="form-group row">
<label for="mobile" class="col-sm-2 col-form-label">Mobile</label>
<div class="col-md-6">
<input type="text" class="form-control" th:field="${userForm.mobile}" id="inputMobile" placeholder="Mobile">
</div>
</div>
<div class="form-group row">
<label for="inputPassword" class="col-sm-2 col-form-label">Password</label>
<div class="col-md-6">
<input type="password" class="form-control" th:field="${userForm.password}" id="inputPassword" placeholder="Password">
</div>
</div>
<div class="form-group row">
<label for="inputPasswordConfirm" class="col-sm-2 col-form-label">Confirm Password</label>
<div class="col-md-6">
<input type="password" class="form-control" th:field="${userForm.passwordConfirm}" id="inputPasswordConfirm" placeholder="Password">
</div>
</div>
<div class="form-group row">
<div class="offset-2 col-sm-10">
<button type="submit" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" class="btn btn-primary">Submit</button>
</div>
</div>
<small th:text="${error}"></small>
</form>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</body>
I binded correctly using th:field="${userForm.username}" but unable to find what is the problem here.
you have to take the model object into the form as follows
<form action="#" th:action="#{/registration}" th:object="${userForm}" method="post">
and add the getters and setters to the Person class
I'm using a combination of Annotation validation and a Custom Validator
Object:
#Entity
#Table(name = "monitoringsystems")
public class MonitoringSystem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull(message = "Name must not be empty.")#Size(min=1, message="Name must not be empty.")
private String name;
#NotNull(message = "URL must not be empty.")#Size(min=1, message="Name must not be empty.")
private String url;
#NotNull(message = "Username must not be empty.")#Size(min=1, message="Name must not be empty.")
private String username;
#NotNull(message = "Password must not be empty.")#Size(min=1, message="Name must not be empty.")
private String password;
#NotNull(message = "Confirm Password must not be empty.")#Size(min=1, message="Name must not be empty.")
#Transient
private String passwordConfirm;
CustomValidator:
#Component
public class MonitoringSystemValidator implements Validator {
#Override
public boolean supports(Class<?> type) {
return MonitoringSystem.class.isAssignableFrom(type);
}
#Override
public void validate(Object o, Errors errors) {
MonitoringSystem monitoringSystem = (MonitoringSystem) o;
if(!monitoringSystem.getPassword().equals(monitoringSystem.getPasswordConfirm())){
errors.rejectValue("passwordConfirm", "Passwords are not equal.");
}
}
}
I initialize the custom validator in my controller and set the mapping for the form and the saving method.
Controller:
#Controller
public class MonitoringSystemController {
#Autowired
private MonitoringSystemValidator monitoringSystemValidator;
#InitBinder
public void dataBinding(WebDataBinder binder) {
binder.addValidators(monitoringSystemValidator);
}
#RequestMapping("/monitoringsystem/new")
public String newMonitoringSystem(Model model, HttpServletRequest request) {
MonitoringSystem monitoringSystem = new MonitoringSystem();
model.addAttribute("monitoringSystem", monitoringSystem);
request.getSession().setAttribute("anonymization", monitoringSystem.getAnonymization());
request.getSession().setAttribute("hosts", monitoringSystem.getHosts());
return "monitoringsystem/form";
}
#RequestMapping(value = "/monitoringsystem/save", method = RequestMethod.POST)
public String save(#Valid MonitoringSystem monitoringSystem, BindingResult result, HttpServletRequest request, Model model) {
if(result.hasErrors()){
model.addAttribute("monitoringSystem", monitoringSystem);
request.getSession().setAttribute("anonymization", request.getSession().getAttribute("anonymization"));
request.getSession().setAttribute("hosts", request.getSession().getAttribute("hosts"));
return "monitoringsystem/form";
}
//more code
In a first step I only want to change the CSS of my fields (I use bootstrap) so display the errors.
Form:
<form class="form-horizontal" th:modelAttribute="monitoringSystem" th:object="${monitoringSystem}" th:action="#{/monitoringsystem/save}" method="post">
<input type="hidden" th:field="*{id}"/>
<fieldset>
<legend>New Monitoring-System</legend>
<div class="form-group" th:classappend="${#fields.hasErrors('name')} ?: 'has-error has-danger'">
<label class="col-md-4 control-label" for="textinput">Systemname</label>
<div class="col-md-5">
<input th:field="*{name}" class="form-control input-md" type="text" />
</div>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('url')} ?: 'has-error has-danger'">
<label class="col-md-4 control-label" for="textinput">URL</label>
<div class="col-md-5">
<input th:field="*{url}" class="form-control input-md" type="text" />
</div>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('username')} ?: 'has-error has-danger'">
<label class="col-md-4 control-label" for="textinput">Username</label>
<div class="col-md-5">
<input th:field="*{username}" class="form-control input-md" type="text" />
</div>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('password')} ?: 'has-error has-danger'">
<label class="col-md-4 control-label" for="textinput">Password</label>
<div class="col-md-5">
<input th:field="*{password}" class="form-control input-md" type="password" />
</div>
</div>
<div class="form-group" th:classappend="${#fields.hasErrors('passwordConfirm')} ?: 'has-error has-danger'">
<label class="col-md-4 control-label" for="textinput">Confirm Password</label>
<div class="col-md-5">
<input th:field="*{passwordConfirm}" class="form-control input-md" type="password" />
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label" for="singlebutton"></label>
<div class="col-md-4">
<a th:href="#{/monitoringsystem}" class="btn btn-default btn-small">Cancel</a> <button id="singlebutton" name="singlebutton" class="btn btn-primary btn-small">Submit</button>
</div>
</div>
</fieldset>
</form>
My validation is working correctly. The form is only saved if my fields are not null, the size is greater 1 and the password match. If not, my controller redirects me to the form.
The problem is, that my css don't change. So there must be a problem with my view-code or the errorBinding is not passed correctly to the view. But I can't find my mistake.
There was an error in my if condition which add the errorclasses. I had to change ${#fields.hasErrors('url')} ?: 'has-error has-danger' to ${#fields.hasErrors('*{name}')} ? 'has-error has-danger'
How can I say trigger error/validation messages in the view from the controller in a better way? Currently, I do this by sending boolean attributes. For example, in creating a product, I have two possible errors. Invalid format of UPC of a product, or duplicate upc. I also have a validati
#RequestMapping("/createProduct")
public String createProduct(Model model, #RequestParam(value = "name") String name,
#RequestParam(value = "upc") String upc, #RequestParam(value = "category") String categoryName,
#RequestParam(value = "description") String description, #RequestParam(value = "price") BigDecimal price,
#RequestParam(value = "stock") int stock){
model.addAttribute("activeTab", 3);
if(Validator.invalidUpcFormat(upc)){
model.addAttribute("invalidFormat", true); //trigger for invalid format
return "management";
}
Category category = productService.getCategory(categoryName);
Product product = new Product(upc, category, name, description, price);
InventoryProduct inventoryProduct = new InventoryProduct(product, stock);
try {
managerService.add(inventoryProduct);
model.addAttribute("productCreated", true);
} catch (DuplicateProductException e) {
model.addAttribute("upc", upc);
model.addAttribute("duplicateProduct", true); // trigger for duplicate product
}
return "management";
}
And here is my view:
<div id="menu3"
class="tab-pane fade <c:if test="${activeTab == 3}">in active</c:if>">
<div class="container-fluid" style="padding: 2%;">
<div class="row">
<div class="col-md-12"
style="padding-left: 15%; padding-right: 15%;">
<c:if test="${productCreated}">
<div class="alert alert-success fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Success!</strong>
Product has been created!
</div>
</c:if>
<c:if test="${duplicateProduct}">
<div class="alert alert-warning fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Oh no!</strong>
Product with the UPC ${upc} already exists!
</div>
</c:if>
<c:if test="${invalidFormat}">
<div class="alert alert-warning fade in">
<a href="#" class="close" data-dismiss="alert"
aria-label="close">×</a> <strong>Oops!</strong>
Invalid UPC format!
</div>
</c:if>
<form
action="${pageContext.request.contextPath}/manager/createProduct"
method="post">
<div class="form-group">
<label for="Name">Name </label> <input type="text" name="name"
class="form-control" required />
</div>
<div class="form-group">
<label for="UPC">UPC </label> <input type="number" name="upc"
class="form-control" required />
</div>
<div class="form-group">
<div class="form-group">
<label for="category">Category</label> <select
class="form-control" name="category" required>
<option selected disabled value="">SELECT CATEGORY</option>
<c:forEach items="${categories}" var="item">
<option>${item.getName()}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control" rows="5" name="description"></textarea>
</div>
<div class="form-group">
<label for="price">Price </label> <input type="number"
name="price" class="form-control" required />
</div>
<div class="form-group">
<label for="stock">Stock </label> <input type="number"
name="stock" class="form-control" required />
</div>
<button type="submit" class="btn btn-primary">Add
product</button>
</form>
</div>
</div>
</div>
</div>
Is there a better of doing this other than sending boolean triggers?
You could use Spring BindingResult. This is typical filled with the result of Binding and Validation results. But you can also add errors by hand.
But first you need to refactor your code, so that you use an single command/form-backing object instead of all the #Param values
public class CreateProductCommand {
private String name;
private String upc;
private String categoryName;
.... //other fields
public CreateProductCommand (){} //parameter less conturctor
Getter+Setter
}
Controller
#RequestMapping("/createProduct")
public ModelAndView createProduct(CreateProductCommand createProductCommand, BindingResult bindingResult) //Binding result must be the parameter direct next to the object that should been validated!!!
{
if (someustomValidationForUcpFail()) {
bindingResult.rejectValue("upc", //the field name of the invalid field
"error.Message.Key",
"Default Error Message");
}
if (bindingResult.hasErrors()) {
ModelMap model = new ModelMap();
model.add("createProductCommand", createProductCommand);
return new ModelAndView("createForm", model)
} else {
Product product = ...
return new ModelAndView("showProduct", "product", product)
}
}
jsp:
You need to use springs form and input tag:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:springForm="http://www.springframework.org/tags/form"
version="2.0">
....
<springForm:form action="<c:url value="/manager/createProduct">" method="POST" modelAttribute="createProductCommand">
<springForm:input path="name"/> <form:errors path="name" />
<springForm:input path="ucp"/> <form:errors path="ucp" />
....
</springForm:form>
....