One of our porject forms has fields of type java.util.Date. At first we used String to store the value and then used regexp validator to check if the date was valid. When we actually needed to convert it to Date we had to do it manually. Naturally this is not a very clean solution, especially if you have to deal with it oftern. So I found a simple solution - wrote my own PropertyEditor for dates and attached it to my forms
package com.anydoby.web;
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
/**
* Supports transformation of dates to strings and vice versa using the specified date format. If
* string representation could not be parsed correctly, returns null date.
*
* @author SergeyZ
*
*/
public class DateEditor extends PropertyEditorSupport {
private final String datePattern;
private final Pattern textValidator;
public DateEditor(String datePattern, Pattern textValidator) {
this.datePattern = datePattern;
this.textValidator = textValidator;
}
@Override
public String getAsText() {
SimpleDateFormat format = new SimpleDateFormat(datePattern);
Object value = getValue();
if (value == null) {
return null;
}
String string = format.format(value);
return string;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.isBlank(text)) {
setValue(null);
} else {
if (!textValidator.matcher(text).matches()) {
throw new IllegalArgumentException("Invalid pattern");
}
SimpleDateFormat format = new SimpleDateFormat(datePattern);
format.setLenient(false);
try {
Date parse = format.parse(text);
setValue(parse);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
}
}and in the Controller’s initBinder
Pattern datePattern = Pattern.compile("\\d{1,2}/\\d{1,2}/\\d{4}");
DateEditor dateEditor = new DateEditor("dd/MM/yyyy", datePattern);
binder.registerCustomEditor(java.util.Date.class, dateEditor);Everything now works ok - Spring converts strings to dates and binds to respective properties. However we got another problem - if an invalid date is entered then the form displays 2 errors - one comes from the databinder and another one from validator, which finds that required field is null. Not very user-friendly. I started thinking and found no better solution than to simply write a custom tag which displays errors:
package com.anydoby.util;
import javax.servlet.jsp.JspException;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.support.BindStatus;
import org.springframework.web.servlet.tags.form.ErrorsTag;
import org.springframework.web.servlet.tags.form.TagWriter;
/**
* This extension to spring tags will show only first error of every field it displays.
* @author SergeyZ
*
*/
public class FirstErrorTag extends ErrorsTag {
private static final long serialVersionUID = 716823632508216981L;
@Override
protected void renderDefaultContent(TagWriter tagWriter) throws JspException {
tagWriter.startTag(getElement());
writeDefaultAttributes(tagWriter);
String delimiter = ObjectUtils.getDisplayString(evaluate("delimiter", getDelimiter()));
BindStatus status = getBindStatus();
String path = status.getPath();
if (StringUtils.isBlank(path)) {
String[] errorMessages = status.getErrorMessages();
for (int i = 0; i < errorMessages.length; i++) {
String errorMessage = errorMessages[i];
if (i > 0) {
tagWriter.appendValue(delimiter);
}
tagWriter.appendValue(getDisplayString(errorMessage));
}
tagWriter.endTag();
} else {
String[] errorMessages = status.getErrorMessages();
if (errorMessages.length > 0) {
tagWriter.appendValue(getDisplayString(errorMessages[0]));
}
tagWriter.endTag();
}
}
}and the taglib.tld
<!DOCTYPE taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/j2ee/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>a</short-name>
<uri>http://anydoby.com/tags</uri>
<tag>
<name>errors</name>
<tag-class>com.anydoby.util.FirstErrorTag</tag-class>
<body-content>JSP</body-content>
<description>Renders field errors in an HTML 'span' tag.</description>
<variable>
<name-given>messages</name-given>
<variable-class>java.util.List</variable-class>
</variable>
<attribute>
<name>path</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Path to errors object for data binding</description>
</attribute>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Standard Attribute</description>
</attribute>
<attribute>
<name>htmlEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Enable/disable HTML escaping of rendered values.</description>
</attribute>
<attribute>
<name>delimiter</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Delimiter for displaying multiple error messages. Defaults to the br tag.</description>
</attribute>
<attribute>
<name>cssClass</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Equivalent to "class" - HTML Optional Attribute</description>
</attribute>
<attribute>
<name>cssStyle</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Equivalent to "style" - HTML Optional Attribute</description>
</attribute>
<attribute>
<name>lang</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Standard Attribute</description>
</attribute>
<attribute>
<name>title</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Standard Attribute</description>
</attribute>
<attribute>
<name>dir</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Standard Attribute</description>
</attribute>
<attribute>
<name>tabindex</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Standard Attribute</description>
</attribute>
<attribute>
<name>onclick</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>ondblclick</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onmousedown</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onmouseup</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onmouseover</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onmousemove</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onmouseout</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onkeypress</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onkeyup</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>onkeydown</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>HTML Event Attribute</description>
</attribute>
<attribute>
<name>element</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Specifies the HTML element that is used to render the enclosing errors.</description>
</attribute>
</tag>
</taglib>The tag is used just as regular Spring errors tag, but it displays only one error it it’s a field error.
First published on the old anydoby.com blog on 2008-04-11.