8Implementing Constraint Validation in a Plain JS Web App

8.1Introduction

The minimal JavaScript front-end web application that we have discussed in Chapter 3 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app’s database. In this chapter, we show how to express integrity constraints in a JavaScript model class, and how to perform constraint validation both in the model part of the app and in the user interface built with HTML5.

We show how to perform responsive validation with the HTML5 form validation API113. Since using the new HTML5 input field types and validation attributes (like required, min, max and pattern) implies defining constraints in the UI, they are not really useful in a best-practice approach where constraints are only checked, but not defined, in the UI.

Consequently, we will not use the new HTML5 features for defining constraints in the UI, but only use two methods of the HTML5 form validation API:

  1. setCustomValidity, which allows to mark a form field as either valid or invalid by assigning either an empty string or a non-empty (constraint violation) message string;
  2. checkValidity, which is invoked on a form before user input data is committed or saved (for instance with a form submission); it tests, if all fields have a valid value.

In the case of two special kinds of attributes, having calendar dates or colors as values, it is desirable to use the new UI widgets defined by HTML5 for picking a date or picking a color (with corresponding input field types). Unfortunately, in 2017, the HTML5 date picker widget is still not supported by all major browsers.

8.2New Issues

Compared to the Minimal App114 discussed in Chapter 3 we have to deal with a number of new issues:

1.In the model code we have to add for every property of a class

a.a check function that can be invoked for validating the constraints defined for the property, and

b.a setter method that invokes the check function and is to be used for setting the value of the property.

2.In the user interface (view) code we have to take care of

a.responsive validation on user input for providing immediate feedback to the user,

b.validation on form submission for preventing the submission of flawed data to the model layer.

For improving the break-down of the view code, we introduce a utility method (in lib/ util.js) that fills a select form control with option elements the contents of which is retrieved from an entity table such as Book. instances. This method is used in the setupUserInterface method of both the updateBook and the deleteBook use cases.

Checking the constraints in the user interface (UI) on user input is important for providing immediate feedback to the user. But it is not safe enough to perform constraint validation only in the UI, because this could be circumvented in a distributed web application where the UI runs in the web browser of a front-end device while the application’s data is managed by a back-end component on a remote web server. Consequently, we need multiple constraint validation, first in the UI on input (or on change) and on form submission, and subsequently in the model layer before saving/sending data to the persistent data store. And in an application based on a DBMS we may also use a third round of validation before persistent storage by using the validation mechanisms of the DBMS. This is a must, when the application’s database is shared with other apps.

Our proposed solution to this multiple validation problem is to keep the constraint validation code in special check functions in the model classes and invoke these functions both in the UI on user input and on form submission, as well as in the create and update data management methods of the model class via invoking the setters. Notice that referential integrity constraints (and other relationship constraints) may also be violated through a delete operation, but in our single-class example we don’t have to consider this.

8.3Make a JavaScript Class Model

Using the information design model shown in Figure 7.2 above as the starting point, we make a JavaScript class model by performing the following steps:

1.Create a check operation for each (non-derived) property in order to have a central place for implementing all the constraints that have been defined for a property in the design model. For a standard identifier attribute, such as Book:: isbn, two check operations are needed:

a.A basic check operation, such as checkIsbn, for checking all basic constraints of the attribute, except the mandatory value and the uniqueness constraints.

b.An extended check operation, such as checkIsbnAsId, for checking, in addition to the basic constraints, the mandatory value and uniqueness constraints that are required for a standard identifier attribute.

The checkIsbnAsId operation is invoked on user input for the isbn form field in the create book form, and also in the setIsbn method, while the checkIsbn operation can be used for testing if a value satisfies the syntactic constraints defined for an ISBN.

2.Create a setter operation for each (non-derived) single-valued property. In the setter, the corresponding check operation is invoked and the property is only set, if the check does not detect any constraint violation.

This leads to the JavaScript class model shown on the right-hand side of the mapping arrow in the following figure.

Figure 8.1 From an information design model to a JS class model

Essentially, the JavaScript class model extends the design model by adding checks and setters for each property. The attached invariants have been dropped since they are taken care of in the checks. Property ranges have been turned into JavaScript datatypes (with a reminder to their real range in curly braces). Notice that the names of check functions are underlined, since this is the convention in UML for class-level (as opposed to instance-level) operations.

8.4Set up the Folder Structure Adding Some Library Files

The MVC folder structure of our validation app extends the structure of the minimal app by adding a lib folder containing the generic code libraries browserShims. js, errorTypes.js and util.js. Thus, we get the following folder structure:

We discuss the contents of the added files in the following sub-sections.

8.4.1Provide general utility functions and JavaScript fixes in library files

We add three library files to the lib folder:

  1. browserShims.js contains a definition of the string trim function for older browsers that don’t support this function (which was only added to JavaScript in ES5, defined in 2009). More browser shims for other recently defined functions, such as querySelector and classList, could also be added to browserShims.js.
  2. util.js contains the definitions of a few utility functions such as isNonEmptyString(x) for testing if x is a non-empty string.
  3. errorTypes.js defines classes for error (or exception) types corresponding to the basic types of property constraints discussed above: StringLengthConstraintViolation, MandatoryValueConstraintViolation, RangeConstraintViolation, IntervalConstraintViolation, PatternConstraintViolation, UniquenessConstraintViolation. In addition, a class NoConstraintViolation is defined for being able to return a validation result object in the case of no constraint violation.

8.4.2Create a start page

The start page index.html takes care of loading CSS page styling files with the help of the following two link elements:

Then it loads the following JavaScript files:

  1. browserShims.js and util.js from the lib folder, discussed in Section 4.1,
  2. errorTypes.js from the lib folder, defining exception classes.
  3. initialize. js from the src/ c folder, defining the app’s MVC namespaces, as discussed in Chapter 3
  4. Book. js from the src/m folder, a model class file that provides data management and other functions discussed in Section 5.

8.5Write the Model Code

The JavaScript class model shown on the right hand side in Figure 8.1 can be coded step by step for getting the code of the model layer of our JavaScript front-end app. These steps are summarized in the following section.

8.5.1Summary

  1. Code the model class as a JavaScript constructor function.
  2. Code the check functions, such as checkIsbn or checkTitle, in the form of class-level (’static’) methods. Take care that all constraints, as specified in the JavaScript class model, are properly coded in the check functions.
  3. Code the setter operations, such as setIsbn or setTitle, as (instance-level) methods. In the setter, the corresponding check operation is invoked and the property is only set, if the check does not detect any constraint violation.
  4. Code the add and remove operations, if there are any, as instance-level methods.

These steps are discussed in more detail in the following sections.

8.5.2Code the model class as a constructor function

The class Book is coded as a corresponding constructor function with the same name Book such that all its (non-derived) properties are supplied with values from corresponding key-value slots of a slots parameter.

In the constructor body, we first assign default values to the class properties. These values will be used when the constructor is invoked as a default constructor (without arguments), or when it is invoked with only some arguments. It is helpful to indicate the range of a property in a comment. This requires to map the platform-independent datatypes of the information design model to the corresponding implicit JavaScript datatypes according to the following table.

Table 8.1 Datatype mapping

Platform-independent datatype JavaScript datatype SQL
String Integer Decimal Boolean Date string number (int) number (float) boolean Date CHAR(n) or VARCHAR(n) INTEGER REAL, DOUBLE PRECISION or DECIMAL(p,s) BOOLEAN DATE

Since the setters may throw constraint violation errors, the constructor function, and any setter, should be called in a try-catch block where the catch clause takes care of processing errors (at least logging suitable error messages).

As in the minimal app, we add a class-level property Book. instances representing the collection of all Book instances managed by the app in the form of an entity table:

8.5.3Code the property checks

Code the property check functions in the form of class-level (’static’) methods. In JavaScript, this means to define them as method slots of the constructor, as in Book. checkIsbn (recall that a constructor is a JS object, since in JavaScript, functions are objects, and as an object, it can have slots).

Take care that all constraints of a property as specified in the class model are properly coded in its check function. This concerns, in particular, the mandatory value and uniqueness constraints implied by the standard identifier declaration (with {id}), and the mandatory value constraints for all properties with multiplicity 1, which is the default when no multiplicity is shown. If any constraint is violated, an error object instantiating one of the error classes listed above in Section 4.1 and defined in the file errorTypes.js is returned.

For instance, for the checkIsbn operation we obtain the following code:

Notice that, since isbn is the standard identifier attribute of Book, we only check the syntactic constraints in checkIsbn, but we check the mandatory value and uniqueness constraints in checkIsbnAsId, which itself first invokes checkIsbn:

We assume that all check functions and setters can deal both with proper data values (that are of the attribute’s range type) and also with string values that are supposed to represent proper data values, but have not yet been converted to the attribute’s range type. We take this approach for avoiding datatype conversions in the user interface (“view”) code. Notice that all data entered by a user in an HTML form field is of type String and must be converted (or de-serialized) before its validity can be checked and it can be assigned to the corresponding property. It is preferable to perform these type conversions in the model code, and not in the user interface code..

For instance, in our example app, we have the integer-valued attribute year. When the user has entered a value for this attribute in a corresponding form field, in the Create or Update user interface, the form field holds a string value. This value is passed to the Book. add or Book.update method, which invokes the setYear and checkYear methods. Only after being validated, this string value is converted to an integer and assigned to the year attribute.

8.5.4Code the property setters

Code the setter operations as (instance-level) methods. In the setter, the corresponding check function is invoked and the property is only set, if the check does not detect any constraint violation. Otherwise, the constraint violation error object returned by the check function is thrown. For instance, the setIsbn operation is coded in the following way:

There are similar setters for the other properties (title, year and edition).

8.5.5Add a serialization function

It is helpful to have an object serialization function tailored to the structure of an object (as defined by its class) such that the result of serializing an object is a human-readable string representation of the object showing all relevant information items of it. By convention, these functions are called toString(). In the case of the Book class, we use the following code:

8.5.6Data management operations

In addition to defining the model class in the form of a constructor function with property definitions, checks and setters, as well as a toString() serialization function, we also need to define the following data management operations as class-level methods of the model class:

  1. Book.convertRec2Obj and Book.retrieveAll for loading all managed Book instances from the persistent data store.
  2. Book. saveAll for saving all managed Book instances to the persistent data store.
  3. Book. add for creating a new Book instance and adding it to the collection of all Book instances.
  4. Book. update for updating an existing Book instance.
  5. Book. destroy for deleting a Book instance.
  6. Book. createTestData for creating a few sample book records to be used as test data.
  7. Book. clearData for clearing the book data store.

All of these methods essentially have the same code as in our minimal app discussed in Part 1, except that now

  1. we may have to catch constraint violations in suitable try-catch blocks in the procedures Book. convertRec2Obj, Book. add, Book.update and Book. createTestData;
  2. we create more informative status and error log messages for better observing what’s going on; and
  3. we can use the toString() function for serializing an object in status and error messages.

Notice that for the change operations add (create) and update, we need to implement an all-or-nothing policy: whenever there is a constraint violation for a property, no new object must be created and no (partial) update of the affected object must be performed.

When a constraint violation is detected in one of the setters called when new Book() is invoked in Book.add, the object creation attempt fails, and instead a constraint violation error message is created. Otherwise, the new book object is added to Book.instances and a status message is created, as shown in the following program listing:

When an object of a model class is to be updated, we first create a clone of it for being able to restore it if the update attempt fails. In the object update attempt, we only assign those properties of the object the value of which has changed, and we report this in a status log.

Normally, all properties defined by a model class, except the standard identifier attribute, can be updated. It is, however, possible to also allow updating the standard identifier attribute. This requires special care for making sure that all references to the given object via its old standard identifier are updated as well.

When a constraint violation is detected in one of the setters invoked in Book. update, the object update attempt fails, and instead the error message of the constraint violation object thrown by the setter and caught in the update method is shown, and the previous state of the object is restored. Otherwise, a status message is created, as shown in the following program listing:

Notice that optional properties, like edition, need to be treated in a special way. If the user doesn’t enter any value for them in a Create or Update user interface, the form field’s value is the empty string "". In the case of an optional property, this means that the property is not assigned a value in the add use case, or that it is unset if it has had a value in the update use case. This is different from the case of a mandatory property, where the empty string value obtained from an empty form field may or may not be an admissible value.

If there is a constraint violation exception, an error message is written to the log and the object concerned is reset to its previous state:

8.6Write the View Code

The user interface (UI) consists of a start page index.html that allows the user choosing one of the data management operations by navigating to the corresponding UI page such as retrieveAndListAllBooks.html or createBook.html in the app folder. The start page index.html has been discussed in Section 4.2.

We render the data management menu items in the form of buttons. For simplicity, we invoke the Book. clearData() and Book. createTestData() methods directly from the buttons’ onclick event handler attribute. Notice, however, that it is generally preferable to register such event handling functions with addEventListener(), as we do in all other cases.

8.6.1The data management UI pages

Each data management UI page loads the same basic CSS and JavaScript files like the start page index.html discussed above. In addition, it loads a use-case-specific view code file src/v/useCase.js and then sets the setupUserInterface procedure of the use-case as an event handler for the page load event, which takes care of initializing the use case when the UI page has been loaded.

8.6.2Initialize the app

For initializing the app, its namespace and MVC sub-namespaces have to be defined. For our example app, the main namespace is defined to be pl, standing for “Public Library”, with the three sub-namespaces m, v and c being initially empty objects:

We put this code in the file initialize. js in the c folder.

8.6.3Set up the user interface

For setting up the user interfaces of the data management use cases, we have to distinguish the case of “Retrieve/List All” from the other ones (Create, Update, Delete). While the latter ones require using an HTML form and attaching event handlers to form controls, in the case of “Retrieve/List All” we only have to render a table displaying all books, as in the case of the Minimal App115 discussed in Chapter 3.

For the Create, Update and Delete use cases, we need to add event listeners for:

  1. responsive validation on form field input events,
  2. handling the event when the user clicks (or pushes) the save (or delete) button,
  3. making sure the main memory data is saved when a beforeunload event occurs, that is, when the browser window/ tab is closed.

For the use case Create, we obtain the following code (in v/ createBook.js)::

Notice that for each input field we add a listener for input events, such that on any user input a validation check is performed because input events are created by user input actions such as typing. We use the predefined function setCustomValidity from the HTML5 form validation API for having our property check functions invoked on the current value of the form field and returning an error message in the case of a constraint violation. So, whenever the string represented by the expression Book. checkIsbn( formEl.isbn.value).message is empty, everything is fine. Otherwise, if it represents an error message, the browser indicates the constraint violation to the user by rendering a red outline for the form field concerned (due to our CSS rule for the :invalid pseudo class).

In addition to the event handlers for responsive constraint validation, we need two more event handlers:

While the validation on user input enhances the usability of the UI by providing immediate feedback to the user, validation on form data submission is even more important for catching invalid data. Therefore, the event handler handleSaveButton ClickEvent() performs the property checks again with the help of setCustomValidity, as shown in the following program listing:

By invoking checkValidity() on the form element, we make sure that the form data is only saved (by Book. add), if there is no constraint violation. After this handleSaveButtonClickEvent handler has been executed on an invalid form, the browser takes control and tests if the predefined property validity has an error flag for any form field. In our approach, since we use setCustomValidity, the validity.customError would be true. If this is the case, the custom constraint violation message will be displayed (in a bubble) and the submit event will be suppressed.

In the UI of the use case Update,which is handled in v/ updateBook.js,we do not have an input, but rather an output field for the standard identifier attribute isbn, since it is not supposed to be modifiable. Consequently, we don’t need to validate any user input for it. However, we need to set up a selection list (in the form of an HTML select element) allowing the user to select a learning unit in the first step, before its data can be modified. This requires to add a change event listener on the select element such that the fields of the UI can be filled with the data of the selected object.

There is no need to set up responsive validation for the standard identifier attribute isbn, but for all other form fields, as shown above for the Create use case.

The logic of the setupUserInterface method for the Delete use case is similar. We only need to take care that the object to be deleted can be selected by providing a selection list, like in the Update use case. No validation is needed for the Delete use case.

8.7Run the App and Get the Code

You can run the validation app116 from our server or download the code117 as a ZIP archive file.

8.8Possible Variations and Extensions

8.8.1Adding an object-level validation function

When object-level validation (across two or more properties) is required for a model class, we can add a custom validation function validate to it, such that object-level validation can be performed before save by invoking validate on the object concerned. For instance, for expressing the constraint defined in the class model shown in Figure 7.1, we define the following validation function:

When a validate function has been defined for a model class, it can be invoked in the create and update methods. For instance,

8.8.2Using implicit JS setters

Since ES5, JavaScript has its own form of setters, which are implicit and allow having the same semantics as explicit setter methods, but with the simple syntax of direct access. In addition to having the advantage of a simpler syntax, implicit JS setters are also safer than explicit setters because they decrease the likelihood of a programmer circumventing a setter by using a direct property assignment when instead a setter should be used. In other OOP languages, like Java, this is prevented by declaring properties to be ’private’. But JavaScript does not have this option.

The following code defines implicit setter and getter methods for the property

Notice that, also in the constructor definition, the internal property _title, used for storing the property value, is not used for setting/getting it, but rather the virtual property title:

We will start using implicit setter and getter methods, along with ES6 class definitions, in Chapter 11.

8.9Points of Attention

8.9.1Boilerplate code

An issue with the do-it-yourself code of this example app is the boilerplate code needed

  1. per model class for the storage management methods add, update, destroy, etc.;
  2. per model class and property for getters, setters and validation checks.

While it is good to write this code a few times for learning app development, you don’t want to write it again and again later when you work on real projects. In Volume 2, we present an approach how to put these methods in a generic form in a meta-class, such that they can be reused in all model classes of an app.

8.9.2Configuring the UI for preventing invalid user input

Some of the new HTML5 input field types (like number, date or color) are intended to allow web browsers rendering corresponding input elements in the form of UI widgets (like date or color pickers) that limit the user’s input options such that only valid input is possible. In terms of usability, it’s preferable to prevent users from entering invalid data instead of allowing to enter it and only then checking its validity and reporting errors. Unfortunately, and this is quite disappointing, many browsers do, in 2017, still not have reasonable widget implementations for these HTML5 input field types yet.

8.10Practice Projects

For the following projects, you have to take care of

  1. adding, for every property, a check function that validates the constraints defined for the property, and a setter method that invokes the check function and is to be used for setting the value of the property,
  2. performing validation before any data is saved in the add and update methods.

in the model code of your app, while In the user interface (“view”) code you have to take care of

  1. styling the user interface with CSS rules (for instance, by integrating a CSS library such as Yahoo’s Pure CSS),
  2. validation on user input for providing immediate feedback to the user,
  3. validation on form submission for preventing the submission of invalid data.

Make sure that your pages comply with the XML syntax of HTML5 by means of XHTML5 validation118(setting the validator field Preset to “HTML5 + SVG 1.1 + MathML 3.0”), and that your JavaScript code complies with our Coding Guidelines119 and its style is checked with JSHint120.

If you have any questions about how to carry out the following projects, you can ask them on our discussion forum121.

8.10.1Project 1Validate movie data

The purpose of the app to be built is managing information about movies. Like in the book data management app discussed in the tutorial, you can make the simplifying assumption that all the data can be kept in main memory. Persistent data storage is implemented with JavaScript’s Local Storage API.

The app deals with just one object type: Movie, as depicted in the following class diagram.

In this model, the following constraints have been expressed:

  1. Due to the fact that the movieId attribute is declared to be the standard identifier of Movie, it is mandatory and unique.
  2. The title attribute is mandatory, as indicated by its multiplicity expression [1], and has a string length constraint requiring its values to have at most 120 characters.
  3. The releaseDate attribute has an interval constraint: it must be greater than or equal to 1895–12–28.

Notice that the releaseDate attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1].

In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatype PositiveInteger to movieId, NonEmptyString to title, and Date to releaseDate. In our plain JavaScript approach, all these property constraints are coded in the model class within property-specific check functions.

You can use the following sample data for testing your app:

Table 8.2 Sample data about movies

Movie ID Title Release date
1 Pulp Fiction 1994–05–12
2 Star Wars
3 Casablanca 1943–01–23
4 The Godfather 1972–03–15

More movie data can be found on the IMDb website122.

8.10.2Project 2Validate country data

The purpose of the app to be built is managing information about countries. The app deals with just one object type: Country, as depicted in the following class diagram.

In this model, the following constraints have been expressed:

  1. Due to the fact that the name attribute is declared to be the standard identifier of Country, it is mandatory and unique.
  2. The name attribute has a string length constraint requiring its values to have at least 3 and at most 50 characters.
  3. The population attribute is mandatory, as indicated by the multiplicity expression [1] appended to the attribute name.
  4. The lifeExpectancy attribute is also mandatory and has an interval constraint: its values must be less than or equal to 100.

Notice that the militaryExpenditure attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1].

In addition to the constraints described in this list, there are the implicit range constraints defined by assigning the datatypes NonEmptyString to name, PositiveInteger to population, PositiveDecimal to lifeExpectancy and Percentage to militaryExpenditure (hint: a percentage value is a decimal number between 0 and 100). In our plain JavaScript approach, all these property constraints are coded in the model class within property-specific check functions.

You can use the sample data shown in the following table for testing your app.

Table 8.3 Sample data about countries

More data about countries can be found in the CIA World Factbook123.

8.11Quiz Questions

If you would like to look up the answers for the following quiz questions, you can check our discussion forum124. If you don’t find an answer in the forum, you may create a post asking for an answer to a particular question.

8.11.1Question 1: Validation in setter (1)

Complete the following setter code fragment:

8.11.2Question 2: Validation in setter (2)

Complete the following setter code fragment:

8.11.3Question 3: Methods to add in JS class model

Consider the simple information design model shown in the class diagram.

Which of the following methods have to be added to the Publisher class in a corresponding JavaScript class model? Select one or many:

Notice that an underlined method name denotes a class-level (“static”) method.

8.11.4Question 4: Implementing constraints

Consider the constraints specified for the object type Person in the class diagram.

Which of the following JavaScript code fragments implements these constraints?

Select one:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset