GValidation is a validation plugin for Griffon - A Grails like application framework for developing desktop applications in Groovy. Like most part of the Griffon framework GValidation's syntax and usage closely resemble its cousin Grails' validation and constraints support. However the similarity pretty much stops here, since Griffon model are very different from Grails' concept of model. In Grails model usually refers to domain classes that are bound to database, however in Griffon models are usually just mere POGOs thus this plugin is designed to work with Griffon models as well as any POGOs.
GValidation is written purely in Groovy while retaining most of the syntax of Grails constraints support.
GValidation plugin depends on the following libraries, and will automatically add them to your application once the plugin is installed.
Apache Commons Lang 2.5 Apache Commons Validator 1.3.1 Jakarta ORO 2.0.8 |
griffon install-plugin validation |
Please use the Bug Tracker to report any bug you find
Once this plugin is installed, an additional annotation @Validatable will become available to you. Once annotated your model object will have a dynamic field errors and a dynamic method validate injected similar to Grails domain class. The errors field encapsulates all errors generated on a particular model, and the validate method performs validation based on the constraints you configure. Here is a typical usage scenario:
@Validatable
class PersonModel {
@Bindable String name
@Bindable String email
@Bindable String blog
static constraints = {
name(blank: false)
email(blank: false, email: true)
blog(url: true)
}
}
|
if (!model.validate()) {
doLater {
// display error messages
...
}
} else {
doLater {
// do the real job
...
}
}
|
All built-in and custom validators provided in this plugin follow the same error message code naming convention. Model specific error message code is generated with the following format:
<modelClass>.<field>.<validator>.message |
So as in the previous example the blank constraint on email field will generate error message code:
personModel.email.blank.message |
Each validator built-in or custom also has a global default error message code associated with in the following format:
default.<validator>.message |
So as shown in the previous example you can provide a global error message for blank validator by using:
default.blank.message |
You can then retrieve the error code and default error code from the Error object using the following fields respectively:
error.errorCode error.defaultErrorCode |
Since v0.8 release validation plugin now can automatically trigger validation for Griffon model beans if a realTime flag is set to true in @Validatable annotation.
@Validatable(realTime=true)
class MyModel{
....
}
|
The real time validation feature is implemented relying on the property change support therefore any property value change, for example triggered by bind{}, will invoke the validation logic associated with that particular property. The actual validation is performed in a separate thread using GriffonApplication.execAsync() before it switches back to EDT for error rendering. Currently this flag only works with Griffon MVC model beans if it is applied to a regular POGO object the flag will be ignored.
GValidation plugin is shipped with a set of built-in validator or constraints as shown in the above example.
Check if the given String field is blank.
Example:
name(blank: false) |
Check if the given field is a valid credit card number.
Exmple:
creditCardNumber(creditCard: true) |
Check if the given field is a valid email address.
Example:
email(email: true) |
Check if the given field is a valid network address. It can be either a host name or an IP address. (This is GValidation specific constraint not available in Grails.)
Example:
hostServer(inetAddress: true) |
Check if the given field is contained in the defined list.
Example:
city(inList: ['New York', 'Toronto', 'London']) |
Check if the field matches with the given regular expression.
Example:
login(matches:"[a-zA-Z]+") |
Ensures a value's size does not exceed the given maximum value. This constraint works with collection, array, as well as string.
Example:
children(maxSize:25) firstName(maxSize:20) |
Ensures a value does not exceed the given maximum value.
Example:
age(max:new Date()) price(max:999F) |
Ensures a value's size does not fall below the given minimum value.
Example:
children(minSize:25) firstName(minSize:2) |
Ensures a value does not fall below the given minimum value.
Example:
age(min:new Date()) price(min:0F) |
Ensures that a property is not equal to the specified value
Examples:
login(notEqual:"Bob") |
Allows a property to be set to null. By default Grails does not allow null values for properties.
Examples:
age(nullable:true) |
Uses a Groovy range to ensure that a property's value occurs within a specified range. Set to a Groovy range which can contain numbers in the form of an IntRange, dates or any object that implements Comparable and provides next and previous methods for navigation.
Examples:
age(range:18..65) createdOn(range: new Date()-10..new Date()) |
Uses a Groovy range to restrict the size of a collection or number or the length of a String. Sets the size of a collection or number property or String length.
Examples:
children(size:5..15) |
To validate that a String value is a valid URL. Set to true if a string value is a URL. Internally uses the org.apache.commons.validator.UrlValidator class.
Examples:
homePage(url:true) |
Adds custom validation to a field. Set to a closure or block to use for custom validation. A single or no parameter block receives the value, a two-parameter block receives the value and object reference. The closure can return: null or true to indicate that the value is valid false to indicate an invalid value and use the default message code
Examples:
// Simple custom validator
even( validator: {
return (it % 2) == 0
})
// Custom validator with access to the object under validation
password1( validator: {
val, obj ->
obj.properties['password2'] == val
})
// Custom validator with custom error
magicNumber( validator: {
val, obj ->
def result = checkMagicNumber()
if(!result)
obj.errors.rejectValue('magicNumber', 'customErrorCode')
return result
})
|
Many of the above explanation were borrowed directly from Grails reference guide
Since the constraints are defined using static fields following Grails convention, no real inheritance can be implemented. However since 0.6 release Validation plugin will basically copy the parent class' constraints to the child before performing validation, thus additionally you can also override the parent constraint in the child class. See the following example:
@Validatable
class ServerParameter {
@Bindable String serverName
@Bindable int port
@Bindable String displayName
def beforeValidation = {
setDisplayName "${serverName}:${port}"
}
static constraints = {
serverName(nullable: false, blank: false)
port(range: 0..65535)
displayName(nullable: false, blank: false)
}
}
@Validatable
class ProtocolSpecificServerParameter extends ServerParameter{
@Bindable String protocol
def beforeValidation = {
setDisplayName "${protocol}://${serverName}:${port}"
}
static constraints = {
protocol(blank: false, nullable: false)
}
}
|
In the above example, the ProtocolSpecificServerParameter will not only inherent ServerParameter's serverName and port fields but also their associated constraints. The only restriction you need to be aware of is if the parent constraint generates error for a certain condition then the overriding child constraint has to generate error as well. In other words, validation plugin does not allow error-hiding by using constraint override in the child class, similar to the method exception treatment during inheritance within Java.
The validator mentioned above allows you to specify a closure as a simple custom constraint easily and quickly however there is no easy way to reuse the closure in other scenarios, hence you will be forced to rewrite the validator each time you use them which is inconvenient and a violation of the DRY principle. Since version 0.3, inspired by Grails Custom Constraint plugin, GValidation plugin now provides you ways to define reusable custom constraints in Griffon.
Once you upgrade the plugin to 0.3 version and above a new artifact type will be added to your Griffon application called Constraint. You can create new constraints by using the new script added by the plugin:
griffon create-constraint <package>.<constraint-name> |
Which in turn will create a Groovy classes under griffon-app/constraints folder with a single method validate defined where you can perform your reusable custom validation logic. A simple custom constraint typically looks like this:class MagicConstraint {
/**
* Generated message
*
* @param propertyValue value of the property to be validated
* @param bean object owner of the property
* @param parameter configuration parameter for the constraint
* @return true if validation passes otherwise false
*/
def validate(propertyValue, bean, parameter) {
if (!parameter)
return true
return propertyValue == 42
}
}
|
Once created a custom constraint pretty much behaves exactly like a built-in constraint, you can easily invoke them in your model by following the simple naming convention, following the above example you can apply the constraint on any field in your model by using the following declaration:
@Validatble
class DemoModel{
...
@Bindable int magicNumber
static constraints = {
...
magicNumber(magic: true)
...
}
|
The GValidation plugin will also take care of the error message code generation for you, as soon as your validate method returns false the plugin will automatically generate error for the appropriate field with the error message code generated by following the same convention as the built-in ones:
<modelClass>.<field>.<validator>.message // specific error message code default.<validator>.message // default global error message code |
As mentioned before once installed the plugin will enhance all your model object to have the additional validate() method, once invoked this method will perform a validate-all executing all constraints on the given model, typical usage scenario:
model.validate()
...
if(model.hasErrors()){
// notify user
...
}
|
Originally proposed by Andres Almiray, since v0.3 GValidation now offers capability to perform validation on only a selected number of fields in the model instead of all. Here is a typical single field validation usage scenario:
model.validate('name')
...
if(model.hasErrors()){
// notify user
...
}
|
Here is how to perform validation on a multiple fields:
model.validate(['name', 'email'])
...
if(model.hasErrors()){
// notify user
...
}
|
Inspired by Rails before_validation callback, now GValidation provides a similar pre-validation callback to give model developer a chance to manipulate data right before validation. Here is an example how this kind of callback is defined:
class ServerModel {
@Bindable String serverName
@Bindable int port
@Bindable String stringForm
def beforeValidation = {
setStringForm "${serverName}:${port}"
}
...
}
|
Although GValidation is not built on top of Spring Validation framework as the Grails constraints do, it still tries to maintain some sort of API consistency when it comes to error generation. However GValidation only provide a subset implementation of the Spring Error objects in Groovy. For API details please see the Errors and Simple Error classes.
Other than the return value of the validation() method itself, once the validation is complete you can use the hasErrors() method that was dynamically injected on your model to check if there is any validation error. For example:
model.validate()
// do something else
...
if(model.hasErrors()){
notifyUser(model.errors)
}
|
You can generate global error at the instance level by calling the reject() method on the errors property.
model.errors.reject('error.code')
|
Or reject a specific field using the rejectValue() method.
model.errors.rejectValue('field', 'error.code')
|
Later on you can iterate through errors using Groovy iterator
model.errors.each{error->
// do something with the error
}
|
Since 0.4 release the dynamic errors field has been enhanced to be Bindable which means you can now directly bind it to your component. It is especially handy when building error notification component such as in the built-in ErrorMessagePanel. Here is how the binding can be achieved in the view with the built-in panel:
container(new ErrorMessagePanel(messageSource),
id: 'errorMessagePanel', constraints: NORTH,
errors: bind(source: model, 'errors'))
|
GValidation is shipped with a simple generic errorMessages widget to help you display error message easily. Of course you can build your own error message feedback component, it is fairly easy to do that, check out the source code for the built-in ErrorMessagePanel for more details. To use the built-in error panel first declare it in your view:
The following example works with v0.4+ binary, if you are using the older version you need to update the errors manually in ErrorMessagePanel
panel(id:'demoPanel'){
borderLayout()
errorMessages(constraints: NORTH, errors: bind(source: model, 'errors')
// the rest of your view
}
|
Later in your controller you can update the error messages:
def doSomething = {evt = null ->
if (!model.validate()) {
doLater {
// do something
}
} else {
doOutside {
// do something interesting
}
}
}
|
Since 0.4 release now you can enhance any POGO class in your application by adding the @Validatable annotation at the class level, then Groovy AST transformation will take care of the rest.
import net.sourceforge.gvalidation.annotation.Validatable
@Validatable
class AnnotatedModel {
String id
String email = " "
static constraints = {
id(nullable: false)
email(email: true)
}
}
|
This kind of annotated classes will go through essentially the same enhancement as any model class. The only difference is that annotated class is enhanced during build time using AST transformation vs. runtime enhancement as what happen to the model instances.
One of the common challenge we face when building UI using any GUI framework is how to effectively and easily notify the user about errors. Ideally a validation framework should not just help developer define constraints and validation logic but also handle the presentation of the error message automatically with little coding involved. With this vision in mind Error Renderer was created.
Error Renderer can be declared easily by using the additional synthetic attribute 'errorRenderer' introduced in 0.7 release. See the following example:
textField(text: bind(target: model, 'email'), errorRenderer:'for: email, styles: [highlight, popup]') |
In the above example, two error renderers were declared for the textField widget for the 'email' field in the model. Basically what it means is that if any error was detected for the email field in the model two types of error renderer will be activated to display the error(s). The styles portion of the configuration is optional. If no renderer style is defined, by default highlight renderer will be used. Currently three types of error renderer styles are implemented, I will go through them quickly here.
This renderer basically change the background color of the component to pink. Mostly it is used for text based input fields. Here is a screen shot of the rendering result.
-

This renderer display the error message associated with the error using a tooltip-like popup box. Here is a screen shot of the rendering result.
-

This is an invisible renderer that does not render anything itself but switch the component visible attribute on when the error is detected. It is commonly used to display initially invisible custom component when error occurs. This renderer is used in combination of the new errorIcon widget also introduced in this release. Here is a screen shot of it used with errorIcon.
-

This is basically the old wine in a new bottle. This widget is essentially identical to the ErrorMessagePanel class existed since v0.2 however it is now implemented as a widget to make it easier to use. Usage:
errorMessages(constraints: NORTH, errors: bind(source: model, 'errors')) |
Screen Shot:

As mentioned before this widget is mainly used in combination with the onWithError renderer. This icon widget is initially invisible and will only be turned on by the onWithError renderer. Usage:
errorIcon(errorRenderer:'for: creditCard, styles: [onWithError]') |
Screen Shot: 