Understanding and Implementing Information Management Concepts and Techniques

BackboneJS Front-End Web Apps with Cloud Storage Tutorial Part 2: Adding Constraint Validation

Learn how to perform constraint validation in a front-end web application built with the BackboneJS framework and the cloud storage service Parse.com

Gerd Wagner

Warning: This tutorial manuscript may contain errors and may still be incomplete. Please report any issue to Gerd Wagner at G.Wagner@b-tu.de.

This tutorial is also available in the following formats: PDF. You may run the example app from our server, or download the code as a ZIP archive file. See also our Web Engineering project page

This tutorial article, along with any associated source code, is licensed under The Code Project Open License (CPOL), implying that the associated code is provided "as-is", can be modified to create derivative works, can be redistributed, and can be used in commercial applications, but the article must not be distributed or republished without the author's consent.

2015-06-01

Revision History
Revision 0.220150601gw
create first revision
Revision 0.120140606gw
create first version

Table of Contents

Foreword
1. Integrity Constraints and Data Validation
1. Introduction
2. Integrity Constraints
2.1. String Length Constraints
2.2. Mandatory Value Constraints
2.3. Range Constraints
2.4. Interval Constraints
2.5. Pattern Constraints
2.6. Cardinality Constraints
2.7. Uniqueness Constraints
2.8. Standard Identifiers (Primary Keys)
2.9. Referential Integrity Constraints
2.10. Frozen Value Constraints
3. Responsive Validation
4. Constraint Validation in MVC Applications
5. Criteria for Evaluating the Validation Support of Frameworks
2. Constraint Validation in a BackboneJS/Parse App
1. Introduction
2. Constraint Validation in BackboneJS
3. Using the HTML5 Form Validation API
4. New Issues
5. Make a BackboneJS/Parse Data Model
6. Set up the folder structure and create four initial files
6.1. Style the user interface with CSS
6.2. Provide general utility functions and JavaScript fixes in library files
6.3. Create a start page
7. Write the Model Code
7.1. Summary
7.2. Encode the model class as an extension of Parse.Object
7.3. Encode the property checks
7.4. Add a serialization function
7.5. Data management operations
8. The View and Controller Layers
8.1. The data management UI pages
8.2. Initialize the app
8.3. Initialize the data management use cases
8.4. Set up the user interface
9. Run the App and Get the Code
10. Possible Variations and Extensions
10.1. Simplifying forms with implicit labels
11. Points of Attention
Glossary

List of Figures

1.1. The object type Person with an interval constraint
1.2. The object type Book with a pattern constraint
1.3. Two object types with cardinality constraints
1.4. The object type Book with a uniqueness constraint
1.5. The object type Book with a standard identifier declaration
2.1. A platform-independent design model with the class Book and two invariants
2.2. Deriving a BackboneJS data model from an information design model
2.3. The validation app's start page index.html.

Foreword

This tutorial is Part 2 of our series of five tutorials about building a front-end web application with BackboneJS using the cloud storage service provided by Parse.com. It shows how to build a front-end web application with constraint validation using the validation features of BackboneJS.

The minimal app that we have discussed in the first part of this 5-part tutorial has been limited to support the minimum functionality of a data management app only. However, it did not take care of preventing users from entering invalid data into the app's database. In this second part of the tutorial we show how to define property constraint checks within a BackboneJS model class, and how to perform constraint validation both in the model part of the app, using the validate method, and in the HTML5 user interface.

The simple form of a data management application presented in this tutorial takes care of only one object type ("books") for which it supports the four standard data management operations (Create/Read/Update/Delete). It extends the minimal app discussed in the Minimal App Tutorial by adding constraint validation (and some CSS styling), but it needs to be enhanced by adding further important parts of the app's overall functionality:

  • Part 3: Managing unidirectional associations between the object types Book and Publisher, assigning a publisher to a book, and between Book and Author, assigning authors to a book.

  • Part 4: Managing a bidirectional association between the object types Book and Publisher, assigning both a publisher to a book and books to a publisher, and between Book and Author, assigning both authors to a book and books to an autor.

  • Part 5: Handling subtype (inheritance) relationships in class hierarchies.

You may also want to take a look at Part 1 (our Minimal App Tutorial without CSS styling and without constraint validation), and at our Complete App Tutorial, which includes all five parts of the tutorial in one document, dealing with multiple object types ("books", "publishers" and "authors") and taking care of constraint validation, associations and subtypes/inheritance.

Chapter 1. Integrity Constraints and Data Validation

1. Introduction

For detecting non-admissible and inconsistent data and for preventing such data to be added to an application's database, we need to define suitable integrity constraints that can be used by the application's data validation mechanisms for catching these cases of flawed data. Integrity constraints are logical conditions that must be satisfied by the data in the model objects stored in the application's database. For instance, if an application is managing data about persons including their birth dates and their death dates, if they have already died, then we must make sure that for any person object with a death date, this date is not before that person object's birth date.

Since integrity maintenance is fundamental in database management, the data definition language part of the relational database language SQL supports the definition of integrity constraints in various forms. On the other hand, however, there is hardly any support for integrity constraints and data validation in common programming languages such as PHP, Java, C# or JavaScript. It is therefore important to take a systematic approach to constraint validation in web application engineering and to choose an application development framework that provides sufficient support for it.

Unfortunately, many web application development frameworks do not provide sufficient support for defining integrity constraints and performing data validation. Integrity constraints should be defined in one (central) place in an app, and then be used for validating data in other parts of the app, such as in the database and in the user interface.

HTML5 provides support for validating user input in an HTML-based user interface (UI). Here, the goal is to provide immediate feedback to the user whenever invalid data has been entered into a form field. We call this UI mechanism responsive validation.

2. Integrity Constraints

The visual language of UML class diagrams supports defining integrity constraints either with the help of special modeling elements, such as multiplicity expressions, or with the help of invariants shown in a special type of rectangle attached to the model element concerned. UML invariants can be expressed in plain English or in the Object Constraint Language (OCL). We use UML class diagrams for making design models with integrity constraints that are independent of a specific programming language or technology platform.

UML class diagrams provide special support for expressing multiplicity (or cardinality) constraints. This type of constraint allows to specify a lower multiplicity (minimum cardinality) or an upper multiplicity (maximum cardinality), or both, for a property or an association end. In UML, this takes the form of a multiplicity expression l..u where the lower multiplicity l is a non-negative integer and the upper multiplicity u is either a positive integer or the special value *, standing for unbounded. For showing property multiplicity constrains in a class diagram, multiplicity expressions are enclosed in brackets and appended to the property name in class rectangles, as shown in the Person class rectangle in the class diagram below.

Integrity constraints may take many different forms. For instance, property constraints define conditions on the admissible property values of an object. They are defined for an object type (or class) such that they apply to all objects of that type. We concentrate on the most important kinds of property constraints:

String Length Constraints

require that the length of a string value for an attribute is less than a certain maximum number, or greater than a minimum number.

Mandatory Value Constraints

require that a property must have a value. For instance, a person must have a name, so the name attribute must not be empty.

Range Constraints

require that an attribute must have a value from the value space of the type that has been defined as its range. For instance, an integer attribute must not have the value "aaa".

Interval Constraints

require that the value of a numeric attribute must be in a specific interval.

Pattern Constraints

require that a string attribute's value must satisfy a certain pattern defined by a regular expression.

Cardinality Constraints

apply to multi-valued properties, only, and require that the cardinality of a multi-valued property's value set is not less than a given minimum cardinality or not greater than a given maximum cardinality.

Uniqueness Constraints

require that a property's value is unique among all instances of the given object type.

Referential Integrity Constraints

require that the values of a reference property refer to an existing object in the range of the reference property.

Frozen Value Constraints

require that the value of a property must not be changed after it has been assigned initially.

In the following sections we discuss the different types of property constraints listed above in more detail. We also show how to express them in UML class diagrams, in SQL table creation statements and, as an example of how to do it yourself in a programming language, we also show how to express them in JavaScript model class definitions, where we encode constraint validations in class-level ("static") check functions. Any systematic approach also requires to define a set of error (or 'exception') classes, including one for each of the standard property constraints listed above.

2.1. String Length Constraints

The length of a string value for a property such as the title of a book may have to be constrained, typically rather by a maximum length, but possibly also by a minimum length. In an SQL table definition, a maximum string length can be specified in parenthesis appended to the SQL datatype CHAR or VARCHAR, as in VARCHAR(50).

2.2. Mandatory Value Constraints

A mandatory value constraint requires that a property must have a value. This can be expressed in a UML class diagram with the help of a multiplicity constraint expression where the lower multiplicity is 1. For a single-valued property, this would result in the multiplicity expression 1..1, or the simplified expression 1, appended to the property name in brackets. For example, the following class diagram defines a mandatory value constraint for the property name:

Whenever a class rectangle does not show a multiplicity expression for a property, the property is mandatory (and single-valued), that is, the multiplicity expression 1 is the default for properties.

In an SQL table creation statement, a mandatory value constraint is expressed in a table column definition by appending the key phrase NOT NULL to the column definition as in the following example:

CREATE TABLE persons(
  name  VARCHAR(30) NOT NULL,
  age   INTEGER
)

According to this table definition, any row of the persons table must have a value in the column name, but not necessarily in the column age.

In JavaScript, we can encode a mandatory value constraint by a class-level check function that tests if the provided argument evaluates to a value, as illustrated in the following example:

Person.checkName = function (n) {
  if (n === undefined) {
    return ...; // error message "A name must be provided!"
  } else {
    ...
  }
};

2.3. Range Constraints

A range constraint requires that a property must have a value from the value space of the type that has been defined as its range. This is implicitly expressed by defining a type for a property as its range. For instance, the attribute age defined for the object type Person in the class diagram above has the range Integer, so it must not have a value like "aaa", which does not denote an integer. However, it may have values like -13 or 321, which also do not make sense as the age of a person. In a similar way, since its range is String, the attribute name may have the value "" (the empty string), which is a valid string that does not make sense as a name.

We can avoid allowing negative integers like -13 as age values, and the empty string as a name, by assigning more specific datatypes as range to these attributes, such as NonNegativeInteger to age, and NonEmptyString to name. Notice that such more specific datatypes are neither predefined in SQL nor in common programming languages, so we have to implement them either in the form of user-defined types, as supported in SQL-99 database management systems such as PostgreSQL, or by using suitable additional constraints such as interval constraints, which are discussed in the next section. In a UML class diagram, we can simply define NonNegativeInteger and NonEmptyString as custom datatypes and then use them in the definition of a property, as illustrated in the following diagram:

In JavaScript, we can encode a range constraint by a check function, as illustrated in the following example:

Person.checkName = function (n) {
  if (typeof(n) !== "string" || n.trim() === "") {
    return ...; // error message "Name must be a non-empty string!"
  } else {
    ...
  }
};

This check function detects and reports a constraint violation if the given value for the name property is not of type "string" or is an empty string.

2.4. Interval Constraints

An interval constraint requires that an attribute's value must be in a specific interval, which is specified by a minimum value or a maximum value, or both. Such a constraint can be defined for any attribute having an ordered type, but normally we define them only for numeric datatypes or calendar datatypes. For instance, we may want to define an interval constraint requiring that the age attribute value must be in the interval [0,120]. In a class diagram, we can define such a constraint as an "invariant" and attach it to the Person class rectangle, as shown in Figure 1.1 below.

Figure 1.1. The object type Person with an interval constraint

The object type Person with an interval constraint

In an SQL table creation statement, an interval constraint is expressed in a table column definition by appending a suitable CHECK clause to the column definition as in the following example:

CREATE TABLE persons(
  name  VARCHAR(30) NOT NULL,
  age   INTEGER CHECK (age >= 0 AND age <= 120)
)

In JavaScript, we can encode an interval constraint in the following way:

Person.checkAge = function (a) {
  if (a < 0 || a > 120) {
    return ...; // error message "Age must be between 0 and 120!";
  } else {
    ...
  }
};

2.5. Pattern Constraints

A pattern constraint requires that a string attribute's value must match a certain pattern, typically defined by a regular expression. For instance, for the object type Book we define an isbn attribute with the datatype String as its range and add a pattern constraint requiring that the isbn attribute value must be a 10-digit string or a 9-digit string followed by "X" to the Book class rectangle shown in Figure 1.2 below.

Figure 1.2. The object type Book with a pattern constraint

The object type Book with a pattern constraint

In an SQL table creation statement, a pattern constraint is expressed in a table column definition by appending a suitable CHECK clause to the column definition as in the following example:

CREATE TABLE books(
  isbn   VARCHAR(10) NOT NULL CHECK (isbn ~ '^\d{9}(\d|X)$'),
  title  VARCHAR(50) NOT NULL
)

The ~ (tilde) symbol denotes the regular expression matching predicate and the regular expression ^\d{9}(\d|X)$ follows the syntax of the POSIX standard (see, e.g. the PostgreSQL documentation).

In JavaScript, we can encode a pattern constraint by using the built-in regular expression function test, as illustrated in the following example:

Person.checkIsbn = function (id) {
  if (!/\b\d{9}(\d|X)\b/.test( id)) {
    return ...; // error message like "The ISBN must be a 10-digit
                // string or a 9-digit string followed by 'X'!"
  } else {
    ...
  }
};

2.6. Cardinality Constraints

A cardinality constraint requires that the cardinality of a multi-valued property's value set is not less than a given minimum cardinality or not greater than a given maximum cardinality. In UML, cardinality constraints are called multiplicity constraints, and minimum and maximum cardinalities are expressed with the lower bound and the upper bound of the multiplicity expression, as shown in Figure 1.3 below, which contains two examples of properties with cardinality constraints.

Figure 1.3. Two object types with cardinality constraints

Two object types with cardinality constraints

The attribute definition nickNames[0..3] in the class Person specifies a minimum cardinality of 0 and a maximum cardinality of 3, with the meaning that a person may have no nickname or at most 3 nicknames. The reference property definition members[3..5] in the class Team specifies a minimum cardinality of 3 and a maximum cardinality of 5, with the meaning that a team must have at least 3 and at most 5 members.

It's not obvious how cardinality constraints could be checked in an SQL database, as there is no explicit concept of cardinality constraints in SQL, and the generic form of constraint expressions in SQL, assertions, are not supported by available DBMSs. However, it seems that the best way to implement a minimum (resp. maximum) cardinality constraint is an on-delete (resp. on-insert) trigger that tests the number of rows with the same reference as the deleted (resp. inserted) row.

In JavaScript, we can encode a cardinality constraint validation for a multi-valued property by testing the size of the property's value set, as illustrated in the following example:

Person.checkNickNames = function (nickNames) {
  if (nickNames.length > 3) {
    return ...; // error message like "There must be no more than 3 nicknames!"
  } else {
    ...
  }
};

With Java Bean Validation annotations, we can specify @Size( max=3) List<String> nickNames or @Size( min=3, max=5) List<Person> members.

2.7. Uniqueness Constraints

A uniqueness constraint requires that a property's value is unique among all instances of the given object type. For instance, for the object type Book we can define the isbn attribute to be unique in a UML class diagram by appending the keyword unique in curly braces to the attribute's definition in the Book class rectangle shown in Figure 1.4 below.

Figure 1.4. The object type Book with a uniqueness constraint

The object type Book with a uniqueness constraint

In an SQL table creation statement, a uniqueness constraint is expressed by appending the keyword UNIQUE to the column definition as in the following example:

CREATE TABLE books(
  isbn   VARCHAR(10) NOT NULL UNIQUE,
  title  VARCHAR(50) NOT NULL
)

In JavaScript, we can encode this uniqueness constraint by a check function that tests if there is already a book with the given isbn value in the books table of the app's database.

2.8. Standard Identifiers (Primary Keys)

An attribute (or, more generally, a combination of attributes) can be declared to be the standard identifier for objects of a given type, if it is mandatory and unique. We can indicate this in a UML class diagram with the help of a (user-defined) stereotype «stdid» assigned to the attribute isbn as shown in Figure 1.5 below.

Figure 1.5. The object type Book with a standard identifier declaration

The object type Book with a standard identifier declaration

Notice that a standard identifier declaration implies both a mandatory value and a uniqueness constraint on the attribute concerned.

Standard identifiers are called primary keys in relational databases. We can declare an attribute to be the primary key in an SQL table creation statement by appending the phrase PRIMARY KEY to the column definition as in the following example:

CREATE TABLE books(
  isbn   VARCHAR(10) PRIMARY KEY,
  title  VARCHAR(50) NOT NULL
)

In JavaScript, we cannot easily encode a standard identifier declaration, because this would have to be part of the metadata of the class definition, and there is no standard support for such metadata in JavaScript. However, we should at least check if the given argument violates the implied mandatory value or uniqueness constraints by invoking the corresponding check functions discussed above.

2.9. Referential Integrity Constraints

A referential integrity constraint requires that the values of a reference property refer to an existing object in the range of the reference property. Since we do not deal with reference properties in this part of the tutorial, we postpone the discussion of referential integrity constraints to the next part of our tutorial.

2.10. Frozen Value Constraints

A frozen value constraint defined for a property requires that the value of this property must not be changed after it has been assigned initially. This includes the special case of a read-only value constraint applying to mandatory properties that are initialized at object creation time. Typical examples of properties with a frozen value constraint are event properties, since the semantic principle that the past cannot be changed prohibits that the property values of past events can be changed.

In Java, a frozen value constraint can be enforced by declaring the property to be final. However, while in Java a final property must be mandatory, a frozen value constraint may also apply to an optional property.

In JavaScript, a read-only property can be defined with

Object.defineProperty( obj, "teamSize", {value: 5, writable: false, enumerable: true})

by making it unwritable, while an entire object o can be frozen by stating Object.freeze( o).

We postpone the further discussion of frozen value constraints to Part 5 of our tutorial.

3. Responsive Validation

This problem is well-known from classical web applications where the front-end component submits the user input data via HTML form submission to a back-end component running on a remote web server. Only this back-end component validates the data and returns the validation results in the form of a set of error messages to the front end, Only then, often several seconds later, and in the hard-to-digest form of a bulk message, does the user get the validation feedback. This approach is no longer considered acceptable today. Rather, in a responsive validation approach, the user should get immediate validation feedback on each single data input, typically in response to input or change events.

So, we need a data validation mechanism in the user interface (UI). Fortunately, the new HTML5 form validation API supports constraint validation in the UI. Alternatively, the jQuery Validation Plugin can be used as a (non-HTML5-based) form validation API.

The HTML5 form validation API essentially provides new input type values and a set of new attributes for form control elements for the purpose of supporting responsive validation directly performed by the browser. Since using the new validation attributes (like required, min, max and pattern) implies defining constraints in the UI, they are not really useful in a general approach where constraints are only checked, but not defined, in the UI.

Consequently, we only use two methods of the HTML5 form validation API for validating constraints in the HTML-forms-based user interface of our app. The first of them, setCustomValidity, allows to mark a form input field as either valid or invalid by assigning either an empty string or a non-empty message string to it. The second method, checkValidity, is invoked on a form and tests, if all input fields have a valid value.

See this Mozilla tutorial or this HTML5Rocks tutorial for more about the HTML5 form validation API.

4. Constraint Validation in MVC Applications

Integrity constraints should be defined in the model classes of an MVC app since they are part of the business semantics of a model class (representing a business object type). However, a more difficult question is where to perform data validation? In the database? In the model classes? In the controller? Or in the user interface ("view")? Or in all of them?

A relational database management system (DBMS) performs data validation whenever there is an attempt to change data in the database, provided that all relevant integrity constraints have been defined in the database. This is essential since we want to avoid, under all circumstances, that invalid data enters the database. However, it requires that we somehow duplicate the code of each integrity constraint, because we want to have it also in the model class to which the constraint belongs.

Also, if the DBMS would be the only application component that validates the data, this would create a latency, and hence usability, problem in distributed applications because the user would not get immediate feedback on invalid input data. Consequently, data validation needs to start in the user interface (UI).

However, it is not sufficient to perform data validation in the UI. We also need to do it in the model classes, and in the database, for making sure that no flawed data enters the application's persistent data store. This creates the problem of how to maintain the constraint definitions in one place (the model), but use them in two or three other places (at least in the model classes and in the UI code, and possibly also in the database).We call this the multiple validation problem. This problem can be solved in different ways. For instance:

  1. Define the constraints in a declarative language (such as Java Persistency and Bean Validation Annotations or ASP.NET Data Annotations) and generate the back-end/model and front-end/UI validation code both in a back-end application programming language such as Java or C#, and in JavaScript.

  2. Keep your validation functions in the (PHP, Java, C# etc.) model classes on the back-end, and invoke them from the JavaScript UI code via XHR. This approach can only be used for specific validations, since it implies the penalty of an additional HTTP communication latency for each validation invoked in this way.

  3. Use JavaScript as your back-end application programming language (such as with NodeJS), then you can encode your validation functions in your JavaScript model classes on the back-end and execute them both before committing changes on the back-end and on user input and form submission in the UI on the front-end side.

The simplest, and most responsive, solution is the third one, using only JavaScript both in the back-end and front-end components.

5. Criteria for Evaluating the Validation Support of Frameworks

The support of MVC frameworks for constraint validation can be evaluated according to the following criteria. Does the framework support

  1. the declaration of all important kinds of property constraints as defined above (String Length Constraints, Mandatory Value Constraints, Range Constraints, etc.) in model classes?

  2. validation in the model class on assign property and before save object?

  3. informative generic validation error messages referring to the object and property concerned?

  4. app-specific validation error messages with parameters?

  5. responsive validation in the user interface on input and on submit based on the constraints defined in the model classes, preferably using the HTML5 constraint validation API, or at least a general front-end API like the jQuery Validation Plugin?

  6. two-fold validation with defining constraints in the model and checking them in the model and in the view?

  7. three-fold validation with defining constraints in the model and checking them in the model, the view and in the database system?

  8. reporting database validation errors that are passed from the database system to the app?

Chapter 2. Constraint Validation in a BackboneJS/Parse App

1. Introduction

The minimal BackboneJS/Parse app that we have discussed in Part 1 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 second part of the tutorial we show how to express integrity constraints in a BackboneJS 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 again consider the single-class data management problem that was considered in Part 1 of this tutorial. So, again, the purpose of our app is to manage information about books. But now we also consider the data integrity rules (also called 'business rules') that govern the management of book data. These integrity rules, or constraints, can be expressed in a UML class diagram as shown in Figure 2.1 below.

Figure 2.1. A platform-independent design model with the class Book and two invariants

A platform-independent design model with the class Book and two invariants

In this simple model, the following constraints have been expressed:

  1. Due to the fact that the isbn attribute is declared to be the standard identifier of Book, it is mandatory and unique.

  2. The isbn attribute has a pattern constraint requiring its values to match the ISBN-10 format that admits only 10-digit strings or 9-digit strings followed by "X".

  3. The title attribute is mandatory.

  4. The year attribute is mandatory and has an interval constraint, however, of a special form since the maximum is not fixed, but provided by the calendaric function nextYear(), which we implement as a utility function.

Notice that the edition attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to these constraints, there are the implicit range constraints defined by assigning the datatype NonEmptyString as range to isbn and title, and Integer to year. In our approach, all these property constraints are encoded in the model class within property-specific check functions.

2. Constraint Validation in BackboneJS

When defining a BackboneJS model class, we can provide a validate function that is called whenever a save operation is invoked on an object, passing the object's property slots from save to validate as the first argument. If the validate function returns anything (an error message or object), save will not continue, and the data will not be written to the data store.

The BackboneJS validation approach does not support any standard constraints, such as mandatory value constraints, but requires to check all constraints that apply to an instance of a class, including all property constraints, in the validate function. For instance, in the following code fragment, we check if a value for the mandatory attribute isbn has been provided and if this value is a non-empty string:

var Book = Parse.Object.extend("Book", {
  ...
  validate: function (slots) {
    if (slots.isbn === undefined || 
        typeof(slots.isbn) !== "string" || 
        slots.isbn.trim() === "") {
    return "The ISBN must be a non-empty string!";
    ...
  },
  ...
});

Any failed validation attempt creates an instance of the BackboneJS event type invalid, which can be caught by a BackboneJS event listener that logs the validation error messages. Such an event listener can be registered for the instances of a model class with the help of the BackboneJS method on in the initialize method, as shown in the following example:

var Book = Parse.Object.extend("Book", {
  ...
  initialize: function () {
    this.on("invalid", function (book, errMsgs) {
      console.log( "Validation error: " + errMsgs.isbn);
    });
  }
});

The on method has two parameters: 1) the name of the BackboneJS event type, and 2) the event handler function, which itself has two parameters: 1) the model object on which the event occurred (here, the book object), and 2) the value returned by the validate function (typically a list of error messages, one for each failed property value).

3. Using the HTML5 Form Validation API

We only use two methods of the HTML5 form validation API for validating constraints in the HTML-forms-based user interface of our app. The first of them, setCustomValidity, allows to mark a form input field as either valid or invalid by assigning either an empty string or a non-empty message string to it. The second method, checkValidity, is invoked on a form and tests, if all input fields have a valid value.

Notice that in our approach there is no need to use the new HTML5 attributes for validation, such as required, since we do all validations with the help of setCustomValidity and our property check functions, as we explain below.

See this Mozilla tutorial or this HTML5Rocks tutorial for more about the HTML5 form validation API.

4. New Issues

Compared to the minimal app discussed in Part 1 (Minimal App Tutorial) we have to deal with a number of new issues:

  1. In the model layer we have to take care of

    1. adding for every property of a model class a check function that can be invoked for validating the constraints defined for the property,

    2. performing validation before any data is saved.

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

    1. styling the user interface with CSS rules,

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

    3. 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 that fills a select form control with option elements the contents of which is retrieved from a BackboneJS collection 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 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 user interface, because this could be circumvented in a distributed web application where the user interface runs in the web browser of a front-end device while the application's data is managed by a backend component on a remote web server. Consequently, we need multiple constraint validation, first in the user interface on user input and before form submission, and subsequently in the model layer before saving/sending data to the persistent datastore. And in an application based on a DBMS we may also use a third round of validation before persistent storage by using the validation mechnisms of the DBMS. This is a must, when the application's database is shared with other apps.

Our solution to this mulitple validation problem is to keep the constraint validation code in special check functions in the model classes and invoke these functions both in the user interface layer on user input and on form submission, as well as in the model layer before save via the BackboneJS validate function defined in the model classes. 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.

5. Make a BackboneJS/Parse Data Model

Using the information design model shown in Figure 2.1 above as the starting point, we make a BackboneJS/Parse data model by performing the following steps:

  1. Add a String-valued id attribute for standard IDs that are automatically generated by Parse. This should be the first property in the list of properties. It should be qualified as the standard identifier attribute with the stdid stereotype, and with the special constraint keyword auto-generated for indicating that the attribute is predefined and its values are automatically generated.

  2. Since Parse is using the predefined id attribute as a standard identifier attribute, the standard identifier attribute of the design model class has to be either replaced by it or it has to be turned into a merely unique attribute by dropping the standard identifier (stdid) declaration of the design model class and adding a uniqueness constraint instead (so it's still a key, but no longer the primary key). The choice of these two options depends on the fact if the standard identifier attribute of the design model class represents a business concept (such as the unqie name of a publisher or the ISBN of a book), or if its values are artificial/technical identifiers without any business meaning (such as in the case of Author::personId). In the former case the attribute has to be preserved, while in the latter case it can be dropped and the id attribute can be used instead.

  3. Add the two Date-valued attributes createdAt and updatedAt, and mark the first one with the special constraint keywords auto-generated, frozen, and the second one with auto-generated, read-only for indicating that they are predefined and automatically filled by Parse.

  4. Create a check operation for each non-derived property of the design model in order to have a central place for implementing all the property constraints defined in the design model.

  5. Create add and remove operations for each non-derived multi-valued property (if there are any).

This leads to the BackboneJS/Parse data model shown on the right hand side of the mapping arrow in the following figure.

Figure 2.2. Deriving a BackboneJS data model from an information design model

Deriving a BackboneJS data model from an information design model
Deriving a BackboneJS data model from an information design model
Deriving a BackboneJS data model from an information design model

The JavaScript data model extends the design model by adding checks for each property. Notice that the names of check operations are underlined, since this is the convention in UML for class-level operations (as opposed to instance-level operations).

6. Set up the folder structure and create four initial files

The MVC folder structure of our validation app extends the structure of the minimal app by adding two folders, css for adding the CSS file main.css and lib for adding the generic code libraries browserShims.js, errorTypes.js and util.js. Thus, we end up with the following folder structure containing four initial files:

publicLibrary
  css
    main.css
  lib
    browserShims.js
    errorTypes.js
    util.js
  src
    ctrl
    model
    view
  index.html

We discuss the contents of the four initial files in the following sections.

6.1. Style the user interface with CSS

We style the UI with the help of the CSS library Pure provided by Yahoo. We only use Pure's basic styles, which include the browser style normalization of normalize.css, and its styles for forms. In addition, we define our own style rules for table and menu elements in main.css.

6.2. Provide general utility functions and JavaScript fixes in library files

We add three library files to the lib folder:

  1. util.js contains the definitions of a few utility functions such as isNonEmptyString(x) for testing if x is a non-empty string.

  2. 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 ECMAScript Edition 5, defined in 2009). More browser shims for other recently defined functions, such as querySelector and classList, could also be added to browserShims.js.

  3. errorTypes.js defines general classes for error (or exception) types: NoConstraintViolation, MandatoryValueConstraintViolation, RangeConstraintViolation, IntervalConstraintViolation, PatternConstraintViolation, UniquenessConstraintViolation, OtherConstraintViolation.

6.3. Create a start page

The start page of the app first takes care of the page styling by loading the Pure CSS base file (from the Yahoo site) and our main.css file with the help of the two link elements (in lines 6 and 7), then it loads the following JavaScript files (in lines 8-12):

  1. browserShims.js and util.js from the lib folder, discussed in Section 6.2,

  2. initialize.js from the src/ctrl folder, defining the app's MVC namespaces, as discussed in Part 1 (the minimal app tutorial).

  3. errorTypes.js from the lib folder, defining the following classes for error (or exception) types: NoConstraintViolation, MandatoryValueConstraintViolation, RangeConstraintViolation, IntervalConstraintViolation, PatternConstraintViolation, UniquenessConstraintViolation, OtherConstraintViolation.

  4. Book.js from the src/model folder, a model class file that provides data management and other functions discussed in Section 7.

Figure 2.3. The validation app's start page index.html.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>BackboneJS/Parse Validation Example App</title>
  <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.5.0/pure-min.css" />
  <link rel="stylesheet" href="css/main.css" /> 
  <script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>
  <script src="lib/browserShims.js"></script>
  <script src="lib/util.js"></script>
  <script src="lib/errorTypes.js"></script>
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/Book.js"></script>
</head>
<body>
  <h1>Public Library</h1>
  <menu>
    <li><a href="listBooks.html"><button type="button">List all books</button></a></li>
    <li><a href="createBook.html"><button type="button">Add a new book</button></a></li>
    <li><a href="updateBook.html"><button type="button">Update a book</button></a></li>
    <li><a href="deleteBook.html"><button type="button">Delete a book</button></a></li>
    <li><button type="button" onclick="Book.clearData()">Clear database</button></li>
    <li><button type="button" onclick="Book.createTestData()">Create test data</button></li>
  </menu>
</body>
</html>

7. Write the Model Code

How to Encode a BackboneJS Data Model

The BackboneJS data model shown on the right hand side in Figure 2.2 can be encoded step by step for getting the code of the model layer of our BackboneJS/Parse app. These steps are summarized in the following section.

7.1. Summary

  1. Encode the model class as an extension of Parse.Object.

  2. Encode the check functions, such as checkIsbn or checkTitle, as class-level methods. Take care that all constraints, as specified in the JavaScript data model, are properly encoded in the check functions.

  3. Encode the add and remove operations, if there are any, as instance-level methods.

  4. Encode any other operation.

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

7.2. Encode the model class as an extension of Parse.Object

The class Book is encoded by extending Parse.Object (more specifically, as a JS object created with Parse.Object.extend). The BackboneJS/Parse model class definition from the minimal app discussed in Part 1 is extended by adding the BackboneJS methods validate and initialize:

var Book = Parse.Object.extend("Book", {
  defaults: {
    isbn: "",
    title: "",
    year: 0
  },
  validate: function (attribs) {
    var errMsgs = {};  // map for error messages
    errMsgs["isbn"] = Book.checkIsbn( attribs.isbn).message;
    errMsgs["title"] = Book.checkTitle( attribs.title).message;
    errMsgs["year"] = Book.checkYear( attribs.year).message;
    if (errMsgs.isbn || errMsgs.title || errMsgs.year) return errMsgs;
  },
  initialize: function () {
    this.on("invalid", function( book, errMsgs){
      console.log( "Validation error(s) for " + book.get("isbn") + ": " + 
          errMsgs.isbn ? "\n" + errMsgs.isbn : "" +
          errMsgs.title ? "\n" + errMsgs.title : "" +
          errMsgs.year ? "\n" + errMsgs.year : ""
      );
    });
  }
});

In the validate method, we invoke the check functions Book.checkIsbn, Book.checkTitle and Book.checkYear and collect the error messages that may result from constraint violations. If at least one constraint violation is detected, the error message array is returned, otherwise nothing is returned. This is the behavior expected by the Backbone constraint validation mechanism, which invokes the validate method for an object whenever a save operation is issued for it. The validate method is invoked bevor the save operation is executed. If the validate method returns something, that is, if a constraint violation is detected, the save operation will not be executed, but an invalid event will be created instead. This Backbone event can be handled by an event handler, which may be registered with this.on("invalid", handlerfunction), as shown in the initialize method.

As in the minimal app, we make the simplifying assumption that we can load all data from the remote Parse data store into main memory on application start up. The purpose of the class-level property Book.instances is to hold the collection of all Book instances managed by the application in the form of a Parse collection obtained from a Parse query retrieving all Book instances:

Book.instances = (new Parse.Query( Book)).collection();

7.3. Encode the property checks

Encode the property check functions in the form of class-level methods, as in Book.checkIsbn. Take care that all constraints of a property as specified in the data model are properly encoded in its check function. This concerns, in particular, the mandatory value and uniqueness constraints implied by the standard identifier declaration (with «stdid»), 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 6.3 and defined in the file model/errorTypes.js is returned.

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

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

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:

Book.checkIsbnAsId = function (id) {
  var constraintViolation = Book.checkIsbn( id);
  if ((constraintViolation instanceof NoConstraintViolation)) {
    if (!id) {
      constraintViolation = new MandatoryValueConstraintViolation(
          "A value for the ISBN must be provided!");
    } else if (Book.instances[id]) {  
      constraintViolation = new UniquenessConstraintViolation(
          "There is already a book record with this ISBN!");
    } else {
      constraintViolation = new NoConstraintViolation();
    } 
  }
  return constraintViolation;
};

7.4. Add a serialization function

It is helpful to have a serialization function tailored to the structure of a 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:

Book.prototype.toString = function () {
  return "Book{ ISBN:" + this.get("isbn") + ", title:" + 
      this.get("title") + ", year:" + this.get("year") + 
      (this.get("edition") || "") +"}"; 
};

7.5. Data 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() function, we also need to define the following data management operations as class-level methods of the model class:

  1. Book.convertRow2Obj and Book.loadAll 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.

  4. Book.update for updating an existing Book instance.

  5. Book.destroy for deleting a Book instance.

  6. Book.createTestData for creating a few example book records to be used as test data.

  7. Book.clearData for clearing the book datastore.

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.convertRow2Obj, Book.add, Book.update and Book.createTestData; and

  2. we can use the toString() function for serializing an object in status and error messages.

Notice that for the change operations add and update, we need to implement an all-or-nothing policy: as soon as 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 in line 6. Otherwise, the new book object is added to Book.instances and a status message is creatred in lines 10 and 11, as shown in the following program listing:

Book.add = function (slots, callback) {
  var bookObj = new Book( slots);
  Book.instances.add( bookObj);
  bookObj.save( null, {
    success: function (bookObj) {
      console.log("Saved: "+ bookObj.toString());
      if (typeof callback === "function") callback( bookObj);
    },
    error: function (obj, error) {
      console.log("Save error: "+ error + " for "+ obj.toString());
    }
  });
};

Likewise, when a constraint violation is detected in a property check invoked via set in Book.update, a constraint violation error message is created and the previous state of the object is restored. Otherwise, a status message is created, as shown in the following program listing:

Book.update = function (id, slots, callback) {
  var bookObj = Book.instances.get( id),
      noConstraintViolated = true,
      updatedProperties = [],
      objectBeforeUpdate = util.cloneObject( bookObj);
  try {
    if (bookObj.get("title") !== slots.title) {
      bookObj.set("title", slots.title);
      updatedProperties.push("title");
    }
    if (bookObj.get("year") !== slots.year) {
      bookObj.set("year", slots.year);
      updatedProperties.push("year");
    }
    if (slots.edition && bookObj.bookObj.get("edition") !== slots.edition) {
      bookObj.set("edition", slots.edition);
      updatedProperties.push("edition");
    }
  } catch (e) {
    console.log( e.constructor.name +": "+ e.message);
    noConstraintViolated = false;
    // restore object to its state before updating
    bookObj = objectBeforeUpdate;
  }
  if (noConstraintViolated) {
    if (updatedProperties.length > 0) {
      console.log("Properties "+ JSON.stringify( updatedProperties) + 
          " modified for book " + slots.isbn);
    } else {
      console.log("No property value changed for book "+ slots.isbn + " !");      
    }
  }
  bookObj.save( null, {
    success: function (bookObj) {
      console.log("Updated: "+ bookObj.toString());
      if (typeof callback === "function") callback( bookObj);
    },
    error: function (bookObj, error) {
      console.log("Update error: "+ JSON.stringify( error));
    }
  });
};

8. The View and Controller Layers

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 listBooks.html or createBook.html in the app folder.

After loading the Pure base stylesheet and our own CSS settings in main.css, we first load some browser shims and utility functions. Then we initialize the app in src/ctrl/initialize.js and continue loading the error classes defined in src/model/errorTypes.js and the model class Book.

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.1. The 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 two use-case-specific view and controller files src/view/useCase.js and src/ctrl/useCase.js and then adds a use case initialize function (such as pl.ctrl.listBooks.initialize) as an event listener for the page load event, which takes care of initializing the use case when the UI page has been loaded (see Section 8.3).

For the "list books" use case, we get the following code in listBooks.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>JS Frontend Validation App Example</title>
  <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.5.0/pure-min.css" />
  <link rel="stylesheet" href="css/main.css" /> 
  <script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>
  <script src="lib/browserShims.js"></script>
  <script src="lib/util.js"></script>
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/errorTypes.js"></script>
  <script src="src/model/Book.js"></script>
  <script src="src/view/listBooks.js"></script>
  <script src="src/ctrl/listBooks.js"></script>
  <script>
    window.addEventListener("load", pl.ctrl.listBooks.initialize);
  </script>
</head>
<body>
  <h1>Public Library: List all books</h1>
  <table id="books">
    <thead>
      <tr><th>ISBN</th><th>Title</th><th>Year</th></tr>
    </thead>
    <tbody></tbody>
  </table>
  <form style="display:none">
    <div class="pure-controls">
     <a href="index.html"><button type="button">Back to main menu</button></a>
    </div>
  </form>
  <div><progress></<progress>></div>
</body>
</html>

Notice that, initially, the form element containing the "Back" button is not displayed. Instead, an HTML progress element is shown during the time required for loading all book records from the remote Parse data store.

For the "create book" use case, in the head of createBook.html, we now use the HTML form styling of PureCSS by loading the corresponding style file:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>JS Frontend Validation App Example</title>
  <link rel="stylesheet" href="http://yui.yahooapis.com/combo?pure/0.5.0/base-
       min.css&pure/0.5.0/forms-min.css" />
   ...
</head>

In the body of the UI page, we have the HTML form fields corresponding to the model properties isbn, title, and year:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
 ...
</head>
<body>
  <h1>Public Library: Create a new book record</h1>
  <form id="Book" class="pure-form pure-form-aligned" style="display:none">
    <div class="pure-control-group">
      <label for="isbn">ISBN</label>
      <input id="isbn" name="isbn" />
    </div>
    <div class="pure-control-group">
      <label for="title">Title</label>
      <input id="title" name="title" />
    </div>
    <div class="pure-control-group">
      <label for="year">Year</label>
      <input id="year" name="year" />
    </div>
    <div class="pure-controls">
      <p><button type="submit" name="commit">Save</button></p>
      <nav><a href="index.html">Back to main menu</a></nav>
    </div>
  </form>
  <div><progress></<progress>></div>
</body>
</html>

Notice that for styling the form elements in createBook.html, and also for updateBook.html and deleteBook.html, we use the Pure CSS form styles. This requires to assign specific values, such as "pure-control-group", to the class attributes of the form's div elements containing the form controls. We have to use explicit labeling (with the label element's for attribute referencing the input element's id), since Pure does not support implicit labels where the label element contains the input element.

8.2. Initialize the app

For initializing the app, its namespace and MVC subnamespaces have to be defined. For our example app, the main namespace is defined to be pl, standing for "Public Library", with the three subnamespaces model, view and ctrl being initially empty objects:

var pl = { model:{}, view:{}, ctrl:{} };

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

8.3. Initialize the data management use cases

For initializing a data management use case, the required data has to be loaded from persistent storage and the UI has to be set up. This is performed with the help of the controller procedures pl.ctrl.createBook.initialize, which loads all book records ny invoking Book.instances.fetch and then invokes pl.view.createBook.setupUserInterface defined in the controller file ctrl/createBook.js with the following code:

pl.ctrl.createBook = {
  initialize: function () {
    // load book data from Parse DB table
    Book.instances.fetch({
      success: function (coll) {
        pl.view.createBook.setupUserInterface(); 
      },
      error: function (coll, error) {
        console.log("Error: " + error);
      }
    });
  }
};

All other data management use cases (read/list, update, delete) are handled in the same way.

8.4. Set up the user interface

For setting up the user interfaces of the data management use cases, we have to distinguish the case of "list books" 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 "list books" we only have to render a table displaying all the books, as shown in the following program listing of view/listBooks.js:

 pl.view.listBooks = {
  setupUserInterface: function () {
    var tableBodyEl = document.querySelector("table#books>tbody");
    var formEl = document.forms[0],
        progressEl = document.getElementsByTagName("progress")[0];
    Book.instances.forEach( function (bkObj) {
      var row = tableBodyEl.insertRow();
      row.insertCell(-1).textContent = bkObj.get("isbn");      
      row.insertCell(-1).textContent = bkObj.get("title");  
      row.insertCell(-1).textContent = bkObj.get("year");
    });
    progressEl.style.display = "none";
    formEl.style.display = "block";
  }
};

For the create, update and delete use cases, we need to attach the following event handlers to form controls:

  1. a function, such as handleSubmitButtonClickEvent, for handling the event when the user clicks the save/submit button,

  2. functions for validating the data entered by the user in form fields (if there are any).

In addition, in line 20 of the following view/createBook.js code, we add an event handler for saving the application data in the case of a beforeunload event, which occurs, for instance, when the browser (or browser tab) is closed:

pl.view.createBook = {
  /**
   * initialize the createBook form
   */
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        progressEl = document.getElementsByTagName("progress")[0],
        saveButton = formEl.commit;
    var titleInpEl = formEl.title, yearInpEl = formEl.year;
    // show form after loading all records
    progressEl.style.display = "none";
    formEl.style.display = "block";
    // validate on input
    formEl.isbn.addEventListener("input", function () { 
      formEl.isbn.setCustomValidity( 
          Book.checkIsbn( formEl.isbn.value).message);
    });
    titleInpEl.addEventListener("input", function () { 
      titleInpEl.setCustomValidity( 
          Book.checkTitle( titleInpEl.value).message);
    });
    yearInpEl.addEventListener("input", function () { 
      yearInpEl.setCustomValidity( 
          Book.checkYear( parseInt( yearInpEl.value)).message);
    });
    saveButton.addEventListener("click", this.handleSaveButtonClickEvent);
  },
  handleSubmitButtonClickEvent: function () {
    ...
  }
};

Notice that for each form 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).

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 handleSubmitButtonClickEvent() performs the property checks again with the help of setCustomValidity, as shown in the following program listing:

handleSaveButtonClickEvent: function (e) {
  var formEl = document.forms['Book'];
  var slots = {isbn: formEl.isbn.value,
          title: formEl.title.value,
          year: parseInt( formEl.year.value)
  };
  // check all input fields and provide error messages in case of constraint violations
  formEl.isbn.setCustomValidity( Book.checkIsbn( slots.isbn).message);
  formEl.title.setCustomValidity( Book.checkTitle( slots.title).message);
  formEl.year.setCustomValidity( Book.checkYear( slots.year).message);
  // save the input data only if all of the form fields are valid
  if (formEl.checkValidity()) {
    Book.add( slots);
    e.preventDefault();
    formEl.reset();
  }
}

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 handleSubmitButtonClickEvent handler has been executed on an invalid form, the browser takes control and tests if the pre-defined 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.

For the use case update book, which is handled in view/updateBook.js, we provide a book selection list, so the user need not enter an identifier for books (an ISBN), but has to select the book to be updated. This implies that there is no need to validate the ISBN form field, but only the title and year fields. We get the following code:

pl.view.updateBook = {
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        progressEl = document.getElementsByTagName("progress")[0],
        saveButton = formEl.commit,
        selectBookEl = formEl.selectBook;
    // show form after loading all records
    progressEl.style.display = "none";
    formEl.style.display = "block";
    // populate the selection list
    Book.instances.forEach( function (book) {
      var bookOptionEl = document.createElement("option");
      bookOptionEl.text = book.get("title");
      bookOptionEl.value = book.id;  // the Parse object ID
      selectBookEl.add( bookOptionEl, null);
    });
    // when a book is selected, fill the form with the book's data
    selectBookEl.addEventListener("change", function () {
      var book = null, 
          bookObjId = selectBookEl.value;
      if (bookObjId !== "0") {
        book = Book.instances.get( bookObjId);
        formEl.isbn.value = book.get("isbn");
        formEl.title.value = book.get("title");
        formEl.year.value = book.get("year");
        saveButton.removeAttribute("disabled");
      } else {  // no book selected
        formEl.reset();
      }
    });
    formEl.title.addEventListener("input", function () { 
      formEl.title.setCustomValidity( Book.checkTitle( formEl.title.value).message);
    });
    formEl.year.addEventListener("input", function () { 
      formEl.year.setCustomValidity( 
          Book.checkYear( parseInt( formEl.year.value)).message);
    });
    saveButton.addEventListener("click", this.handleUpdateButtonClickEvent);
    // neutralize the submit event
    formEl.addEventListener( 'submit', function (e) { 
        e.preventDefault();;
        formEl.reset();
    });
  },

When the save button on the update book form is clicked, the title and year form field values are validated by invoking setCustomValidity, and then the book record is updated if the form data validity can be established with checkValidity():

handleUpdateButtonClickEvent: function (evt) {
  var formEl = document.forms['Book'],
      progressEl = document.getElementsByTagName("progress")[0];
  var bookObjId = formEl.selectBook.value;
  var slots = {isbn: formEl.isbn.value,
               title: formEl.title.value,
               year: parseInt( formEl.year.value)
  };
  formEl.title.setCustomValidity( Book.checkTitle( slots.title).message);
  formEl.year.setCustomValidity( Book.checkYear( slots.year).message);
  if (formEl.checkValidity()) {
    formEl.style.display = "none";
    progressEl.style.display = "block";
    Book.update( bookObjId, slots,
      function () {
        progressEl.style.display = "none";
        formEl.style.display = "block";
        evt.preventDefault();
        formEl.reset();
     });
  }
}

The logic of the setupUserInterface method for the delete use case is similar.

9. Run the App and Get the Code

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

10. Possible Variations and Extensions

10.1. Simplifying forms with implicit labels

The explicit labeling of form fields used in this tutorial requires to add an id value to the input element and a for-reference to its label element as in the following example:

<div class="pure-control-group">
  <label for="isbn">ISBN:</label>
  <input id="isbn" name="isbn" />
</div>

This technique for associating a label with a form field is getting quite inconvenient when we have many form fields on a page because we have to make up a great many of unique id values and have to make sure that they don't conflict with any of the id values of other elements on the same page. It's therefore preferable to use an approach, called implicit labeling, that does not need all these id references. In this approach we make the input element a child element of its label element, as in

<div>
  <label>ISBN: <input name="isbn" /></label>
</div>

Having input as a child of its label doesn't seem very logical (rather, one would expect the label to be a child of an input element). But that's the way, it is defined in HTML5.

A small disadvantage of using implicit labels is the lack of support by popular CSS libraries, such as Pure CSS. In the following parts 3-5 of this tutorial, we will use our own CSS styling for implicitly labeled form fields.

11. Points of Attention

You may have noticed the repetitive code structures (called boilerplate code) needed in the model layer per class and per property for constraint validation (checks and setters) and per class for the data storage management methods add, update, and destroy. 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 Part 6 of our tutorial series, we will present an approach how to put these methods in a generic form in a meta-class called mODELcLASS, such that they can be reused in all model classes of an app.

Glossary

E

Extensible Markup Language

XML allows to mark up the structure of all kinds of documents, data files and messages in a machine-readable way. XML may also be human-readable, if the tag names used are self-explaining. XML is based on Unicode. SVG and MathML are based on XML, and there is an XML-based version of HTML.

XML provides a syntax for expressing structured information in the form of an XML document with elements and their attributes. The specific elements and attributes used in an XML document can come from any vocabulary, such as public standards or user-defined XML formats.

H

Hypertext Markup Language

HTML allows marking up (or describing) the structure of a human-readable web document or web user interface. The XML-based version of HTML, which is called "XHTML5", provides a simpler and cleaner syntax compared to traditional HTML.

Hypertext Transfer Protocol

HTTP is a stateless request/response protocol using human-readable text messages for the communication between web clients and web servers. The main purpose of HTTP has been to allow fetching web documents identified by URLs from a web browser, and invoking the operations of a backend web application program from a HTML form executed by a web browser. More recently, HTTP is increasingly used for providing web APIs and web services.