Spring has a tag library that helps you in building forms. It also has a tag called errors
that lets you output validation errors in a formatted way. However, this tag has some limitations. When multiple errors are displayed at the same time, you can only control the styling of the outside container and not the styling of each of the errors. So for example it is not possible to display the error messages as an unordered list.
This tutorial describes how to create a custom errors tag that has some extra customization attibutes compared to the basic Spring form errors tag.
The steps we will take include the following:
- Creating a Tag Library with our custom tag definition in it.
- Creating a custom class that specifies how our new tag will work.
- Include the tag library in a sample JSP and use it to display errors.
Creating the Tag Library
A tag library definition is contained in a .tld
file and also each tag has a class which contains the logic that results in some kind of output when we use the tag. We are using the basic Spring error tag’s definition as a starting point, but add two extra attributes to it. The added attributes are highlighted in the code below.
<?xml version="1.0" encoding="UTF-8"?> <taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"> <tlib-version>1.0</tlib-version> <short-name>Jtuts common tags</short-name> <uri>http://jtuts.com/taglib/tags</uri> <tag> <description>Renders field errors wrapped in a specified tag.</description> <name>errors</name> <tag-class>jtuts.tag.CustomSpringFormErrorsTag</tag-class> <body-content>JSP</body-content> <variable> <name-given>messages</name-given> <variable-class>java.util.List</variable-class> </variable> <attribute> <description>Path to errors object for data binding</description> <name>path</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>id</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Enable/disable HTML escaping of rendered values.</description> <name>htmlEscape</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Specifies the HTML element that is used to render the individual errors.</description> <name>innerElement</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Specifies the CSS class that the element has that is used to render the individual errors.</description> <name>innerElementCssClass</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Delimiter for displaying multiple error messages. Defaults to the br tag.</description> <name>delimiter</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Equivalent to "class" - HTML Optional Attribute</description> <name>cssClass</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Equivalent to "style" - HTML Optional Attribute</description> <name>cssStyle</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>lang</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>title</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>dir</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Standard Attribute</description> <name>tabindex</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onclick</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>ondblclick</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onmousedown</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onmouseup</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onmouseover</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onmousemove</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onmouseout</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onkeypress</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onkeyup</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>HTML Event Attribute</description> <name>onkeydown</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <description>Specifies the HTML element that is used to render the enclosing errors.</description> <name>element</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <dynamic-attributes>true</dynamic-attributes> </tag> </taglib>
One of the new attributes is innerElement
. It can be used to specify an HTML element that will be used to wrap every single error message individually. The other new attribute is the innerElementCssClass
. This specifies the CSS class or classes that will be applied to each of the inner wrapping elements.
The other attributes are just copied from the original Spring form errors tag.
Creating the tag class
The tag class specifies what kind of output will the tag produce based on it’s attribute values. Here is the full class for the new tag.
package jtuts.tag; import javax.servlet.jsp.JspException; import org.springframework.util.ObjectUtils; import org.springframework.web.servlet.tags.form.ErrorsTag; import org.springframework.web.servlet.tags.form.TagWriter; /** * The Spring form:errors tag extended with attributes which allow to use an inner element with a * css class which wraps every individual error item. */ public class CustomSpringFormErrorsTag extends ErrorsTag { private static final long serialVersionUID = 24723583545L; private String innerElement; private String innerElementCssClass; @Override protected void renderDefaultContent(TagWriter tagWriter) throws JspException { tagWriter.startTag(getElement()); writeDefaultAttributes(tagWriter); String delimiter = ObjectUtils.getDisplayString(evaluate("delimiter", getDelimiter())); String[] errorMessages = getBindStatus().getErrorMessages(); for (int i = 0; i < errorMessages.length; i++) { String errorMessage = errorMessages[i]; if (i > 0) { tagWriter.appendValue(delimiter); } if (innerElement != null) tagWriter.appendValue(startTagForInnerElement()); tagWriter.appendValue(getDisplayString(errorMessage)); if (innerElement != null) tagWriter.appendValue(endTagForInnerElement()); } tagWriter.endTag(); } private String startTagForInnerElement() { StringBuilder stringBuilder = new StringBuilder("<" + innerElement); if (innerElementCssClass != null) stringBuilder.append(" class=\"" + innerElementCssClass + "\""); stringBuilder.append(">"); return stringBuilder.toString(); } private String endTagForInnerElement() { return "</" + innerElement + ">"; } public String getInnerElement() { return innerElement; } public void setInnerElement(String innerElement) { this.innerElement = innerElement; } public String getInnerElementCssClass() { return innerElementCssClass; } public void setInnerElementCssClass(String innerElementCssClass) { this.innerElementCssClass = innerElementCssClass; } }
You can see that it extends the basic error tag (org.springframework.web.servlet.tags.form.ErrorsTag
). We override the renderDefaultContent
method that creates the output of the tag. Anything that you pass to this method will be appended to the output produced when someone uses this tag.
As you can see the extra logic that we have added is pretty simple. We just print an opening and closing tag around each error if the user provided the innerElement
attribute and we also add the CSS classes to the opening tag if the innerElementCssClass
attribute is specified.
We have also set up some extra methods, but they are just there to simplify the renderDefaultContent
method.
The outer element that encloses all the errors is just displayed the same way as it was in the super implementation (startTag
, endTag
, writeDefaultAttributes
calls).
Using the tag
For demonstration purposes I have created a very simple registration form that uses the newly created tag.
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="jtuts" uri="/WEB-INF/tags/jtuts.tld" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Registration form with a custom errors tag.</title> <style> .error-message { color: #CC3344; } </style> </head> <body> <h1>Register</h1> <form:form commandName="userForm" action="${pageContext.request.contextPath}/register" method="post"> <jtuts:errors path="*" element="ul" innerElement="li" delimiter="" cssClass="error-message" /> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td>User Name:</td> <td><form:input path="userName" /></td> </tr> <tr> <td>Email Address:</td> <td><form:input path="emailAddress" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save User" /> </td> </tr> </table> </form:form> </body> </html>
Notice the taglib definition with the taglib
directive and the usage of our new jtuts:errors
tag.
The POM, the Controller, the form class and validation
For the sake of completeness I also show you the remaining parts of the sample application.
The pom.xml
looks like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>how-to-create-a-custom-spring-form-errors-tag-20150126</groupId> <artifactId>how-to-create-a-custom-spring-form-errors-tag-20150126</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>how-to-create-a-custom-spring-form-errors-tag-20150126 Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <spring.version>4.1.4.RELEASE</spring.version> <javax.validation.validation-api.version>1.1.0.Final</javax.validation.validation-api.version> <hibernate-validator.version>5.1.3.Final</hibernate-validator.version> <servlet-api.version>2.5</servlet-api.version> <jsp-api.version>2.2</jsp-api.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>${javax.validation.validation-api.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>${hibernate-validator.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>${jsp-api.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/jtuts</path> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
I just included the minimum dependencies required for this project. I also used the Tomcat Maven plugin to be able to deploy the app easily.
The Controller
class:
package jtuts.controller; import javax.validation.Valid; import jtuts.form.UserForm; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller public class RegisterController { @RequestMapping(value="register", method = RequestMethod.GET) public ModelAndView registerGet() { ModelAndView model = new ModelAndView(); model.setViewName("register"); model.addObject("userForm", new UserForm()); return model; } @RequestMapping(value="register", method = RequestMethod.POST) public ModelAndView registerPost(@Valid @ModelAttribute("userForm") UserForm userForm, BindingResult result) { ModelAndView model = new ModelAndView(); model.setViewName("register"); return model; } }
In the controller we are just running the form through some basic validation. The validation rules are set up in the form class which looks like this:
package jtuts.form; import org.hibernate.validator.constraints.NotEmpty; public class UserForm { @NotEmpty(message="The first name may not be empty.") private String firstName; @NotEmpty(message="The last name may not be empty.") private String lastName; @NotEmpty(message="The user name may not be empty.") private String userName; @NotEmpty(message="The email address may not be empty.") private String emailAddress; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } }
The Spring configuration for the validator to work:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <context:component-scan base-package="jtuts" /> <mvc:annotation-driven validator="validator" /> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/> </beans>
The working application
This is what the working application looks like when we try to submit an empty form. Notice that we are displaying the error messages as an unordered list which was not possible with the original version of the errors tag.
Download
You can download the whole working source code from here.