Call for feature requests: GWT validation framework

13 views
Skip to first unread message

nikola...@gmail.com

unread,
Nov 24, 2006, 8:58:38 AM11/24/06
to Google Web Toolkit
Hello,

everyone - including me - needs client-side validation in his GWT
driven projects, but there's no direct support for it included in the
core libraries.

So I'd like to kick off a small open source project to create a simple,
light weight, JS-compilable validation framework.

It won't be the silver bullet, but at least it will be able to validate
date fields, mandatory data etc. and display the appropriate error
messages.

We are currently in the project definition phase and try to define the
project's scope. So there are still many ways to submit your ideas and
participate.

The current project home is http://code.google.com/p/gwt-validator/
(well, it's still empty)

How can you help us ?
- think about how a validation framework can support you in your
projects
- define requirements
- help us coding and testing (we called it 'lightweight', so there's
not much work to do ;-))

For now, just add all your input as replies to this posting. We'll
collect and review it later.

Regards
Nikolaus

mP

unread,
Nov 25, 2006, 2:55:25 PM11/25/06
to Google Web Toolkit
Whats wrong with using java to validate ? How are the validation rules
going to be defined ?

bg

unread,
Nov 25, 2006, 3:45:57 PM11/25/06
to Google Web Toolkit
I was thinking about this, and was thinking that validation should be
done by sharing a validation class between the client and server so
that the same validation is being done on both ends.

Robert kebernet Cooper

unread,
Nov 25, 2006, 3:57:21 PM11/25/06
to Google Web Toolkit
I agree. Sharing client and server side validation is key here, but I
tend to look at it a little differently.

Ideally your client side code should be manipulating a model and the
validation should take place on the model and then be able to run on
the same model objects as they come to the server. The big thing you
need is a way for something in the model layer -- or data binding -- to
through validation exceptions and have them rendered in a reasonable
way to the user. The lack of reflection-type functionality in GWT makes
this kind of thing a bit hard, though. I may give it some thought later
today.

nikola...@gmail.com

unread,
Nov 25, 2006, 6:50:38 PM11/25/06
to Google Web Toolkit

mP schrieb:

> Whats wrong with using java to validate ? How are the validation rules
> going to be defined ?

Validation rules should be defined in the code, but in a declarative
way. We don't want to use any XML for this. Everything should be
lightweight and compilable into JS.

nikola...@gmail.com

unread,
Nov 25, 2006, 7:03:04 PM11/25/06
to Google Web Toolkit
Having a consistent validation mechanism both on the client and on the
server may be attractive in the first thought, especially in web
applications which are mostly form based and very limited in the way
that they interact with the user.

However in GWT with it's rich set of widgets and it's fine grained
interaction I see a stronger difference between the validation
requirements on the client and on the server.

In our opinion the model whose classes need to be validated will often
differ between the client and the server which makes it difficult to
share the same set of validation rules. With the lack of reflection and
common infrastructure like xml parsers etc. it will be difficult to
define those rules in a declarative way.

Furthermore there are far more possibilities to interact with the user,
display error messages, manipulate widgets etc. than in traditional web
applications. Catching a validation exception and displaying an
unordered list of error messages is not sufficient for this interaction
model. With GWT we have focus listeners, validation not only when the
user clicks submit, etc.

Robert kebernet Cooper

unread,
Nov 25, 2006, 7:23:01 PM11/25/06
to Google Web Toolkit
""Furthermore there are far more possibilities to interact with the
user,
display error messages, manipulate widgets etc. than in traditional web
applications. Catching a validation exception and displaying an
unordered list of error messages is not sufficient for this interaction
model. With GWT we have focus listeners, validation not only when the
user clicks submit, etc""

Well, that seems like a matter of technique. Again, if you are
validating at the model level, validation doesn't occur except when the
model is changed. The other thing to keep in mind if that what happens
on a validation error needs to be non-blocking and usually non
intrusive. If selecting "Other" on a select box should make the "Other
value" text area required, then it should just highlight with a
possible message, not break the users flow. You just need a way to kick
that out.


I have been toying around with implementing some basic reflection-style
stuff with a compile-time code generator that builds name-based hashes
to JSNI formatted calls, and have had some success with it. You do end
up with a much fatter cache download because it in effect, short
circuits the uncalled method pruning with the compiler, so you have to
use it on targeted object graphs, but it can be done.

Adrian Bosworth

unread,
Nov 25, 2006, 7:34:55 PM11/25/06
to Google Web Toolkit
The way I have approached this is that the Validation model needs to be
very flexible, extensible, can be fired as a result of any browser
event but can also act server side or seemlessly via RPC. The model I
have used is pretty straightforward and very lightweight.... (all of
the classes and interfaces in the following description are included at
the end of the post).

The main Validator interface extends all of the main interesting form
field Listeners in GWT - KeyboardListener, ClickListener,
FocusListener, LoadListener, ChangeListener.

The model has the concept of a Validatable data object. This is
another interface that provides methods for registration of Validator
implementations and methods for Validators to call back the Validatable
to notify it that it is either valid or invalid, including the
provision of a suitable message.

So when the model is built data objects are assigned suitable Validator
implementations. When the model is sent to the client a view will be
constructed to allow the user to edit it - in other words Widgets will
be used to edit our Validatable items. So, we can consider these
Widgets to be ValidatableOwners - another interface that provides a
single method to retrieve the Validatable model object. Because the
Validatable extends a number of Listeners we assign it to be a listener
to all of the matching listeners of the type of Widget.

However we don't necessarily want to validate on every single listener
callback so we include the concept of sinkable / unsinkable validation
execution points - basically the same idea as sinking / unsinking
browser events. An AbstractValidator implements all of the Listener
methods but checks against the bitfield of sunk execution points to
decide whether to go ahead with call to perform the validation.

All of the GWT listener methods pass in the Widget that is the subject
of the event. Remember that our Widgets in this model implement the
ValidatableOwner method. So, the AbstractValidator gets hold of the
Validatable from the ValidationOwner Widget and calls an abstract
validate(Validatable) method.

(The ValidatableOwner interface could be removed from the design if you
don't mind instantiating a new Validator for every Validatable in the
model)

At this point we are free to implement any kind of specialisation of
the AbstractValidator. These can be very simple - see the
RequiredValueValidator example class. All that is required is to sink
the appropriate execution points and in the validate method make sure
to call back the Validatable passed in to tell it whether or not it is
valid.

At the point that the model has been told that it is invalid by one or
more Validators it is then free to do whatever it wants with that
information. In my application I fire out a PropertyValidationEvent
that tells all listeners on that model data object that the model is
invalid. For instance this could include a surrounding panel that then
displays the validation messages next to the Widget that is viewing the
data. Likewise if the save button is a PropertyListener on the model
then it can respond by disabling itself.

By the same token because the Validators are owned by the model the
Save button can ask the model to validate itself before it goes
anywhere near requesting that the model by returned to the server to
save.

Because the model data objects (the Validatables) contain references to
the Validators that are going to act on them you are also free to tell
the model to validate itself at any point you want to - client or
server side. One of the nice features is that implementations of the
Validator.validate(Validatable) method can do absolutely anything you
want including remote procedure calls. (Although one thing I haven't
given much thought to is what happens if you use a Validator that uses
RPC but call it server side - any thoughts anybody?)

Incidentally the reason for making the Validatable.xxxValue methods all
return objects rather than the equivalent primary types is to ensure
that unset values can be validated in the model - eg a text box is
being used to get an integer value but the user has not yet typed a
value. Also I don't consider those methods to be an exhaustive set,
I'm sure others would be useful - eg dateValue etc

So in summary - the intention of the design is that the model
encapsulates the validation framework but does not need to have any
knowledge of how it is being validated. Likewise the view of the model
data object (the Widget) only has to know to set any Validator that the
model give it as all of the set of Listeners that it supports; and
because of the execution point sinking mechanism does not have to
concern itself about which Listeners are important or what the
validator is intending to do.

As for the ordering of the validation as mentioned by Nikolaus, well
there are a couple of possibilities. Firstly the current model could
cover this by adding validators in order of importance, or
alternatively you could extend this framework to include an importance
level on the validation callbacks to the model.

/**
* @author Adrian Bosworth
*/
public interface Validator extends KeyboardListener, FocusListener,
ChangeListener,
ClickListener, LoadListener, IsSerializable {

// The execution points of validators. A validator can request to
be fired on any
// of these events.
// Note: Not all widgets support all of these events
public static final int VALIDATE_ON_KEY_PRESS = 1;
public static final int VALIDATE_ON_KEY_DOWN = 2;
public static final int VALIDATE_ON_KEY_UP = 4;
public static final int VALIDATE_ON_FOCUS = 8;
public static final int VALIDATE_ON_FOCUS_LOST = 16;
public static final int VALIDATE_ON_CHANGE = 32;
public static final int VALIDATE_ON_CLICK = 64;
public static final int VALIDATE_ON_LOAD = 128;


/**
* Adds a set of execution points to be sunk (acted on) by this
Validator.
* The supplied int should be a bit
* field of any of the statically defined execution points.
* @param executionPoints the bit field of execution points.
*/
public void sinkExecutionPoints(int executionPoints);

/**
* Removes a set of execution points from those sunk by this
Validator.
* The specified int should be a bit field of any of the statically
defined
* execution points.
* @param executionPoints the execution points to remove from the
validator
*/
public void unsinkExecutionPoints(int executionPoints);

/**
* Determine when the validation should occur. The value is a bit
field that should
* be compared against the statically defined values.
* @return a bit field representing the requested validation
execution points.
*/
public int getExecutionPoints();

/**
* Validate the property, getting the property to fire an
appropriate validation event
* @param validatable the subject of the validation
*/
public void validate(Validatable validatable);
}


/**
* @author Adrian Bosworth
*/
public abstract class AbstractValidator implements Validator {

/**
* The execution point - a bit field
*/
private int executionPoints = 0;

// javadoc inherited
public void sinkExecutionPoints(int executionPoints) {
this.executionPoints = this.executionPoints | executionPoints;
}

// javadoc inherited
public void unsinkExecutionPoints(int executionPoints) {
this.executionPoints = this.executionPoints & executionPoints;
}

// javadoc inherited
public int getExecutionPoints() {
return executionPoints;
}

/**
* Check whether the execution point contains the specified mask
* @param mask the mask to check is specified by the execution
point
* @return true if the execution point specifies the execution
* indication by the mask
*/
private boolean containsExecutionPoint(int mask) {
return (executionPoints & mask) == mask;
}

/**
* Validate the Validatable owned by this Widget (ValidatableOwner)
* @param widget the ValidatableOwner that has the Validatable that
* we want to validate
*/
private void validate(Widget widget) {
ValidatableOwner owner = (ValidatableOwner) widget;
Validatable validatable = owner.getValidatable();
validate(validatable);
}

// javadoc inherited
public void onKeyDown(Widget widget, char c, int i) {
if (containsExecutionPoint(Validator.VALIDATE_ON_KEY_DOWN)) {
validate(widget);
}
}

// javadoc inherited
public void onKeyPress(Widget widget, char c, int i) {
if (containsExecutionPoint(Validator.VALIDATE_ON_KEY_PRESS)) {
validate(widget);
}
}

// javadoc inherited
public void onKeyUp(final Widget widget, char c, int i) {
if (containsExecutionPoint(Validator.VALIDATE_ON_KEY_UP)) {
DeferredCommand.add(new Command() {
public void execute() {
validate(widget);
}
});
}
}

// javadoc inherited
public void onFocus(Widget widget) {
if (containsExecutionPoint(Validator.VALIDATE_ON_FOCUS)) {
validate(widget);
}
}

// javadoc inherited
public void onLostFocus(Widget widget) {
if (containsExecutionPoint(Validator.VALIDATE_ON_FOCUS_LOST)) {
validate(widget);
}
}

// javadoc inherited
public void onChange(Widget widget) {
if (containsExecutionPoint(Validator.VALIDATE_ON_CHANGE)) {
validate(widget);
}
}

// javadoc inherited
public void onClick(Widget widget) {
if (containsExecutionPoint(Validator.VALIDATE_ON_CLICK)) {
validate(widget);
}
}

// javadoc inherited
public void onLoad(Widget widget) {
if (containsExecutionPoint(Validator.VALIDATE_ON_LOAD)) {
validate(widget);
}
}

// javadoc inherited
public void onError(Widget widget) {
// do nothing, not interested in this
}
}

/**
* ValidatableOwner instances hold a reference to a Validatable which
is usually
* a data model object of some kind from which we want to extract a
value to
* validate.
*
* @author Adrian Bosworth
*/
public interface ValidatableOwner {

/**
* Get the Validatable model object from this ValidatableOwner
* @return the Validatable that this owner holds a reference to
*/
public Validatable getValidatable();
}


/**
* The model data objects would implement this interface...
* @author Adrian Bosworth
*/
public interface Validatable {

/**
* Get the List of Validators being used to validate this
Validatable
* @return the List of Validators being used to validate this
Validatable
*/
public List getValidators();

/**
* Add a Validator to the List of Validators being used to validate
this Validatable
* @param validator
*/
public void addValidator(Validator validator);

/**
* Remove the Validator from the List of Validators being used to
validate
* this Validatable
* @param validator the Validator to remove from the List of
Validators
*/
public void removeValidator(Validator validator);

/**
* Notify this Validatable that it has been deemed invalid by the
specified Validator.
* @param validator the Validator that determined this Validatable
was not valid
* @param message the reason that the Validatable was deemed to be
invalid
*/
public void setInvalid(Validator validator, String message);

/**
* Notify this Validatable that it has been deemed valid by the
specified Validator.
* @param validator the Validator that determined this property was
valid
*/
public void setValid(Validator validator);

/**
* Check whether this Validatable is valid
* @return true if this Validatable is valid
*/
public boolean isValid();

/**
* Get the label for the Validatable (the name displayed against
the property
* in the user interface)
* @return the label for this Validatable
*/
public String getLabel();

/**
* Get the value of data item as a String
* @return the value of the data item as String
*/
public String stringValue();

/**
* Get the value of data item as an int
* @return the value of the data item as an int
*/
public Integer integerValue();

/**
* Get the value of data item as an boolean
* @return the value of the data item as a boolean
*/
public Boolean booleanValue();

/**
* Get the value of the data item as an Object
* @return the value of the data item as an Object
*/
public Object objectValue();
}


/**
* A very simple validator example that sinks validation on the key
* up and focus lost events
*
* @author Adrian Bosworth
*/
public class RequiredValueValidator extends AbstractValidator {

/**
* Create a new RequiredValueValidator that will validate a
* property on the Key Up and Focus Lost events of any widgets
* that are viewing the Validatable model value
*/
public RequiredValueValidator() {
// do the validation after every key press
// and when the focus is lost
sinkExecutionPoints(Validator.VALIDATE_ON_KEY_UP);
sinkExecutionPoints(Validator.VALIDATE_ON_FOCUS_LOST);
}

// javadoc inherited
public void validate(Validatable validatable) {
String value = validatable.stringValue();
if (value == null || "".equals(value)) {
validatable.setInvalid(this, "A value is required for " +
validatable.getLabel());
} else {
validatable.setValid(this);
}
}
}


/**
* An example Widget using this validation framework
*/
public class MyTextBox extends TextBox implements ValidatableOwner {

public MyTextBox(Validatable model) {
List validators = model.getValidators();
for (Iterator iterator = validators.iterator();
iterator.hasNext();) {
Validator validator = (Validator) iterator.next();
addChangeListener(validator);
addClickListener(validator);
addKeyboardListener(validator);
addFocusListener(validator);
}
}
}

nikola...@gmail.com

unread,
Nov 25, 2006, 7:35:19 PM11/25/06
to Google Web Toolkit
Robert kebernet Cooper wrote:

> Well, that seems like a matter of technique. Again, if you are
> validating at the model level, validation doesn't occur except when the
> model is changed.

We tend to think that the client and the server side model will differ
in most cases. Our applications usually consist of at least three
functional layers:

- GWT application, running in the user's web browser, here we need
'client model' validation
- Proxy layer, Servlet for handling the RPC calls, transforms the
client model into the server model and vice versa, here we need 'server
model' validation
- Application logic layer (Spring et al), here we need 'server model'
validation

> The other thing to keep in mind if that what happens
> on a validation error needs to be non-blocking and usually non
> intrusive. If selecting "Other" on a select box should make the "Other
> value" text area required, then it should just highlight with a
> possible message, not break the users flow. You just need a way to kick
> that out.

I agree. Our current approach is to non-block the workflow by default.
Its in the developers responsibility to handle the validation error.
The validator just signals it and might perform basic user alerting
(highlighting widgets, focus change etc.).

> I have been toying around with implementing some basic reflection-style
> stuff with a compile-time code generator that builds name-based hashes
> to JSNI formatted calls, and have had some success with it. You do end
> up with a much fatter cache download because it in effect, short
> circuits the uncalled method pruning with the compiler, so you have to
> use it on targeted object graphs, but it can be done.

Reflection would be nice in GWT, but as long as there's no standardized
way to do it or work around it's absence we try to keep away from it.
gwt-validator won't be the silver bullet, but it should help it's
developers in 90% of all cases.
However, your approach is nice.

nikola...@gmail.com

unread,
Nov 25, 2006, 7:49:31 PM11/25/06
to Google Web Toolkit
Adrian, your approach is very interesting and you provided new points
of view to our project definition.

Especially the way you bridged the gap between the user interaction
part and the model validation is interesting. However we won't be that
intrusive to GWTs widget classes, so wrapping them is no option for us.
But there will be other ways to work around this.

Great work, thank you.

Robert kebernet Cooper

unread,
Nov 25, 2006, 8:39:46 PM11/25/06
to Google Web Toolkit

On Nov 25, 7:35 pm, "nikolaus.r...@gmail.com" <nikolaus.r...@gmail.com>
wrote:

> way to do it or work around it's absence we try to keep away from it.
> gwt-validator won't be the silver bullet, but it should help it's
> developers in 90% of all cases.
> However, your approach is nice.

Yeah, I started a while back with implementing
java.beans.PropertyChangeSupport a long time ago, which has no
reflection dependency. I am trying to put together a version of
java.beans.Introspector and related stuff with the reflection
precompiler stuff. The big problem is where do I draw the line for a
"release". That whole Beans API is friggin huge and getting to the
"this is enough for my needs" is much easier than "This is a good
implementation of the API."

Reply all
Reply to author
Forward
0 new messages