1
votes

This question follows on from my earlier one, Inline validation using JSR-303 in GWT, part 1: Validation on TextBox fails. Why? in which I describe a failed first attempt to use JSR-303 for inline validation, and ask for help in understanding why it didn't work.

The main documentation I'm using is the GWT validation dev guide and JSR-303 spec.

My second attempt moves the constraint definitions into an interface:

import javax.validation.constraints.Pattern;
public interface FooValidation {
    @Pattern(regexp="^[0-9]+$", message="Foo: expected digits.")
    public String getFoo();
}

MyValidatorFactory now specifies that the FooValidation interface is to be validated

@GwtValidation(FooValidation.class)
public interface GwtValidator extends Validator {
}

and my TextBox implements the interface in which the JSR-303 constraints are defined:

public class FooWidget extends TextBox implements FooValidation {
    public String getFoo() { 
        return getValue();  // from TextBox
    }
}

The field is placed in the form using UiBinder:

<my:FooWidget ui:field="foo"></my:FooWidget>

and included in my form widget in the usual way:

@UiField
FooWidget foo;

The constraints can then be checked onBlur, for example:

@UiHandler("foo")
void onBlur(BlurEvent ev) {
    // Need to cast (code smell?) to FooValidation, 
    // because that is the class set up for validations
    Set<ConstraintViolation<FooValidation>> violations =
                              validator.validate((FooValidation)foo);
    for (ConstraintViolation<FooValidation> violation : violations) {
        // TODO - report violation.getMessage() to user
    }
}

Now, this seems to work (based on very limited smoke testing, yesterday!). But the approach feels a bit laboured. I've introduced two new 'artefacts' (FooWidget and FooValidation) just to service the validation mechanism, which goes against the cut of Occam's Razor! Am I missing something simpler? Before I go ahead and implement a load of validations using this pattern, I'd be very interested to learn of any pitfalls, and of other people's experience using JSR-303 for inline validation in GWT.

I expect I can refine the above approach, and tease out some general purpose abstractions, but it would be good to know if I've missed something before continuing along that path.

1

1 Answers

0
votes

As this question has remained unanswered, I'm posting an answer that reflects what we decided to do.

It was pointed out here by @Thomas Broyer that "GWT-Validation is almost unmaintained". So, we have decided to grow our own, borrowing the idea that when a validator performs validation, it returns a set of Violations, but also adding control over when validators are triggered.

Here's an example of code that uses our home-grown validation framework. We have a form in which the user specifies the date and time of outbound and inbound journeys. We want to constrain the outbound time to be at most 15 minutes in the past, and to be before the inbound time (actually, that's a slight over simplification, but it's hopefully enough to understand the requirements behind this example).

// Check that we're not returning from our destination before we get there.
// Create a Validator that can get DateTime from two widgets 
// (both implement HasValue<DateTime>) so that it may compare 
// two DateTime values:
JourneyDateTimeValidator journeyDateTimeValidator 
    = new JourneyDateTimeValidator(outboundDateTime, inboundDateTime);

//  Add a constraint with an error message for when it is violated:
journeyDateTimeValidator.addConstraint(
        new JourneyDateTimeValidator.Constraint(
            MESSAGES.mustNotLeaveBeforeArriving()));

// Create a Validator to check that a DateTime is not in the past:
ValueValidator<DateTime> outboundValidator 
    = new ValueValidator<DateTime>(outboundDateTime);

// outboundDateTime must be no more than 15 mins in the past.
final int N = 15;

// Add a constrint to the validator, 
// to check the DateTime value is no more than N minutes in the past.
// The NotInThePastConstraint is an example of a special-purpose constraint,
// but, of course there are many general-purpose ones.  We can add many
// Constraints to the same Validator.  This is similar specification
// of a list of Constraints in JSR-303.
outboundValidator.addConstraint(new NotInThePastConstraint(N*60*1000, // ms
            MESSAGES.mustNotBeInThePast()));

// outboundValidityIndicator is a GWT Label that can display 
// a Tick, or a Cross with an error message, or nothing.
// We set up Triggers, each of which can have a list of Validators 
// to be run when the Trigger fires. 
// The ValidityIndicator refreshes its display 
// based on the list of Violations returned by the Validators.

outboundValidityIndicator.getTriggers().onValueChange(outboundDateTime)
    .addValidator(outboundValidator)
    .addValidator(journeyDateTimeValidator);

// We also want the same Validators triggered when inboundDateTime changes:
outboundValidityIndicator.getTriggers().onValueChange(inboundDateTime)
    .addValidator(outboundValidator)
    .addValidator(journeyDateTimeValidator);

// We keep a list of all ValidatityIndicators in our form, 
// so we can get them all to refresh at once
// (we may do this, for example, onMouseOver of the form's submit button):
formValidatorList.add(outboundValidityIndicator);