Thursday, October 26, 2006

Form Validation Made Simple

The next thing I wanted to figure out was the "Wicket Way" of doing form validation. I've made the mistake in the past of going off and doing things on my own when an existing framework already provided a particular best practice for accomplishing the task. Fortunately, Wicket makes it quite easy to add standard form validators and to add your own.

I decided to start with my "headline" field from my simple article form. A headline can be from 1 to 80 characters, must be provided, and must not contain HTML code. I decided to bite off the hard part first: Wicket does not provide a validator class for rejecting input that contains HTML.

I created a new package, net.spatula.news.ui.validators, figuring I'll want to reuse these and keep them all in one place. I looked at one of the standard validators in the Wicket API to see what it extended and/or implemented, and it turns out that it both extended AbstractValidator and implemented IValidator. Mind you, I still think it's dumb to start Interface class names with an I, especially since nobody who does this does it consistently. Nobody shortens "Abstract" to "A" or spells out the word "Interface." Nobody starts every normal class name with a "C" or the word "Class." This is all superfluous fluff. I know by the fact that the name follows the "implements" keyword that it's a bloody interface. But I digress.

I asked Eclipse to create for me a class called NoHtmlValidator, and have it extend AbstractValidator and implement (gag) IValidator. I was pleased to see that all I had to do was supply the method body for public void validate(FormComponent component). Easy.

FormComponents have a method called getInput(), which returns the value you might get from request.getParameter("name"). It's the String value of the component's data. Fortunately for me, a TextField is just one value, so for now, this will suffice. I coded up something really simple:


public void validate(FormComponent component) {
String data = component.getInput();
Pattern pattern = Pattern.compile(".*<[-\\w]+[^>]+>.*", Pattern.DOTALL);
Matcher matcher = pattern.matcher(data);
if (matcher.matches()) {
error(component);
}
}


I'll refactor the Pattern to be a static member of the class later, so it doesn't have to be compiled every time. As you can see, the match is pretty dumb, but if HTML is detected, I call error(component). Notice that I don't actually give the error message here. It turns out that Wicket wants to get your error text from a resource (.properties) file.

Having had a little experience with Wicket so far, I figured that surely this meant that the name of the resource file it would want would be the class name of my page, followed by .properties. Sure enough, that's what it is looking for. Inside this file, Wicket wants a line like this:

formName.componentName.ValidatorClassName=Message to use when validation error occurs

After fighting with it initially, I found that the formName is the name you used when creating the form component. It is NOT the form's class name (if you created a separate form class). Now you might be thinking, "you're going to end up having to create one of these for every single field that doesn't allow HTML. You'll have messages coming out your ears in this file!"

Not so! Wicket provides this fantastic way of generalizing your messages. You can create a properties file for your application name (which unsurprisingly uses the ApplicationClassName.properties as a name) and say something like this:

NoHtmlValidator=HTML is not allowed for ${label}.

Now any time the NoHtmlValidator issues an error in the application, if there isn't a more specific error for that field, the generic "HTML is not allowed for ${label}." is used. ${label} is substituted with the name of the form component.

But Nick, you say, sometimes I use weird names for my components, and I would rather send a more friendly version of the name to the end user! For example, I might want to say "the headline" instead of "headline." Wicket provides for this too. Inside your PageClassName.properties file, you can specify user-friendly names for your components:

formName.componentName=friendly name

For example:

articleForm.headline=the headline

Now that we've created the error, how do we get it on the page? There are two good ways. You can either put all your feedback at the top of the page, or you can put your errors adjacent the form components to which they're related.

The first method is the easiest. In your HTML page, you create a span or a paragraph with a wicket:id as a placeholder for your error messages. For example:

<span wicket:id="messages">Errors and messages go here</span>

Then you add that component to your page. Inside your page class:

add(new FeedbackPanel("messages"));

That's really all you have to do. When a validator calls error(component), the error will show up in a list where your placeholder is in the HTML.

But suppose you'd rather put the headline errors with the headlines. This turns out to be fairly simple as well. There's a subclass of FeedbackPanel called ComponentFeedbackPanel. As you might imagine, you need to put a span or paragraph tag next to your component in the HTML and give it a wicket ID, just as you would with a regular FeedbackPanel. The only difference is that you're putting it where you want it to appear.

Next, add the ComponentFeedbackPanel to your form. Here's how all of this stuff looks in my code:


TextField headline = new TextField("headline", new PropertyModel(article, "headline"));
headline.add(new NoHtmlValidator());
headline.setRequired(true);
headline.add(StringValidator.lengthBetween(1, 80));
add(headline);
add(new ComponentFeedbackPanel("headlineErrors", headline));


That's all there is to it. The messages now show where they belong.

One other thing I discovered is that the RequiredValidator is deprecated in favor of setRequired, but a field that fails to pass that validation still uses the resource text for RequiredValidator.

Want that error message to show up in red? Add this to your page's CSS:

.feedbackPanelERROR { color: red }

Labels:


Comments:
Excellent tips! I'll definitely use them.
 
I couldnt follow this article
Something is missing
 
Where do we place the properties file if we choose the generic validation approach?
 
In the package with your Application. If your application is Foo.java, then your application-wide properties file is Foo.properties.
 
Thanks for the instant reply!! Never got that in my whole life!! not even from my useless lecturers!!

and after 2 days of very hard work!! it finally worked!!

Thank you SO MUCH

but i have a question if you know... if we extend the AbstractValidator in an inner class of some class... how would we reference it?
the same if we override these methods in an anonymous class e.g.

component.add(new NumberValidator() {
@Override
protected void onValidate(IValidatable validatable) {
....
}
This is just if you know...
Thanks again,
 
I think you should be able to override getResourceKey() in the anonymous inner class to give it a name that could be looked up in a properties file. See the Javadoc for AbstractValidator.

I would probably not recommend doing it this way. For one thing, chances are if you validate something in one place, you're going to want the same validation capability elsewhere eventually, so it's better to have these as regular classes. For another, it may confuse people who are accustomed to the resource key always being the classname... someone may see the name in the properties file and go looking for a regular class of that name and not find it.

A potential happy medium might be to use named inner classes and then a resource key like MyFormClass.MyValidatorClass might work out of the box... if it didn't you could override getResourceKey so that it would.
 
Thanks again Nick

It all goes back to what my lecturer likes :)
and he likes ugly anonymous classes!! so your first suggestion did work but without overriding the resource method.. rather reporting the error as

error(validatable, "SomeValidator");

HannaH
 
Thanks for this post. It helped me understand the ComponentFeedbackPanel.
 
What if validator has to be associated with more then one error message. For example DateRangeValidator could have messages "Start Date is not defined", "End Date is not defined", "End Date is invalid"...etc...Based on the example you provided it looks like it could be only one message per validator.
 
Wicket gives you the option of including variables in your messages as well. Look at AbstractValidator#error(IValidatable validatable, java.util.Map vars).

You can create resource like:
DateRange=${which} is not defined
daterange.startdate=Start Date

and then

Map vars = new HashMap();
vars.put("which", getString("daterange.startdate"));
error(validatable, vars);

in your validator.
 
Thank you Nick ! This is very helpful.


What if i want my error messages to come from someplace else ? Database for example ?
What comes to mind is to have in a property file
DateRange=${message}

Map vars = new HashMap();
vars.put("message", getErrorFromDB(SOME_ERROR_ID));
error(validatable, vars);

But is there a more elegant way ? Do I HAVE TO have error for a validator defined in a property file ?



Another, question....How do I control the display of multiple error messages in[span wicket:id="messages"]Errors and messages go here[/span] For example "," separated, each on a separate line, etc

thanks!

Vicky
 
For something like that, I would suggest implementing a custom Localizer for your Application. Look at IResourceSettings.html#getStringResourceLoaders() and IStringResourceLoader. You could write your own IStringResourceLoader that knows how to query a database for a String resource (optionally caching it) and then add it to your application settings. You could keep the default one and add your own such that if the resource wasn't found in a properties file, the database got queried.
 
Thanks!!!!
 
Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]