Monday, May 14, 2012

Beyond Hello - Input validation

Input validation

If you have gotten Beyond Hello it should be time to add some input validation.
It should not be possible for the user to produce invalid input. 
For example, it should not be possible to press the Save  button before all input fields have been filled in correctly. Vaadin has a number of built-in validators that I will use to build simple a Screen Validator.

Initially, the OK button should be disabled. It should only come alive when the two fields Name and Email are filled in correctly.
We will require name to be between 3 and 32 characters long, and email should be on the form "a@b.c" where "b.c" is some domain.
When both fields are filled in, the OK button should become enabled, and if we go back and erase or change the field contents, the Screen Validator should maintain the state of the OK button, turning it on and off in response to the validation state.

To know whether a screen is valid, we need to know if one of the widgets in it is invalid. In other words, the screen is valid if all the input fields it contains are valid.
This means that the Validator must know which widgets to validate.
Let's create a class to keep track of a widget's validation state. This is not needed to manage the OK button state, but can be useful in providing hints to the user, and it can be useful for debugging.

Here, there is a 'status line' telling the user which field failed validation. It shows the name of the field and its validation message. We create a class to hold this information:
private class ValidationState{
String name;
String validationMessage="";
Boolean valid = true;
public ValidationState(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getValidationMessage() {
return validationMessage;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public void setValidationMessage(String validationMessage) {
this.validationMessage = validationMessage;
}
The contents of the status line is name + ":" + validationMessage.
public class ScreenValidator {
private class ValidationState{
                 ...
}
private HashMap<Field, ValidationState> hashMapField = new HashMap<Field, ValidationState>();
Each field that is added to the ScreenValidator gets stored in a HashMap.
We also need a callback structure to let the parent widget know whether our screen is valid or not.
To this end, we define a BooleanEvent and a BooleanEventHandler.
package com.example.vaadin.events;
public class BooleanEvent {
private Boolean value;
public BooleanEvent(Boolean value) {
this.value = value;
}
public Boolean getValue() {
return value;
}
}
This event type simply holds a true/false value, we will let true indicate valid, false will indicate not valid. Then we define the event handler:
package com.example.vaadin.events;
public abstract class BooleanEventHandler {
public abstract void eventOccurred(BooleanEvent evt);
}
We now have all the building blocks we need in order to put it all together.
package com.example.vaadin.view;
import com.example.vaadin.events.BooleanEvent;
import com.example.vaadin.events.BooleanEventHandler;
import com.vaadin.ui.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
public class ScreenValidator {
private class ValidationState{
String name;
String validationMessage="";
Boolean valid = true;
public ValidationState(String name) {
this.name = name;
}
public String getName() {
if (name == null){
return "No Name";
}
return name;
}
public String getValidationMessage() {
if (validationMessage == null){
return "";
}
return validationMessage;
}
public void setValid(Boolean valid) {
this.valid = valid;
}

public void setValidationMessage(String validationMessage) {
this.validationMessage = validationMessage;
}
}
private HashMap<Field, ValidationState> hashMapField = new HashMap<Field, ValidationState>();
private Boolean valid = false;
private ArrayList<BooleanEventHandler> listValidHandler = new ArrayList<BooleanEventHandler>();
private String validationError = "";
public void addValidationHandler(BooleanEventHandler h) {
listValidHandler.add(h);
}
public void removeValidationHandler(BooleanEventHandler h) {
listValidHandler.remove(h);
}
void callbackValid() {
for (BooleanEventHandler h : listValidHandler) {
h.eventOccurred(new BooleanEvent(valid));
}
}
/**
* Add a component to the validator
* @param c The component you are adding validation for.
* @param name The name of the field, typically the same as the field's prompt
*/
public void addComponent(Field c, String name) {
hashMapField.put(c, new ValidationState(name));
}
public void removeAllComponents(){
hashMapField.clear();
}
/**
* Validate - Generate callback if change in  valid status
*/
public void validate() {
Boolean oldValid = valid; // Use this is you only want to callback on changes in validation status
valid = true;
validationError = null;
String strDebug = this.getClass().getSimpleName() + ".validate() :-";
for (Entry<Field, ValidationState> e : hashMapField.entrySet()) {
ValidationState validationState = e.getValue();
Boolean validField = true; // DEBUG
Field f = e.getKey();
if (!f.isVisible()) {
continue;
}
try {
f.validate();
} catch (Exception ex) {
if (validationError == null) {
validationError = validationState.getName() + ":" + ex.getMessage();
}
validationState.setValid(false);
validationState.setValidationMessage(ex.getMessage());
valid = false;
validField = false;
}
strDebug += "  " + validationState.getName() + ":" + f.getClass().getSimpleName() + ":" + f.toString() + ":" + validField;
}
strDebug += " valid=" + valid + ":" + validationError;
System.out.println(strDebug); // Track validation state
//if (oldValid != valid){ // Only call back if change
callbackValid();
//}
}
public Boolean isValid() {
return valid;
}
public String getValidationError() {
if (validationError == null){
return "";
}
return validationError;
}
}

To use the ScreenValidator, you first instantiate it, then add all the Fields you want validated.
screenValidator.addComponent(textFieldName, "Name");
screenValidator.addComponent(textFieldEmail, "Email");
 Now, ScreenValidator knows which Fields to validate.
To see it all in action, download the project from Ramsli.org