How to avoid duplicate code for constraint validat

2019-08-26 10:39发布

问题:

Integrity constraints should be defined in the model classes of a MVC application since they are part of the semantics of a model class (representing a business object type). However, constraints also have to be validated on user input (and on form submission button click) in the HTML5-form-based view code of the app. How can we avoid to duplicate the validation code and keep it in the model code of a JavaScript MVC app?

回答1:

Unfortunately, many MVC application development frameworks do not provide any support for maintaining the integrity constraints code in the model and use it for data validation both in the view (on input and on form submission) and in the model (before save).

I'll try to provide a sketch of the approach that I've developed to solve this multiple validation problem with the HTML5 forms API, and its setCustomValidity and checkValidity methods. Since it requires to execute the same code both in the view and in the model, it only works in a JavaScript MVC web app, where we have the same programming language (JavaScript) in both parts of the app, unlike in other (e.g. PHP od Java) web apps, where the backend model code is not in JavaScript.

Normally we have to validate property constraints only. It's natural, therefore, to write a class-level check function that validates all constraints for each property of a model class. For instance, the following check function makes sure that a value for the ISBN property of a Book object is a 10-digit string or a 9-digit string followed by "X":

Book.checkIsbn = function (isbn) {
  if (typeof(isbn) !== "string" || isbn.trim() === "") {
    return new RangeConstraintViolation("The ISBN must be a non-empty string!");
  } else if (!/\b\d{9}(\d|X)\b/.test( isbn)) {
    return new PatternConstraintViolation(
        'The ISBN must be a 10-digit string or a 9-digit string followed by "X"!');
  } else {
    return new NoConstraintViolation();
  }
};

The check function returns an instance of a ConstraintViolation class, which has a string-valued message property. In the HTML5 form providing the view (or UI), we add an event listener for input events to the form input field concerned for handling the validation on user input:

isbnInpEl.addEventListener("input", function () { 
  isbnInpEl.setCustomValidity( Book.checkIsbn( isbnInpEl.value).message);
});

This event listener invokes the setCustomValidity method, which has a string parameter. We provide the message created by our property check function as the argument for the invocation of setCustomValidity. If it's empty, this means the user input is valid. Otherwise, if it contains a non-empty constraint violation message, the HTML5 browser will take care of indicating the constraint violation to the user (e.g. by showing a red border for the input field and displaying the message).

For handling the validation on form submission button click, we check all input fields of the form by invoking the HTML5 checkValidity method on the form element object:

if (formEl.checkValidity()) {
  // save
  formEl.reset();
}

Finally, for checking the constraints in the model class before saving any new data, we may invoke the check functions in the property setters, as in

Book.prototype.setIsbn = function (isbn) {
  var validationResult = Book.checkIsbn( isbn);
  if (validationResult instanceof NoConstraintViolation) {
    this.isbn = isbn;
  } else {
    throw validationResult;
  }
};

You can find the full example code in my book Building Front-End Web Apps with Plain JavaScript.