How to Improve Tapestry Errors Reporting When Using Bootstrap
Twitter recently released the Bootstrap framework. As I’m currently working on a recent project, I decided to give it a try on the Tapestry webapp.
So far, it proved to be easy to integrate. I introduced a Border
component like this one:
package com.kalixia.iot.console.components;
import org.apache.tapestry5.annotations.Parameter;
public class Border {
@Parameter(required = true, defaultPrefix = "literal")
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
<!DOCTYPE html>
<html lang="en" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
<head>
<meta charset="utf-8"/>
<title>My Website :: ${title}</title>
<meta name="description" content="My Website :: ${title}"/>
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap-1.1.1.min.css"/>
<link rel="shortcut icon" href="images/favicon.ico">
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="72x72" href="images/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="114x114" href="images/apple-touch-icon-114x114.png">
</head>
<body>
<t:body/>
</body>
</html>
Unfortunately, error messages where not displayed in a nice way. The end result was an ugly error message. After going through Bootstrap demo page, I was able to find out the required markup to add when displaying an error. The next part was to find out how to tweak Tapestry in such a way that it would render the errors using Bootstrap.
I found out that we are supposed to override the default ValidationDecorator
. The easy way to do this is within your Module class:
public static void contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration,
final Environment environment) {
MarkupRendererFilter validationDecorator = new MarkupRendererFilter() {
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
ValidationDecorator decorator = new BootstrapValidationDecorator(environment, writer);
environment.push(ValidationDecorator.class, decorator);
renderer.renderMarkup(writer);
environment.pop(ValidationDecorator.class);
}
};
configuration.override("DefaultValidationDecorator", validationDecorator);
}
You then need to add the following class which does all the required markup stuff:
/**
* Override default Tapestry validation decorator, so that CSS class of the "row"
* is added an "error" CSS class if the field is in error.
*
* @author Jerome Bernard
*/
public class BootstrapValidationDecorator extends BaseValidationDecorator {
private final Environment environment;
private final MarkupWriter markupWriter;
/**
* @param environment used to locate objects and services during the render
* @param markupWriter
*/
public BootstrapValidationDecorator(Environment environment, MarkupWriter markupWriter) {
this.environment = environment;
this.markupWriter = markupWriter;
}
@Override
public void insideField(Field field) {
if (inError(field))
addErrorClassToUpperDivElement();
}
@Override
public void afterField(Field field) {
if (inError(field)) {
markupWriter.element("span", "class", "help-inline");
markupWriter.cdata(getErrorMessage(field));
markupWriter.end();
}
}
private void addErrorClassToUpperDivElement() {
Element element = markupWriter.getElement();
do {
element = element.getContainer();
} while (!element.getAttribute("class").contains("clearfix"));
element.addClassName("error");
}
private boolean inError(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.inError(field);
}
private String getErrorMessage(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.getError(field);
}
}
Displaying an error for a form field is way nicer now: