graniteds.orgCommunity Documentation

Chapter 10. Client-Side Validation API (JSR 303)

10.1. Integration with code generation tools (Gfx)
10.2. Using the FormValidator class

The "Bean Validation" specification (aka JSR-303) standardizes an annotation-based validation framework for Java. It provides an easy and powerful way of processing bean validations, with a pre-defined set of constraint annotations, allowing to arbitrarily extend the framework with user specific constraints.

It's of course possible to use it in a Java client application, and Bean Validation constraint annotations can be put on any bean with property accessors. JavaFX however doesn't provide any simple way to integrate data binding and validation. GraniteDS provide a simple client component named FormValidator that helps bridging Bean Validation and JavaFX data binding.

The Bean Validation specification was primarily intended to be used with Java entity beans. GraniteDS code generation tools replicate your Java model into a JavaFX-enabled model and may be configured in order to copy validation annotations. All you have to do is to change the default org.granite.generator.as3.DefaultEntityFactory to org.granite.generator.as3.BVEntityFactory.

With the Ant task, use the entityfactory attribute as follow in your build.xml:



<gfx entityfactory="org.granite.generator.as3.BVEntityFactory" ...>
    ...
</gfx>
        

With the Maven plugin, add the entityfactory option in the plugin configuration:



<configuration>
    <generatorToUse>graniteds23</generatorToUse>
    <baseOutputDirectory>${project.build.directory}/generated-sources</baseOutputDirectory>
    <outputDirectory>${basedir}/src/main/java</outputDirectory>
    <translators>
        <translator>com.wineshop.admin=com.wineshop.admin.client</translator>
    </translators>
    <extraOptions>
        <tide>true</tide>
        <uid>uid</uid>
        <transformer>org.granite.generator.javafx.JavaFXGroovyTransformer</transformer>
        <as3typefactory>org.granite.generator.javafx.DefaultJavaFXTypeFactory</as3typefactory>
        <entityFactory>org.granite.generator.as3.BVEntityFactory</entityFactory>
        <outputEnumToBaseOutputDirectory>false</outputEnumToBaseOutputDirectory>
    </extraOptions>
    ...
</configuration>

        

Then, provided that you have a Java entity bean like this one:



@Entity
public class Person {
    @Id @GeneratedValue
    private Integer id;
    
    @Basic
    @Size(min=1, max=50)
    private String firstname;
    
    @Basic
    @NotNull(message="You must provide a lastname")
    @Size(min=1, max=255)
    private String lastname;
    // getters and setters...
}
        

... you will get this generated ActionScript3 code:



@JavaFXObject
public class PersonBase implements Identifiable, Lazyable, DataNotifier {
    ...
    private StringProperty firstnameProperty = new SimpleStringProperty(this, "firstname");
    private StringProperty lastnameProperty = new SimpleStringProperty(this, "lastname");
    
    public void setFirstname(String value) {
        this.firstname = value;
    }
    @Size(min=1, max=50, message="{javax.validation.constraints.Size.message}")
    public String getFirstname() {
        return this.firstname;
    }
    public void setLastname(String value) {
        this.lastname = value;
    }
    @NotNull(message="You must provide a last name")
    @Size(min=1, max=255, message="{javax.validation.constraints.Size.message}")
    public function get lastname():String {
        return this.lastname;
    }
    ....
}
        

You may then use the standard Bean Validation mechanism to validate your client JavaFX bean.

This works for plain Java beans and entity beans.

With the FormValidator component, you can easily add validation to any part of a UI form: the FormValidator performs validation on the fly whenever the user enters data into user inputs and automatically displays error messages when these data are incorrect, based on constraint annotations placed on the bean properties. This however requires that the form uses JavaFX data binding to propagate updates between UI components and data beans.

Example (using the Person bean introduced above and bidirectional bindings):



private Person person = new Person();
private VBox personForm;
private FormValidator personFormValidator;
public void buildForm() {
    person = new Person();
    
    personForm = new VBox();
    TextField textFirstname = new TextField();
    TextField textLastname = new TextField();
    
    personForm.getChildren().add(textFirstname);
    personForm.getChildren().add(textLastname);
    
    texteFirstname.textProperty().bindBidirectional(person.firstnameProperty());
    texteLastname.textProperty().bindBidirectional(person.lastnameProperty());
    
    personFormValidator = new FormValidator();
    personFormValidator.setForm(formPerson);
}
public void validate() {
    if (!personFormValidator.validate(person)) {
        // Data is invalid
        return;
    }
        
    // Data is valid, do something useful...
}
        

In the above sample, the personForm form uses two bidirectional bindings between the text inputs and the person bean. Each time the user enter some text in an input, the value of the input is copied into the bean and triggers a validation.

Note that JavaFX does not provide any standard way of displaying the error messages, so you are basically on your own to choose whatever look & feel you prefer (tooltip, basic text...).

To allow displaying these messages at the right time, the form validator dispatches two particular events on the target form: ValidationResultEvent.VALID and ValidationResultEvent.INVALID. The event also contains a list of more detailed error messages of type ValidationResult.

Here a very basic example that simply changes the border color of the inputs to red when the input data in invalid.



personForm.addEventHandler(ValidationResultEvent.ANY, new EventHandler<ValidationResultEvent>() {
    @Override
    public void handle(ValidationResultEvent event) {
        if (event.getEventType() == ValidationResultEvent.INVALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: red");
        else if (event.getEventType() == ValidationResultEvent.VALID)
            ((Node)event.getTarget()).setStyle("-fx-border-color: null");
    }
});
        

The global validation of the person bean will be performed when FormValidator.validateEntity() is called. However, class-level constraint violations cannot be automatically associated to an input, and these violations prevent the fValidator.validateEntity() call to succeed while nothing cannot be automatically displayed to the user.

To solve this problem, two options are available:

The second option let you do whatever you want with these unhandled violations. You can display the error messages anywhere and get any useful information from the ConstraintViolation objects.