Engineering Front-End Web Apps with BackboneJS and Cloud Storage

An incremental five-part in-depth tutorial about engineering front-end web applications with the BackboneJS framework and the Parse.com cloud storage service

Gerd Wagner

Warning: This textbook is a draft version, so it may still contain errors and may still be incomplete in certain respects. Please report any issue to Gerd Wagner at G.Wagner@b-tu.de.

This textbook is also available in the following formats: PDF. See also the project page.

This textbook, 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 textbook must not be distributed or republished without the author's consent.

2015-02-24

Revision History
20150312gw
Create first version.

Acknowledgements

Table of Contents

Foreword
I. Getting Started
1. A Quick Tour of the Foundations of Web Apps
1. The World Wide Web (WWW)
2. HTML and XML
2.1. XML documents
2.2. Unicode and UTF-8
2.3. XML namespaces
2.4. Correct XML documents
2.5. The evolution of HTML
2.6. HTML forms
3. JavaScript
3.1. Types and data literals in JavaScript
3.2. Variable scope
3.3. Strict Mode
3.4. Different kinds of objects
3.5. Array lists
3.6. Maps
3.7. JavaScript supports four types of basic data structures
3.8. Defining and using classes
3.9. JavaScript as an object-oriented language
3.10. Further reading about JavaScript
2. BackboneJS/Parse Web Apps Tutorial Part 1
1. Using the Cloud Storage Service of Parse.com with BackboneJS
1.1. Getting Started with Parse
1.2. The BackboneJS/Parse Model Layer
2. Building a Minimal BackboneJS/Parse App in Seven Steps
2.1. Step 1 - Set up the Folder Structure
2.2. Step 2 - Write the Model Code
2.3. Step 3 - Initialize the App
2.4. Step 4 - Develop the Use Case "List Objects"
2.5. Step 5 - Develop the Use Case "Create Object"
2.6. Step 6 - Develop the Use Case "Update Object"
2.7. Step 7 - Develop the Use Case "Delete Object"
3. Run the App and Get the Code
4. Points of Attention
II. Integrity Constraints
3. Integrity Constraints and Data Validation
1. String Length Constraints
2. Mandatory Value Constraints
3. Range Constraints
4. Interval Constraints
5. Pattern Constraints
6. Cardinality Constraints
7. Uniqueness Constraints
8. Standard Identifiers (Primary Keys)
9. Referential Integrity Constraints
10. Frozen Value Constraints
11. Constraint Validation in MVC Applications
4. Validating Integrity Constraints in a Parse/BackboneJS App
1. Constraint Validation in the BackboneJS Model Layer
2. Using the HTML5 Form Validation API
3. New Issues
4. Make a Parse/BackboneJS Data Model
5. Set up the folder structure and create four initial files
5.1. Style the user interface with CSS
5.2. Provide general utility functions and JavaScript fixes in library files
5.3. Create a start page
6. Write the Model Code
6.1. Summary
6.2. Encode the model class as an extension of Parse.Object
6.3. Encode the property checks
6.4. Encode the property setters
6.5. Add a serialization function
6.6. Data management operations
7. The View and Controller Layers
7.1. The data management UI pages
7.2. Initialize the app
7.3. Initialize the data management use cases
7.4. Set up the user interface
8. Run the App and Get the Code
9. Possible Variations and Extensions
9.1. Simplifying forms with implicit labels
10. Points of Attention
III. Associations
5. Reference Properties and Unidirectional Associations
1. References and Reference Properties
2. Referential Integrity
3. Modeling Reference Properties as Unidirectional Associations
4. Representing Unidirectional Associations as Reference Properties
5. Adding Directionality to a Non-Directed Association
6. Our Running Example
7. Eliminating Unidirectional Associations
7.1. The basic elimination procedure restricted to unidirectional associations
7.2. Eliminating associations from the Publisher-Book-Author design model
8. Rendering Reference Properties in the User Interface
6. Bidirectional Associations
1. Inverse Reference Properties
2. Making an Association-Free Information Design Model
2.1. The basic procedure
2.2. How to eliminate uni-directional associations
2.3. How to eliminate bi-directional associations
2.4. The resulting association-free design model
IV. Inheritance in Class Hierarchies
7. Subtyping and Inheritance
1. Introducing Subtypes by Specialization
2. Introducing Supertypes by Generalization
3. Intension versus Extension
4. Type Hierarchies
5. The Class Hierarchy Merge Design Pattern
6. Subtyping and Inheritance in Computational Languages
6.1. Subtyping and Inheritance with OOP Classes
6.2. Subtyping and Inheritance with Database Tables
Glossary

List of Figures

2.1. The object type Book.
2.2. Looking up your app's data with the web user interface of Parse.
3.1. The object type Person with an interval constraint
3.2. The object type Book with a pattern constraint
3.3. Two object types with cardinality constraints
3.4. The object type Book with a uniqueness constraint
3.5. The object type Book with a standard identifier declaration
4.1. A platform-independent design model with the class Book and two invariants
4.2. Deriving a BackboneJS data model from an information design model
4.3. The validation app's start page index.html.
5.1. A committee has a club member as chair expressed by the reference property chair
5.2. A committee has a club member as chair expressed by an association end with a "dot"
5.3. Representing the unidirectional association ClubMember has Committee as chairedCommittee as a reference property
5.4. A model of the Committee-has-ClubMember-as-chair association without ownership dots
5.5. Modeling a bidirectional association between Committee and ClubMember
5.6. The Publisher-Book information design model with a unidirectional association
5.7. The Publisher-Book-Author information design model with two unidirectional associations
5.8. Turn a non-functional target association end into a corresponding reference property
5.9. The association-free Publisher-Book design model
5.10. The association-free Publisher-Book-Author design model
6.1. The Publisher-Book-Author information design model with two bidirectional associations
6.2. Turn a bi-directional one-to-one association into a pair of mutually inverse single-valued reference properties
6.3. Turn a bi-directional many-to-many association into a pair of mutually inverse multi-valued reference properties
6.4. The association-free design model
7.1. The object type Book is specialized by two subtypes: TextBook and Biography
7.2. The object types Employee and Author share several attributes
7.3. The object types Employee and Author have been generalized by adding the common supertype Person
7.4. The complete class model containing two inheritance hierarchies
7.5. A class hierarchy having the root class Vehicle
7.6. A multiple inheritance hierarchy
7.7. The design model resulting from applying the Class Hierarchy Merge design pattern
7.8. A class model with a Person roles hierarchy
7.9. An SQL table model with a single table representing the Book class hierarchy
7.10. An SQL table model with a single table representing the Person roles hierarchy
7.11. An SQL table model with the table Person as the root of a table hierarchy

List of Tables

1.1. Constructor-based versus factory-based classes
2.1. Table 2: A collection of book objects represented as a Parse table
5.1. An example of an association table
5.2. Different terminologies
5.3. Functionality types

Foreword

This textbook shows how to build front-end web applications with the BackboneJS framework using the cloud storage service provided by Parse.com. It follows a "learning by doing" approach, which means that you do not read lots of text about the intricacies of JavaScript (like in most JavaScript programming books), but rather read the minimum necessary to start programming your first app.

A front-end web application can be provided by any web server, but it is executed on the user's computer device (smartphone, tablet or notebook), and not on the remote web server. Typically, but not necessarily, a front-end web application is a single-user application, which is not shared with other users.

Part I. Getting Started

In this first part of the book we summarize the web's foundations and show how to build a front-end web application with minimal effort using plain JavaScript and the Local Storage API. It shows how to build such an app with minimal effort, not using any (third-party) framework or library. A front-end web app can be provided by any web server, but it is executed on the user's computer device (smartphone, tablet or notebook), and not on the remote web server. Typically, but not necessarily, a front-end web app is a single-user application, which is not shared with other users.

The minimal version of a JavaScript front-end data management application discussed in this tutorial only includes a minimum of the overall functionality required for a complete app. It takes care of only one object type ("books") and supports the four standard data management operations (Create/Read/Update/Delete), but it needs to be enhanced by styling the user interface with CSS rules, and by adding further important parts of the app's overall functionality.

Chapter 1. A Quick Tour of the Foundations of Web Apps

If you are already familiar with HTML, XML and JavaScript, you can skip this chapter and immediately start developing a minimal front-end web application with JavaScript in the following chapter.

1. The World Wide Web (WWW)

After the Internet had been established in the 1980'ies, Tim Berners-Lee developed the idea and the first infrastructure components of the WWW in 1989 at the European research institution CERN in Geneva, Switzerland. The WWW (or, simply, "the web") is based on

2. HTML and XML

HTML allows to mark up (or describe) the structure of a human-readable web document or web user interface, while XML allows to mark up the structure of all kinds of documents, data files and messages, whether they are human-readable or not. HTML can be based on XML.

2.1. XML documents

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 your own user-defined XML format. XML is used for specifying

  • document formats, such as XHTML5, the Scalable Vector Graphics (SVG) format or the DocBook format,

  • data interchange file formats, such as the Mathematical Markup Language (MathML) or the Universal Business Language (UBL),

  • message formats, such as the web service message format SOAP

2.2. Unicode and UTF-8

XML is based on Unicode, which is a platform-independent character set that includes almost all characters from most of the world's script languages including Hindi, Burmese and Gaelic. Each character is assigned a unique integer code in the range between 0 and 1,114,111. For example, the Greek letter π has the code 960, so it can be inserted in an XML document as π (using the XML entity syntax).

Unicode includes legacy character sets like ASCII and ISO-8859-1 (Latin-1) as subsets.

The default encoding of an XML document is UTF-8, which uses only a single byte for ASCII characters, but three bytes for less common characters.

Almost all Unicode characters are legal in a well-formed XML document. Illegal characters are the control characters with code 0 through 31, except for the carriage return, line feed and tab. It is therefore dangerous to copy text from another (non-XML) text to an XML document (often, the form feed character creates a problem).

2.3. XML namespaces

Generally, namespaces help to avoid name conflicts. They allow to reuse the same (local) name in different namespace contexts.

XML namespaces are identified with the help of a namespace URI (such as the SVG namespace URI "http://www.w3.org/2000/svg"), which is associated with a namespace prefix (such as "svg"). Such a namespace represents a collection of names, both for elements and attributes, and allows namespace-qualified names of the form prefix:name (such as "svg:circle" as a namespace-qualified name for SVG circle elements).

A default namespace is declared in the start tag of an element in the following way:

<html xmlns="http://www.w3.org/1999/xhtml">

This example shows the start tag of the HTML root element, in which the XHTML namespace is declared as the default namespace.

The following example shows a namespace declaration for the SVG namespace:

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
   ...
  </head>
  <body>
    <figure>
      <figcaption>Figure 1: A blue circle</figcaption>
      <svg:svg xmlns:svg="http://www.w3.org/2000/svg">
        <svg:circle cx="100" cy="100" r="50" fill="blue"/>
      </svg:svg>
    </figure>
  </body>
</html>

2.4. Correct XML documents

XML defines two syntactic correctness criteria. An XML document must be well-formed, and if it is based on a grammar (or schema), then it must also be valid against that grammar.

An XML document is called well-formed, if it satisfies the following syntactic conditions:

  1. There must be exactly one root element.

  2. Each element has a start tag and an end tag; however, empty elements can be closed as <phone/> instead of <phone></phone>.

  3. Tags don't overlap, e.g. we cannot have

    <author><name>Lee Hong</author></name>
  4. Attribute names are unique within the scope of an element, e.g. the following code is not correct:

    <attachment file="lecture2.html" file="lecture3.html"/>

An XML document is called valid against a particular grammar (such as a DTD or an XML Schema), if

  1. it is well-formed,

  2. and it respects the grammar.

2.5. The evolution of HTML

The World-Wide Web Committee (W3C) has developed the following important versions of HTML:

  • HTML4 as an SGML-based language (in 1997),

  • XHTML 1 as an XML-based version of HTML4 (in 2000),

  • (X)HTML5 in cooperation (and competition) with the WHAT working group led by Ian Hickson and supported by browser vendors (in 2014).

HTML was originally designed as a structure description language, and not as a presentation description language. But HTML4 has a lot of purely presentational elements such as font. XHTML has been taking HTML back to its roots, dropping presentational elements and defining a simple and clear syntax, in support of the goals of

  • device independence,

  • accessibility, and

  • usability.

We adopt the symbolic equation

HTML = HTML5 = XHTML5

stating that when we say "HTML" or "HTML5", we actually mean XHTML5

because we prefer the clear syntax of XML documents over the liberal and confusing HTML4-style syntax that is also allowed by HTML5.

The following simple example shows the basic code template to be used for any HTML document:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <meta charset="UTF-8" />
  <title>XHTML5 Template Example</title>
 </head>
 <body>
  <h1>XHTML5 Template Example</h1>
  <section><h1>First Section Title</h1>
   ...
  </section>
 </body>
</html>

2.6. HTML forms

For user-interactive web applications, the web browser needs to render a user interface. The traditional metaphor for a software application's user interface is that of a form. The special elements for data input, data output and form actions are called form controls. An HTML form is a section of a document consisting of block elements that contain controls and labels on those controls.

Users complete a form by entering text into input fields and by selecting items from choice controls. A completed form is submitted with the help of a submit button. When a user submits a form, it is sent to a web server either with the HTTP GET method or with the HTTP POST method. The standard encoding for the submission is called URL-encoded. It is represented by the Internet media type application/x-www-form-urlencoded. In this encoding, spaces become plus signs, and any other reserved characters become encoded as a percent sign and hexadecimal digits, as defined in RFC 1738.

Each control has both an initial value and a current value, both of which are strings. The initial value is specified with the control element's value attribute, except for the initial value of a textarea element, which is given by its initial contents. The control's current value is first set to the initial value. Thereafter, the control's current value may be modified through user interaction or scripts. When a form is submitted for processing, some controls have their name paired with their current value and these pairs are submitted with the form.

Labels are associated with a control by including the control as a subelement of a label element ("implicit labels"), or by giving the control an id value and referencing this id in the for attribute of the label element ("explicit labels"). It seems that implicit labels are (in 2015) still not widely supported by CSS libraries and assistive technologies. Therefore, explicit labels may be preferable, despite the fact that they imply quite some overhead by requiring a reference/identifier pair for every labeled HTML form field.

In the simple user interfaces of our "Getting Started" applications, we only need three types of form controls:

  1. single line input fields created with an <input name="..." /> element,

  2. push buttons created with a <button type="button">...</button> element, and

  3. dropdown selection lists created with a select element of the following form:

    <select name="...">
      <option value="value1"> option1 </option>
      <option value="value2"> option2 </option>
      ...
    </select>

An example of an HTML form with implicit labels for creating such a user interface is

<form id="Book">
  <p><label>ISBN: <input name="isbn" /></label></p>
  <p><label>Title: <input name="title" /></label></p>
  <p><label>Year: <input name="year" /></label></p>
  <p><button type="button" id="saveButton">Save</button></p>
</form>

In an HTML-form-based user interface (UI), we have a correspondence between the different kinds of properties defined in the model classes of an app and the form controls used for the input and output of their values. We have to distinguish between various kinds of model class attributes, which are typically mapped to various kinds of input fields. This mapping is also called data binding.

In general, an attribute of a model class can always be represented in the UI by a plain input control (with the default setting type="text"), no matter which datatype has been defined as the range of the attribute in the model class. However, in special cases, other types of input controls (for instance, type="date"), or other controls, may be used. For instance, if the attribute's range is an enumeration, a select control or, if the number of possible choices is small enough (say, less than 8), a radio button group can be used.

3. JavaScript

This section provides a brief overview of JavaScript, assuming that the reader is already familiar with basic programming concepts and has some experience with programming, for instance, in PHP, Java or C#.

JavaScript is an object-oriented programming language that can be used for

  1. Enriching a web page by

    • generating browser-specific HTML content or CSS styling,

    • inserting dynamic HTML content,

    • producing special audio-visual effects (animations).

  2. Enriching a web user interface by

    • implementing advanced user interface components,

    • validating user input on the client side,

    • automatically pre-filling certain form fields.

  3. Implementing a front-end web application with local data storage.

  4. Implementing a front-end component for a distributed web application with remote data storage managed by a back-end component (server-side program).

3.1. Types and data literals in JavaScript

JavaScript has three primitive data types: string, number and boolean. There are three reference types: object, array and function. Arrays and functions are just special kinds of objects. Types are not declared and not checked since a JavaScript program is not compiled. Type conversion (casting) is performed automatically.

The value of a variable may be

  • a data value: either a string, a number, or a boolean,

  • an object reference: either referencing an ordinary object, or an array, or a function,

  • one of the following two special data values: undefined or null.

All numeric data values are represented in 64-bit floating point format with an optional exponent (like in the numeric data literal 3.1e10). There is no explicit type distinction between integers and floating point numbers. For making sure that a numeric value is an integer, or that a string representing a number is converted to an integer, one has to apply the predefined function parseInt. If a numeric expression cannot be evaluated to a number, its value is set to NaN ("not a number").

Like in Java, there are two pre-defined Boolean data literals, true and false, and the Boolean operator symbols are the exclamation mark ! for NOT, the double ampersand && for AND, and the double bar || for OR. When a non-Boolean value is used in a condition, or as an operand of a Boolean expression, it is converted into a Boolean value according to the following rules. The empty string, the (numerical) data literal 0, as well as undefined and null, are mapped to false, and all other values are mapped to true.

For equality and inequality testing, always use the triple equality symbol === and !== instead of the double equality symbol == and !=. Otherwise, for instance, the number 2 would be the same as the string "2", since the condition (2 == "2") evaluates to true in JavaScript.

3.2. Variable scope

In the current version of JavaScript, ECMAScript 5, there are only two kinds of scope for variables: the global scope (with window as the context object) and function scope, but no block scope. Consequently, declaring a variable within a block is confusing and should be avoided. For instance, although this is a frequently used pattern, even by experienced JavaScript programmers, it is a pitfall to declare the counter variable of a for loop in the loop, as in

function foo() {
  for (var i=0; i < 10; i++) {
    ...  // do something with i
  }
}

Instead, and this is exactly how JavaScript is interpreting this code, we should write:

function foo() {
  var i=0;
  for (i=0; i < 10; i++) {
    ...  // do something with i
  }
}

All variables should be declared at the beginning of a function. Only in the next version of JavaScript, ECMAScript 6, block scope will be supported by means of a new form of variable declaration with the keyword let.

3.3. Strict Mode

Starting from ECMAScript 5, we can use strict mode for getting more runtime error checking. For instance, in strict mode, all variables must be declared. An assignment to an undeclared variable throws an exception.

We can turn strict mode on by typing the following statement as the first line in a JavaScript file or inside a <script> element:

'use strict';

It is generally recommended that you use strict mode, except your code depends on libraries that are incompatible with strict mode.

3.4. Different kinds of objects

JavaScript objects are different from classical OO/UML objects. In particular, they need not instantiate a class. And they can have their own (instance-level) methods in the form of method slots, so they do not only have (ordinary) property slots, but also method slots. In addition they may also have key-value slots. So, they may have three different kinds of slots, while classical objects (called "instance specifications" in UML) only have property slots.

A JavaScript object is essentially a set of name-value-pairs, also called slots, where names can be property names, function names or keys of a map. Objects can be created in an ad-hoc manner, using JavaScript's object literal notation (JSON), without instantiating a class:

var person1 = { lastName:"Smith", firstName:"Tom"};
var o1 = {};  // an empty object with no slots

Whenever the name in a slot is an admissible JavaScript identifier, the slot may be either a property slot, a method slot or a key-value slot. Otherwise, if the name is some other type of string (in particular when it contains any blank space), then the slot represents a key-value slot, which is a map element, as explained below.

The name in a property slot may denote either

  1. a data-valued property, in which case the value is a data value or, more generally, a data-valued expression;

    or

  2. an object-valued property, in which case the value is an object reference or, more generally, an object expression.

The name in a method slot denotes a JavaScript function (better called method), and its value is a function definition text.

Object properties can be accessed in two ways:

  1. Using the dot notation (like in C++/Java):

    person1.lastName = "Smith"
  2. Using a map notation:

    person1["lastName"] = "Smith"

JavaScript objects can be used in many different ways for different purposes. Here are five different use cases for, or possible meanings of, JavaScript objects:

  1. A record is a set of property slots like, for instance,

    var myRecord = {firstName:"Tom", lastName:"Smith", age:26}
  2. A map (or 'associative array') supports look-ups of values based on keys like, for instance,

    var numeral2number = {"one":"1", "two":"2", "three":"3"}

    which associates the value "1" with the key "one", "2" with "two", etc. A key need not be a valid JavaScript identifier, but can be any kind of string (e.g. it may contain blank spaces).

  3. An untyped object does not instantiate a class. It may have property slots and function slots like, for instance,

    var person1 = { 
      lastName: "Smith", 
      firstName: "Tom",
      getInitials: function () {
        return this.firstName.charAt(0) + this.lastName.charAt(0); 
      }  
    };
  4. A namespace may be defined in the form of an untyped object referenced by a global object variable, the name of which represents a namespace prefix. For instance, the following object variable provides the main namespace of an application based on the Model-View-Controller (MVC) architecture paradigm where we have three subnamespaces corresponding to the three parts of an MVC application:

    var myApp = { model:{}, view:{}, ctrl:{} };
  5. A typed object o instantiates a class that is defined either by a JavaScript constructor function C or by a factory object F. See ???

3.5. Array lists

A JavaScript array represents, in fact, the logical data structure of an array list, which is a list where each list item can be accessed via an index number (like the elements of an array). Using the term 'array' without saying 'JavaScript array' creates a terminological ambiguity. But for simplicity, we will sometimes just say 'array' instead of 'JavaScript array'.

A variable may be initialized with a JavaScript array literal:

var a = [1,2,3];

Because they are array lists, JavaScript arrays can grow dynamically: it is possible to use indexes that are greater than the length of the array. For instance, after the array variable initialization above, the array held by the variable a has the length 3, but still we can assign a fifth array element like in

a[4] = 7;

The contents of an array a are processed with the help of a standard for loop with a counter variable counting from the first array index 0 to the last array index, which is a.length-1:

for (i=0; i < a.length; i++) { ...}

Since arrays are special types of objects, we sometimes need a method for finding out if a variable represents an array. We can test, if a variable a represents an array by applying the predefined datatype predicate isArray as in Array.isArray( a).

For adding a new element to an array, we append it to the array using the push operation as in:

a.push( newElement);

For deleting an element at position i from an array a, we use the pre-defined array method splice as in:

a.splice( i, 1);

For searching a value v in an array a, we can use the pre-defined array method indexOf, which returns the position, if found, or -1, otherwise, as in:

if (a.indexOf(v) > -1)  ...

For looping over an array a, we have two options: for loops or the array method forEach. In any case, we can use a for loop, as in the following example:

var i=0;
for (i=0; i < a.length; i++) {
  console.log( a[i]);
}

If a is sufficiently small (say, it does not contain more than a few hundred elements), we can use the pre-defined array method forEach, as in the following example, where the parameter elem iteratively assumes each element of the array as its value:

a.forEach( function (elem) {
  console.log( elem);
}) 

For cloning an array a, we can use the array function slice in the following way:

var clone = a.slice(0);

3.6. Maps

A map (also called 'hash map' or 'associative array') provides a mapping from keys to their associated values. The keys of a map may be string literals that include blank spaces like in:

var myTranslation = { 
    "my house": "mein Haus", 
    "my boat": "mein Boot", 
    "my horse": "mein Pferd"
}

A map is processed with the help of a special loop where we loop over all keys of the map using the pre-defined function Object.keys(m), which returns an array of all keys of a map m. For instance,

var i=0, key="", keys=[];
keys = Object.keys( myTranslation);
for (i=0; i < keys.length; i++) {
  key = keys[i];
  alert('The translation of '+ key +' is '+ myTranslation[key]);
}

For adding a new entry to a map, we associate the new value with its key as in:

myTranslation["my car"] = "mein Auto";

For deleting an entry from a map, we can use the pre-defined delete operator as in:

delete myTranslation["my boat"];

For searching in a map if it contains an entry for a certain key, such as for testing if the translation map contains an entry for "my bike" we can check the following:

if (myTranslation["my bike"])  ...

For looping over a map m, we first convert it to an array of its keys with the help of the predefined Object.keys method, and then we can use either a for loop or the forEach method. The following example shows how to loop with for:

var i=0, key="", keys=[];
keys = Object.keys( m);
for (i=0; i < keys.length; i++) {
  key = keys[i];
  console.log( m[key]);
}

Again, if m is sufficiently small, we can use the forEach method, as in the following example:

Object.keys( m).forEach( function (key) {
  console.log( m[key]);
}) 

Notice that using the forEach method is more concise.

3.7. JavaScript supports four types of basic data structures

In summary, the four types of basic data structures supported are:

  1. array lists, such as ["one","two","three"], which are special JS objects called 'arrays', but since they are dynamic, they are rather array lists as defined in the Java programming language.

  2. maps, which are also special JS objects, such as {"one":1,"two":2,"three":3}, as discussed above,

  3. records, which are special JS objects, such as {firstName:"Tom",lastName:"Smith"}, as discussed above,

  4. JSON tables, which are special maps where the values are entity records (with a primary key slot), and the keys are the primary keys of these entity records.

Notice that our distinction between maps, records and JSON tables is a purely conceptual distinction, and not a syntactical one. For a JavaScript engine, both {firstName:"Tom",lastName:"Smith"} and {"one":1,"two":2,"three":3} are just objects. But conceptually, {firstName:"Tom",lastName:"Smith"} is a record because firstName and lastName are intended to denote properties or fields, while {"one":1,"two":2,"three":3} is a map because "one" and "two" are not intended to denote properties/fields, but are just arbitrary string values used as keys for a map.

Making such conceptual distinctions helps to better understand the options offered by JavaScript.

3.8. Defining and using classes

The concept of a class is fundamental in object-oriented programming. Objects instantiate (or are classified by) a class. A class defines the properties and methods for the objects that instantiate it. Having a class concept is essential for being able to implement a data model in the form of model classes. However, classes and their inheritance/extension mechanism are over-used in classical OO languages, such as in Java, where all code has to live in the context of a class. This is not the case in JavaScript where we have the freedom to use classes for implementing model classes only, while keeping method libraries in namespace objects.

There is no explicit class concept in JavaScript. However, classes can be defined in two ways:

  1. In the form of a constructor function that allows to create new instances of the class with the help of the new operator. This is the classical approach recommended in the Mozilla JavaScript documents.

  2. In the form of a factory object that uses the predefined Object.create method for creating new instances of the class. In this approach, the constructor-based inheritance mechanism has to be replaced by another mechanism. Eric Elliott has argued that factory-based classes are a viable alternative to constructor-based classes in JavaScript (in fact, he even condemns the use of classical inheritance and constructor-based classes, throwing out the baby with the bath water).

Since we often need to define class hierarchies, and not just single classes, these two alternative approaches cannot be mixed within the same class hierarchy, and we have to make a choice whenever we build an app. Their pros and cons are summarized in Table 1.1.

Table 1.1. Constructor-based versus factory-based classes

Pros Cons
Constructor-based classes Higher performance object creation
  1. Do not allow to declare properties

  2. Incompatible with object pools

  3. Do not support multiple inheritance

  4. Do not support multiple classification

Factory-based classes
  1. Allow property declarations (with a property label, a range and many other constraints)

  2. Can support object pools

  3. Can support multiple inheritance

  4. Can support multiple classification

Lower performance object creation

3.8.1. Constructor-based classes

A constructor-based class can be defined in two or three steps. First define the constructor function that defines the properties of the class and assigns them the values of the constructor parameters:

function Person( first, last) {
  this.firstName = first; 
  this.lastName = last; 
}

Next, define the instance-level methods of the class as method slots of the object property prototype of the constructor:

Person.prototype.getInitials = function () {
  return this.firstName.charAt(0) + this.lastName.charAt(0); 
}

Finally, class-level ("static") methods can be defined as method slots of the constructor, as in

Person.checkName = function (n) {
  ... 
}

An instance of such a constructor-based class is created by applying the new operator to the constructor function and providing suitable arguments for the constructor parameters:

var pers1 = new Person("Tom","Smith");

The method getInitials is invoked on the object pers1 of type Person by using the 'dot notation':

alert("The initials of the person are: " + pers1.getInitials());

When a typed object o is created with o = new C(...), where C references a named function with name "C", the type (or class) name of o can be retrieved with the introspective expression o.constructor.name. which returns "C" (however the function name property is not yet supported by Internet Explorer up to the current version 11).

In this approach, an inheritance mechanism is provided via the predefined prototype property of a constructor function. This will be explained in Part 5 of this tutorial (on subtyping).

3.8.2. Factory-based classes

In this approach we define a JavaScript object Person (actually representing a class) with a special create method that invokes the predefined Object.create method for creating objects of type Person:

var Person = {
  typeName: "Person",
  properties: {
    firstName: {range:"NonEmptyString", label:"First name", 
        writable: true, enumerable: true},
    lastName: {range:"NonEmptyString", label:"Last name", 
        writable: true, enumerable: true}
  },
  methods: {
    getInitials: function () {
      return this.firstName.charAt(0) + this.lastName.charAt(0); 
    }
  },
  create: function (slots) {
    var obj=null, properties = this.properties;
    // create object
    obj = Object.create( this.methods, properties);
    // add property slot for direct type
    Object.defineProperty( obj, "type", 
        {value: this, writable: false, enumerable: true});
    // initialize object
    Object.keys( slots).forEach( function (prop) {
      if (properties[prop]) obj[prop] = slots[prop];
    })
    return obj;
  }
};

Notice that our Person object actually represents a factory-based class. An instance of such a factory-based class is created by invoking its create method:

var pers1 = Person.create( {firstName:"Tom", lastName:"Smith"});

The method getInitials is invoked on the object pers1 of type Person by using the 'dot notation', like in the constructor-based approach:

alert("The initials of the person are: " + pers1.getInitials());

Notice that each property declaration for an object created with Object.create has to include the 'descriptors' writable: true and enumerable: true, as in lines 5 and 7 of the Person object definition above.

In a general approach, like in the model-based development framework mODELcLASSjs presented in part 6 of this tutorial, we would not repeatedly define the create method in each class definition, but rather have a generic constructor function for defining factory-based classes. Such a factory class constructor, like mODELcLASS, would also provide an inheritance mechanism by merging the own properties and methods with the properties and methods of the superclass.

3.9. JavaScript as an object-oriented language

JavaScript is object-oriented, but in a different way than classical OO programming languages such as Java and C++. There is no explicit class concept in JavaScript. Rather, classes have to be defined in the form of special objects: either as constructor functions or as factory objects.

However, objects can also be created without instantiating a class, in which case they are untyped, and properties as well as methods can be defined for specific objects independently of any class definition. At run time, properties and methods can be added to, or removed from, any object and class.

3.10. Further reading about JavaScript

Good open access books about JavaScript are

Chapter 2. Building a Minimal Front-End App with BackboneJS in Seven Steps

In this chapter, we show how to build a minimal front-end web application with the BackboneJS framework and the cloud storage service Parse.com. Most parts of the app's code are executed on the front-end, while the persistent storage functionality is executed on the back-end server(s) operated by Parse.

The purpose of our example app is to manage information about books. That is, we deal with a single object type: Book, as depicted in Figure Figure 2.1.

Figure 2.1. The object type Book.

The object type Book.

What do we need for such a data management app? There are four standard use cases, which have to be supported by the app:

  1. Create a new book record by allowing the user to enter the data of a book that is to be added to the collection of stored book records.

  2. Read (or retrieve) all books from the data store and show them in the form of a list.

  3. Update the data of a book record.

  4. Delete a book record.

These four standard use cases, and the corresponding data management operations, are often summarized with the acronym CRUD.

For entering data with the help of the keyboard and the screen of our computer, we can use HTML forms, which provide the user interface technology for web applications.

For maintaining a collection of persistent data objects, we need a storage technology that allows to keep data objects in persistent records on a secondary storage device, either locally or remotely. An attractive option for remote storage is using a cloud storage service, where data is stored in logical data pools, which may physically span multiple servers in a cloud infrastructure owned and managed by a cloud service company. A cloud storage service is typically reachable via a web API, which may be integrated with the data management methods of a framework, as in the case of the cloud storage service provided by Parse.com. For our minimal BackboneJS app, we will therefore use the adapted version of BackboneJS provided by Parse.com.

1. Using the Cloud Storage Service of Parse.com with BackboneJS

Parse.com is a cloud service platform providing a remote storage service that can be invoked from a JavaScript front-end program based on an adapted version of the BackboneJS library. More specifically, for using the storage service we need three BackboneJS/Parse API classes:

  1. Parse.Object allows to define model classes for creating objects that can be saved to, and retrievd from, the Parse data store;

  2. Parse.Query allows to query the Parse data store;

  3. Parse.Collection allows to represent and process collections of objects, such as the population of a table or the result set of a query.

For maintaining a collection of data objects in a persistent data store, the Parse platform provides back-end storage services, that can be invoked via a web API from BackboneJS model objects with methods such as save and destroy using the JavaScript XML HTTP Request (XHR) API for exchanging HTTP messages with the Parse server. In general, sending data to a remote back-end server from a JavaScript front-end program is done with asynchronous remote procedure calls using XHR. Since such a remote procedure call can result either in a reply message confirming the successful completion of the call or indicating an error state, we generally need to specify two JavaScript methods as the arguments for calling the remote procedure asynchronously: the first method is invoked when the remote procedure call has been successfully completed, and the second one when it failed.

1.1. Getting Started with Parse

Any web page of your BackboneJS/Parse app needs to load the BackboneJS/Parse library from the Parse content delivery network with the help of the following HTML script element:

<script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>

For being able to use a Parse service via a BackboneJS/Parse method invocation in your app's JavaScript code, you need to set up a user account on the Parse website. Then, go to the Dashboard and create a new Parse app account using the name of your app. After your Parse app account has been created, you can look up its Application ID and its JavaScript Key under "Application Keys". You need to provide these two keys as the arguments of the Parse.initialize method when you initialize your app, as in the following code pattern:

Parse.initialize("APPLICATION_ID", "JAVASCRIPT_KEY");

1.2. The BackboneJS/Parse Model Layer

1.2.1. Defining a model class

A BackboneJS/Parse model class is defined by extending the predefined class Parse.Object using the function Parse.Object.extend, providing the name of the new class and certain class definition slots. In the simplest case, such a definition only includes a defaults slot for defining the class attributes' default values. This is shown in the following definition of a model class Book, which serves as our running example:

var Book = Parse.Object.extend("Book", {
  defaults: {
    isbn: "",
    title: "",
    year: 0
  }
});

Notice that the defaults slot sort of defines the properties of a class. We create a new object as an instance of a model class in the following way:

var book = new Book({
      isbn: "123456789X", 
      title: "BackboneJS/Parse Tutorial", 
      year: 2013
});

After the object has been created, its property slots are not represented as direct slots (e.g., there is no book.isbn slot), but rather as key-value slots in the predefined map-valued property attributes

We can access the value of a property with the help of the method get as in:

var publicationYear = book.get("year");

We can assign a value to a property with the help of the method set as in:

book.set("year", 2014);

1.2.2. Representing the collection of all instances of a class

For representing the collection of all the instances of a class C managed by the application, we define the class-level property C.instances. This property is set to the Parse collection representing the population of the class in the following way:

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

The class-level property Book.instances is set to the Parse collection that stores the results from the query retrieving the entire population of the Book table with the help of the query object new Parse.Query( Book).

1.2.3. Fetching data from the Parse data store

For initializing a data management use case, we generally need to fetch the current data tfrom the Parse data store. Since Book.instances is a Parse collection, we can apply the fetch method to it for refreshing its contens from the current state of the Parse data store:

Book.instances.fetch({  // load data from Parse data store
  success: function (coll) {
    ...;  // set up the user interface
  },
  error: function (coll, error) {
    console.log("Error: " + error);
  }
});

1.2.4. Writing data to the Parse data store

Writing data to the Parse data store is done with the help of the Parse method save, as shown in the following example

var book = new Book( ...);
book.save( null, {
  success: function (obj) {
    console.log("Saved: " + JSON.stringify(obj));
  },
  error: function (obj, error) {
    console.log("Error: " + error + " for " + JSON.stringify(obj));
  }
});

Notice that, as an asynchronous remote procedure call, the Parse save method is invoked with a success and an error callback method as arguments.

To make sure the data was saved, you can look it up with the web user interface of Parse. You should see something like in the following screen shot.

Figure 2.2. Looking up your app's data with the web user interface of Parse.

Looking up your app's data with the web user interface of Parse.

Three attributes are provided by Parse automatically. The attribute objectId is a unique identifier for each Parse object, while createdAt and updatedAt represent the time that each object was created and last modified in the Parse data store. Each of these attributes is filled in by Parse, so they are read-only in your app's JavaScript code.

1.2.5. Destroying a Parse object

Deleting an existing object from the Parse data store is done with the help of the Parse method destroy:

var book = Book.instances.get( id);
book.destroy({
  success: function (obj) {
    console.log("Deleted book "+ JSON.stringify(obj));
  },
  error: function (obj, error) {
    console.log( error + "\nCannot delete the book " + 
        JSON.stringify(obj) +". Not found in database!");
  }
});

2. Building a Minimal BackboneJS/Parse App in Seven Steps

2.1. Step 1 - Set up the Folder Structure

In the first step, we set up our folder structure for the application. We pick a name for our app, such as "Public Library", and a corresponding (possibly abbreviated) name for the application folder, such as "publicLibrary". Then we create this folder on our computer's disk and add three subfolders: "css" for our CSS style files, "lib" for the libraries used in the app, and "src" for our JavaScript source code files. In the "src" folder, we create the subfolders "model", "view" and "ctrl", following the Model-View-Controller application architecture paradigm. Thus, we get the following folder structure:

publicLibrary
  css
  lib
  src
    ctrl
    model
    view

2.2. Step 2 - Write the Model Code

In the second step, we create the model classes for our app, using one JavaScript file for each model class. In the information model for our example app, shown in Figure Figure 2.1, there is only one class, representing the object type Book. So, in the folder src/model, we create a file Book.js containig the entire code for the model class Book.

The first part of this JavaScript contains the Parse/Backbone model class definition, which for now only includes a defaults property for defining the attributes' default values:

var Book = Parse.Object.extend("Book", {
  defaults: {
    isbn: "",
    title: "",
    year: 0
  }
});

In addition to defining the Parse/Backbone model class, we also define the following items in the Book.js file:

  1. A class-level property Book.instances representing the collection of all Book instances managed by the application.

  2. A class-level method Book.add for creating a new book record in the Parse data store.

  3. A class-level method Book.update for updating a book record in the Parse data store.

  4. A class-level method Book.destroy for deleting a book record from the Parse data store.

2.2.1. Representing the collection of all Book instances

For representing the collection of all Book instances managed by the application, we define and initialize the class-level property Book.instances in the following way:

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

The class-level property Book.instances is set to the Parse collection object obtained by converting the result list from retrieving the population of the Book table with the help of the query object new Parse.Query( Book).

2.2.2. Creating a new Book record

Creating and storing a new book record in the Parse data store is done with the help of the following class-level method:

Book.add = function (slots) {
  var book = null;
  // make sure that year is an integer
  slots.year = parseInt( slots.year);
  book = new Book( slots);
  book.save( null, {
    success: function (obj) {
      console.log("Saved: "+ JSON.stringify(obj));
    },
    error: function (obj, error) {
      console.log("Error: "+ error + " for "+ JSON.stringify(obj));
    }
  });
};

2.2.3. Updating an existing Book record

An existing book record can be updated in the Parse data store witrh the help of the following class-level method:

Book.update = function (id, slots) {
  var book = Book.instances.get( id);
  // make sure that year is an integer
  slots.year = parseInt( slots.year);  
  if (book.title !== slots.title) { 
    book.set("title", slots.title);
  }
  if (book.year !== slots.year) { 
    book.set("year", slots.year);
  }
  book.save( null, {
    success: function (obj) {
      console.log("Saved: "+ JSON.stringify(obj));
    },
    error: function (obj, error) {
      console.log("Error: "+ error + "Model: "+ JSON.stringify(obj));
    }
  });
};

2.2.4. Deleting an existing Book record

Deleting an existing book record in the Parse data store is done witrh the help of the following class-level method:

Book.destroy = function (id) {
  var book = Book.instances.get( id);
  Book.instances.remove( book);
  book.destroy({
    success: function (obj) {
      console.log("Deleted book "+ JSON.stringify(obj));
    },
    error: function (obj, error) {
      console.log( error + "\nCannot delete the book " + 
          JSON.stringify(obj) +". Not found in database!");
    }
  });
};

2.2.5. Creating test data

The following procedure creates 3 book objects and saves them in the Parse data store using Parse.Object.saveAll:

Book.createTestData = function () {
  var book1 = new Book(
        {isbn:"006251587X", title:"Weaving the Web", year:2000});
  var book2 = new Book(
        {isbn:"0465026567", title:"Gödel, Escher, Bach", year:1999});
  var book3 = new Book(
        {isbn:"0465030793", title:"I Am A Strange Loop", year:2008});
  var list = [book1, book2, book3];
  Parse.Object.saveAll( list, {
    success: function (list) {
      console.log("Saved: "+ list.length);
    },
    error: function (error) {
      console.log("Error: "+ error);
    }
  });
};

The Parse user interface allows you to take a look at your Parse tables, and you should see a table like the following.

Table 2.1. Table 2: A collection of book objects represented as a Parse table

ISBN Title Year
006251587X Weaving the Web 2000
0465026567 Gödel, Escher, Bach 1999
0465030793 I Am A Strange Loop 2008

2.3. Step 3 - Initialize the App

We initialize the application in src/ctrl/initialize.js by defining its namespaces and by invoking the Parse.initialize procedure:

var pl = { model:{}, view:{}, ctrl:{} };
Parse.initialize("APPLICATION_ID", "JAVASCRIPT_KEY");

Here, the main namespace is defined to be pl, standing for "public library", with the three subnamespaces model, view and ctrl being initially empty objects.

You have to replace the srtings APPLICATION_ID with your Parse Application ID, and JAVASCRIPT_KEY with your Parse JavaScript Key, which you can look up on your Parse account page.

2.4. Step 4 - Develop the Use Case "List Objects"

This use case represents the "Read" from the four basic data management use cases Create-Read-Update-Delete (CRUD). The simple logic of this use case consists of two steps:

  1. Read the collection of all objects from the persistent data store.

  2. Display each object as a row in a HTML table on the screen.

The user interface for this use case is provided by the HTML file listBooks.html (in the main folder publicLibrary), consisting of the following code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal Parse/Backbone App Example</title>
  <script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>
  <script src="src/ctrl/initialize.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>
</body>
</html>

Notice that this HTML file, which is used for running the "list books" use case, loads four JavaScript files: the Parse function library parse-1.3.5.min.js from the Parse website, the application initialization code file src/ctrl/initialize.js, the model code file src/model/Book.js, the view code file src/view/listBooks.js and the controller code file src/ctrl/listBooks.js. While the model code file and the application initialization code file have been discussed above, we now develop the "list books" use case's controller and view code files.

For initializing this use case, we have the following code in src/ctrl/listBooks.js:

pl.ctrl.listBooks = {
  initialize: function () {
    Book.instances.fetch({  // load data from Parse data store
      success: function (coll) {
        pl.view.listBooks.setUpUserInterface(); 
      },
      error: function (coll, error) {
        console.log("Error: " + error);
      }
    });
  }
};

Notice that the initialize procedure invokes the setUpUserInterface procedure when the instances of the Book model class can be fetched from the Parse data store (in the success callback).

The setUpUserInterface procedure sets up the user interface by constructing a HTML table populated with the book records from the Book.instances collection:

pl.view.listBooks = {
  setUpUserInterface: function () {
    var tableBodyEl = document.querySelector("table#books>tbody");
    Book.instances.forEach( function (bkObj) {
      var row = tableBodyEl.insertRow(-1);
      row.insertCell(-1).textContent = bkObj.get("isbn");      
      row.insertCell(-1).textContent = bkObj.get("title");  
      row.insertCell(-1).textContent = bkObj.get("year");
    });
  }
};

The view table is created in a loop over all elements of the Parse collection Book.instances. In each step of this loop, a new HTML table row is created in the table body element with the help of the DOM operation insertRow, and then three cells are created within this row with the help of the DOM operation insertCell: the first one for the isbn property value of the book object, and the second and third ones for its title and year property values, which are accessed using the get method on the book Parse object. Both insertRow and insertCell have to be invoked with the argument -1 for making sure that new elements are appended to the list of rows and cells.

2.5. Step 5 - Develop the Use Case "Create Object"

The HTML user interface for the use case "create book" requires a form with a form field for each attribute of the Book model class. For our example app, this page would be called createBook.html (in the main folder publicLibrary) and would contain the following HTML code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Minimal Parse/Backbone App Example</title>
    <script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>
    <script src="src/ctrl/initialize.js"></script>
    <script src="src/model/Book.js"></script>
    <script src="src/view/createBook.js"></script>
    <script src="src/ctrl/createBook.js"></script>
    <script>
      window.addEventListener("load", pl.ctrl.createBook.initialize);
    </script>
  </head>
  <body>
    <h1>Public Library: Create a new book record</h1>
    <form id="Book">
      <p><label>ISBN: <input name="isbn" /></label></p>
      <p><label>Title: <input name="title" /></label></p>
      <p><label>Year: <input name="year" /></label></p>
      <p><button type="button" name="commit">Save</button></p>
    </form>
    <p><a href="index.html">Main menu</a></p>
  </body>
</html>

As in all four use cases, we load both a use-case-specific controller code file, src/ctrl/createBook.js, and a view code file:, src/view/createBook.js, which are presented in the next two program listings.

The controller code file just contains an initialize procedure for initializing the use case in the same way as for the "list books" use case, except that after succesfully fetching all book records from the Parse data store, the procedure for settiing up the "create book" user interface is invoked:

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

The view code file just contains the setUpUserInterface procedure for setting up the HTML user interface of the use case:

pl.view.createBook = {
  setUpUserInterface: function () {
    var saveButton = document.forms['Book'].commit;
    saveButton.addEventListener("click", function (e) {
      var formEl = document.forms['Book'];
      var slots = { isbn: formEl.isbn.value, 
            title: formEl.title.value, 
            year: formEl.year.value
          };
      Book.add( slots);
      formEl.reset();
    });
  }
};

In the event handler for clicking the save button, the form field values corresponding to the Book attributes isbn, title and year are retrieved and passed to the method Book.add for creating and storing a new book record. Finally, the "create book" form is reset

2.6. Step 6 - Develop the Use Case "Update Object"

In the form for the "update book" user interface (publicLibrary/updateBook.html), the form field for the standard identifier attribute isbn is an HTML output element because the user is not allowed to change the standard identifier of an object in the "update object" use case. Otherwise, the form is very similar to createBook.html. It contains the following HTML code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Minimal Parse/Backbone App Example</title>
    <script src="http://www.parsecdn.com/js/parse-1.3.5.min.js"></script>
    <script src="src/ctrl/initialize.js"></script>
    <script src="src/model/Book.js"></script>
    <script src="src/view/updateBook.js"></script>
    <script src="src/ctrl/updateBook.js"></script>
    <script>
      window.addEventListener("load", pl.ctrl.updateBook.initialize);
    </script>
  </head>
  <body>
    <h1>Public Library: Update a book record</h1>
    <form id="Book">
      <p><label>Select book: 
        <select name="selectBook"><option value="0">-----</option></select>
      </label></p>
      <p><label>ISBN: <output name="isbn" /></label></p>
      <p><label>Title: <input name="title" /></label></p>
      <p><label>Year: <input name="year" /></label></p>
      <p><button type="button" name="commit">Save Changes</button></p>
    </form>
    <p><a href="index.html">Main menu</a></p>
  </body>
</html>

The controller code file just contains an initialize procedure for initializing the use case in the same way as before:

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

The view code file just contains the setUpUserInterface procedure for setting up the HTML user interface of the use case:

pl.view.updateBook = {
  setUpUserInterface: function () {
    var formEl = document.forms['Book'],
        saveButton = formEl.commit,
        selectBookEl = formEl.selectBook;
    // 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 its 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");
      } else {  // no book selected
        formEl.isbn.value = "";
        formEl.title.value = "";
        formEl.year.value = "";
      }
    });
    //  event handler for clicking the save button
    saveButton.addEventListener("click", function () {
      var formEl = document.forms['Book'];
      var bookObjId = formEl.selectBook.value;
      var slots = { isbn: formEl.isbn.value, 
            title: formEl.title.value, 
            year: formEl.year.value
          };
      Book.update( bookObjId, slots);
      formEl.reset();
    });
  }
};

This procedure has three parts:

  1. The book selection list is populated with the titles of the books that are already stored, while the value of each selection option is set to the book's Parse object ID.

  2. In a change event handler for the book selection field, we populate the form with the data of the selected book whenever a book is selected.

  3. In the event handler for clicking the update button we invoke the method Book.update for updating the selected book record, and then reset the "update book" form.

2.7. Step 7 - Develop the Use Case "Delete Object"

In the form for the "delete book" user interface (publicLibrary/deleteBook.html), we just need a book selection field, as in updateBook.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Minimal Parse/Backbone App Example</title>
    <script src="http://www.parsecdn.com/js/parse-1.2.8.min.js"></script>
    <script src="src/ctrl/initialize.js"></script>
    <script src="src/model/Book.js"></script>
    <script src="src/view/deleteBook.js"></script>
    <script src="src/ctrl/deleteBook.js"></script>
    <script>
      window.addEventListener("load", pl.ctrl.deleteBook.initialize);
    </script>
  </head>
  <body>
    <h1>Public Library: Delete a book record</h1>
    <form id="Book">
      <p>
        <label>Select book: <select name="selectBook"></select></label>
      </p>
      <p><button type="button" name="commit">Delete</button></p>
    </form>
    <p><a href="index.html">Main menu</a></p>
  </body>
</html>

The controller code file again just contains an initialize procedure for initializing the use case by fetching all book records from the Parse data store and invoking the setUpUserInterface procedure:

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

The view code file just contains the setUpUserInterface procedure for setting up the HTML user interface of the use case:

pl.view.deleteBook = {
  setUpUserInterface: function () {
    var deleteButton = document.forms['Book'].commit;
    var selectEl = document.forms['Book'].selectBook;
    // populate the select list with books
    Book.instances.forEach( function (book) {
      var bookOptionEl = document.createElement("option");
      bookOptionEl.text = book.get("title");
      bookOptionEl.value = book.id;  // the Parse object ID
      selectEl.add( bookOptionEl, null);
    });
    deleteButton.addEventListener("click", function () {
      var selectEl = document.forms['Book'].selectBook,
          id = selectEl.value;
      if (id) {
        Book.destroy( id);
        selectEl.remove( selectEl.selectedIndex);
      }
    });
  }
};

This procedure has two parts:

  1. The book selection list is populated with the titles of the books that are already stored, while the value of each selection option is set to the book's Parse object ID.

  2. In the event handler for clicking the delete button we invoke the method Book.destroy for deleting the selected book record, and then also remove the book from the options of the selection list.

3. Run the App and Get the Code

You can run the minimal app from our server or download the code as a ZIP archive file. Recall that you have to edit the src/ctrl/initialize.js file and enter your Parse APPLICATION_ID and JAVASCRIPT_KEY before you run it, as discussed above.

4. Points of Attention

The code of this app should be extended by

  • adding some CSS styling for the user interface pages and

  • adding constraint validation.

We show how to do this in the follow-up tutorial Adding Constraint Validation.

Notice that in this tutorial, we have made the assumption that all application data can be loaded into main memory (like all book data is loaded into the collection Book.instances). In the case of remote storage, this approach only works for very small databases. Otherwise, it's no longer possible to load the entire population of all tables into main memory, but we have to use a technique where only parts of the table contents are loaded.

Another issue with the do-it-yourself code of this example app is the boilerplate code needed 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 another tutorial, we 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.

Part II. Integrity Constraints

For catching various cases of flawed data, we need to define suitable integrity constraints that can be used by the application's data validation mechanisms. Integrity constraints may take many different forms. The most important type of integrity constraints are property constraints, which define conditions on the admissible property values of an object of a certain type.

Chapter 3. Integrity Constraints and Data Validation

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.

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 with this constraint must not be changed after it has been assigned initially.

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.

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. Notice that in HTML5, there is some support of data validation for user input in form fields.

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.

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. 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 {
    ...
  }
};

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.

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 3.1 below.

Figure 3.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 {
    ...
  }
};

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 3.2 below.

Figure 3.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 {
    ...
  }
};

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 3.3 below, which contains two examples of properties with cardinality constraints.

Figure 3.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.

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 3.4 below.

Figure 3.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.

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 3.5 below.

Figure 3.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.

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.

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.

11. Constraint Validation in MVC Applications

Unfortunately, many MVC application development frameworks do not provide sufficient support for integrity constraints and data validation.

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. 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, typically 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.

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.

However, it is not sufficient to perform data validation in the user interface. 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 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.

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 4. Validating Integrity Constraints in a Parse/BackboneJS App

The minimal Parse/BackboneJS 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 4.1 below.

Figure 4.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.

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.

1. Constraint Validation in the BackboneJS Model Layer

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 objcet), 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).

2. 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.

3. 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. 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 a two-fold validation of constraints, 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. In fact, in an application based on a DBMS we may even 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 certain relationship (such as referential integrity) constraints may also be violated through a delete operation, but in our single-class example we don't have to consider this.

4. Make a Parse/BackboneJS Data Model

Using the information design model shown in Figure 4.1 above as the starting point, we make a Parse/BackboneJS 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 Parse/BackboneJS data model shown on the right hand side of the mapping arrow in the following figure.

Figure 4.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).

5. 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 and util.js. Thus, we end up with the following folder structure containing four initial files:

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

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

5.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.

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

We add two 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.

5.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 5.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 src/model 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 6.

Figure 4.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>Parse/BackboneJS Validation Example App</title>
  <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.3.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/browserEnhancements.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>
</head>
<body>
  <h1>Public Library</h1>
  <menu>
    <li><a href="listBooks.html">List all books</a></li>
    <li><a href="createBook.html">Add a new book</a></li>
    <li><a href="updateBook.html">Update a book record</a></li>
    <li><a href="deleteBook.html">Delete a book record</a></li>
    <li><button type="button" onclick="Book.createTestData()">Create test data</button> 
      <button type="button" onclick="Book.clearData()">Clear data</button></li>
  </menu>
</body>
</html>

6. Write the Model Code

How to Encode a BackboneJS Data Model

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

6.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.

6.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 Parse/BackboneJS 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 = {};  // associative array 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 attribute 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();

6.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 5.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;
};

6.4. Encode the property setters

Encode 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 encoded in the following way:

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

There are similar setters for the properties title and year.

6.5. 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.isbn + ", title:" + 
      this.title + ", year:" + this.year +"}"; 
};

6.6. 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.loadAllInstances for loading all managed Book instances from the persistent data store.

  2. Book.saveAllInstances for saving all managed Book instances to the persistent data store.

  3. Book.createRow for creating a new Book instance.

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

  5. Book.deleteRow 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.createRow, Book.updateRow 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 createRow and updateRow, 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.createRow, 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.createRow = function (slots) {
  var book = null;
  try {
    book = new Book( slots);
  } catch (e) {
    console.log( e.name +": "+ e.message);
    book = null;
  }
  if (book) {
    Book.instances[book.isbn] = book;
    console.log( book.toString() + " created!");
  }
};

Likewise, when a constraint violation is detected in one of the setters invoked in Book.updateRow, a constraint violation error message is created (in line 16) and the previous state of the object is restored (in line 19). Otherwise, a status message is created (in lines 23 or 25), as shown in the following program listing:

Book.updateRow = function (slots) {
  var book = Book.instances[slots.isbn],
      noConstraintViolated = true,
      updatedProperties = [],
      objectBeforeUpdate = util.cloneObject( book);
  try {
    if (book.title !== slots.title) {
      book.setTitle( slots.title);
      updatedProperties.push("title");
    }
    if (book.year !== parseInt( slots.year)) {
      book.setYear( slots.year);
      updatedProperties.push("year");
    }
  } catch (e) {
    console.log( e.name +": "+ e.message);
    noConstraintViolated = false;
    // restore object to its state before updating
    Book.instances[slots.isbn] = objectBeforeUpdate;
  }
  if (noConstraintViolated) {
    if (updatedProperties.length > 0) {
      console.log("Properties " + updatedProperties.toString() +
          " modified for book " + slots.isbn);
    } else {
      console.log("No property value changed for book " + slots.isbn + " !");
    }
  }
};

7. 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.

7.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 7.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.3.0/pure-min.css" />
  <link rel="stylesheet" href="css/main.css" /> 
  <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>
  <nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>

For the "create book" use case, we get the following code in createBook.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/combo?pure/0.3.0/base-
       min.css&pure/0.3.0/forms-min.css" />
  <link rel="stylesheet" href="css/main.css" /> 
  <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/createBook.js"></script>
  <script src="src/ctrl/createBook.js"></script>
  <script>
    window.addEventListener("load", pl.ctrl.createBook.initialize);
  </script>
  </head>
  <body>
  <h1>Public Library: Create a new book record</h1>
  <form id="Book" class="pure-form pure-form-aligned">
    <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>
</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.

7.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.

7.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 and pl.ctrl.createBook.loadData defined in the controller file ctrl/createBook.js with the following code:

pl.ctrl.createBook = {
  initialize: function () {
    pl.ctrl.createBook.loadData();
    pl.view.createBook.setupUserInterface();
  },
  loadData: function () {
    Book.loadAllInstances();
  }
};

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

7.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 row={}, key="", keys = Object.keys( Book.instances);
    for (var i=0; i < keys.length; i++) {
      key = keys[i];
      row = tableBodyEl.insertRow(-1);
      row.insertCell(-1).textContent = Book.instances[key].isbn;
      row.insertCell(-1).textContent = Book.instances[key].title;
      row.insertCell(-1).textContent = Book.instances[key].year;
    }
  }
};

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 = {
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        submitButton = formEl.commit;
    submitButton.addEventListener("click", 
        this.handleSubmitButtonClickEvent);
    formEl.isbn.addEventListener("input", function () { 
        formEl.isbn.setCustomValidity( 
            Book.checkIsbnAsId( formEl.isbn.value).message);
    });
    formEl.title.addEventListener("input", function () { 
        formEl.title.setCustomValidity( 
            Book.checkTitle( formEl.title.value).message);
    });
    formEl.year.addEventListener("input", function () { 
        formEl.year.setCustomValidity( 
            Book.checkYear( formEl.year.value).message);
    });
    // neutralize the submit event
    formEl.addEventListener( 'submit', function (e) { 
        e.preventDefault();;
        formEl.reset();
    });
    window.addEventListener("beforeunload", function () {
        Book.saveAllInstances(); 
    });
  },
  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:

handleSubmitButtonClickEvent: function () {
  var formEl = document.forms['Book'];
  var slots = { isbn: formEl.isbn.value, 
                title: formEl.title.value, 
                year: formEl.year.value};
  // check all input fields and create error messages in case of constraint violations
  formEl.isbn.setCustomValidity( Book.checkIsbnAsId( 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.createRow( slots);
  }
}

By invoking checkValidity() on the form element, we make sure that the form data is only saved (by Book.createRow), 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'],
        submitButton = formEl.commit,
        selectBookEl = formEl.selectBook;
    // set up the book selection list
    util.fillWithOptionsFromAssocArray( Book.instances, selectBookEl, 
    // when a book is selected, populate the form with its data
    selectBookEl.addEventListener("change", function () {
      var bookKey = selectBookEl.value;
      if (bookKey) {
        book = Book.instances[bookKey];
        formEl.isbn.value = book.isbn;
        formEl.title.value = book.title;
        formEl.year.value = book.year;
      } else {
        formEl.isbn.value = "";
        formEl.title.value = "";
        formEl.year.value = "";
      }
    });
    formEl.title.addEventListener("input", function () { 
        formEl.title.setCustomValidity( 
            Book.checkTitle( formEl.title.value).message);
    });
    formEl.year.addEventListener("input", function () { 
        formEl.year.setCustomValidity( 
            Book.checkYear( formEl.year.value).message);
    });
    submitButton.addEventListener("click", this.handleSubmitButtonClickEvent);
    // neutralize the submit event
    formEl.addEventListener( 'submit', function (e) { 
        e.preventDefault();;
        formEl.reset();
    });
    window.addEventListener("beforeunload", function () {
        Book.saveAllInstances(); 
    });
  },

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():

  handleSubmitButtonClickEvent: function () {
    var formEl = document.forms['Book'];
    var slots = { isbn: formEl.isbn.value, 
        title: formEl.title.value, 
        year: formEl.year.value};
    formEl.title.setCustomValidity( Book.checkTitle( slots.title).message);
    formEl.year.setCustomValidity( Book.checkYear( slots.year).message);
    if (formEl.checkValidity()) {
      Book.updateRow( slots);
    }
  }

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

8. 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.

9. Possible Variations and Extensions

9.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.

10. 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.

Part III. Associations

Chapter 5. Reference Properties and Unidirectional Associations

A property defined for an object type, or class, is called a reference property if its values are references that reference an object from some class. For instance, the class Committee shown in Figure 5.1 below has a reference property chair, the values of which are references to objects of type ClubMember.

An association between object types classifies relationships between objects of those types. For instance, the association Committee-has-ClubMember-as-chair, which is visualized as a connection line in the class diagram shown in Figure 5.2 below, classifies the relationships FinanceCommittee-has-PeterMiller-as-chair, RecruitmentCommittee-has-SusanSmith-as-chair and AdvisoryCommittee-has-SarahAnderson-as-chair, where the objects PeterMiller, SusanSmith and SarahAnderson are of type ClubMember, and the objects FinanceCommittee, RecruitmentCommittee and AdvisoryCommittee are of type Committee.

Reference properties correspond to a special form of associations, namely to unidirectional binary associations. While a binary association does, in general, not need to be directional, a reference property represents a binary association that is directed from the property's domain class (where it is defined) to its range class.

In general, associations are relationship types with two or more object types participating in them. An association between two object types is called binary. In this tutorial we only discuss binary associations. For simplicity, we just say 'association' when we actually mean 'binary association'.

Table 5.1. An example of an association table

Committee-has-ClubMember-as-chair
Finance Committee Peter Miller
Recruitment Committee Susan Smith
Advisory Committee Sarah Anderson

While individual relationships (such as FinanceCommittee-has-PeterMiller-as-chair) are important information items in business communication and in information systems, associations (such as Committee-has-ClubMember-as-chair) are important elements of information models. Consequently, software applications have to implement them in a proper way, typically as part of their model layer within a model-view-controller (MVC) architecure. Unfortunately, many application development frameworks lack the required support for dealing with associations.

In mathematics, associations have been formalized in an abstract way as sets of uniform tuples, called relations. In Entity-Relationship (ER) modeling, which is the classical information modeling approach in information systems and software engineering, objects are called entities, and associations are called relationship types. The Unified Modeling Language (UML) includes the UML Class Diagram language for information modeling. In UML, object types are called classes, relationship types are called associations, and individual relationships are called "links". These three terminologies are summarized in the following table:

Table 5.2. Different terminologies

Our preferred term(s) UML ER Diagrams Mathematics
object object entity individual
object type (class) class entity type unary relation
relationship link relationship tuple
association (relationship type) association relationship type relation
functional association one-to-one, many-to-one or one-to-many relationship type function


We first discuss reference properties, which represent unidirectional binary associations in a model without any explicit graphical rendering of the association in the model diagram.

1. References and Reference Properties

A reference can be either human-readable or an internal object reference. Human-readable references refer to identifiers that are used in human communication, such as the unique names of astronomical bodies, the ISBN of books and the employee numbers of the employees of a company. Internal object references refer to the memory addresses of objects, thus providing an efficient mechanism for accessing objects in the main memory of a computer.

Some languages, like SQL and XML, support only human-readable, but not internal references. Human-readable references are called foreign keys, and the identifiers they refer to are called primary keys, in SQL. In XML, human-readable references are called ID references and the corresponding attribute type is IDREF.

Objects can be referenced either with the help of human-readable references (such as integer codes) or with internal object references, which are preferable for accessing objects efficiently in main memory. Following the XML terminology, we also call human-readable references ID references and use the suffix IdRef for the names of human-readable reference properties. When we store persistent objects in the form of records or table rows, we need to convert internal object references, stored in properties like publisher, to ID references, stored in properties like publisherIdRef. This conversion is performed as part of the serialization of the object by assigning the standard identifier value of the referenced object to the ID reference property of the referencing object.

In object-oriented languages, a property is defined for an object type, or class, which is its domain. The values of a property are either data values from some datatype, in which case the property is called an attribute, or they are object references referencing an object from some class, in which case the property is called a reference property. For instance, the class Committee shown in Figure 5.1 below has an attribute name with range string, and a reference property chair with range ClubMember.

Figure 5.1. A committee has a club member as chair expressed by the reference property chair

A committee has a club member as chair expressed by the reference property chair

Object-oriented programming languages, such as JavaScript, PHP, Java and C#, directly support the concept of reference properties, which are properties whose range is not a datatype but a reference type, or class, and whose values are object references to instances of that class.

By default, the multiplicity of a property is 1, which means that the property is mandatory and functional (or, in other words, single-valued), having exactly one value, like the property chair in class Committee shown in Figure 5.1. When a functional property is optional (not mandatory), it has the multiplicity 0..1, which means that the property's minimum cardinality is 0 and its maximum cardinality is 1.

A reference property can be either single-valued (functional) or multi-valued (non-functional). For instance, the reference property Committee::chair shown in Figure 5.1 is single-valued, since it assigns a unique club member as chair to a club. An example of a multi-valued reference property is provided by the property Book::authors shown in Figure Figure 5.10, “The association-free Publisher-Book-Author design model” below.

Normally, a multi-valued reference property is set-valued, implying that the order of the references does not matter. In certain cases, however, it may be list-valued, such that the references are ordered.

2. Referential Integrity

References are important information items in our application's database. However, they are only meaningful, when their referential integrity is maintained by the app. This requires that for any reference, there is a referenced object in the database. Consequently, any reference property p with domain class C and range class D comes with a referential integrity constraint that has to be checked whenever

  1. a new object of type C is created,

  2. the value of p is changed for some object of type C,

  3. an object of type D is destroyed.

A referential integrity constraint also implies two change dependencies:

  1. An object creation dependency: an object with a reference to another object can only be created after the referenced object has been created.

  2. An object destruction dependency: an object that is referenced by another object can only be destroyed after

    1. the referencing object is destroyed first, or

    2. the reference in the referencing object is either deleted or replaced by a another reference.

    For every reference property in our app's model we have to choose, which of these two possible deletion policies applies.

In certain cases, we may want to relax this strict regime and allow creating objects that have non-referencing values for an ID reference property, but we do not consider such cases.

Typically, object creation dependencies are managed in the user interface by not allowing the user to enter a value of an ID reference property, but only to select one from a list of all existing target objects.

3. Modeling Reference Properties as Unidirectional Associations

A reference property (such as chair in the example shown in Figure 5.1 above) can be visualized in a UML class diagram in the form of an association end owned by its domain class. This requires to connect the domain class and the range class of the reference property with an association line and annotate the end of this line at the range class side with a "dot", with the property name and with a multiplicity symbol, as shown in Figure 5.2 below for the case of our example. In this way we get a unidirectional association, the source class of which is the property's domain and the target class of which is the property's range.

The fact that an association end is owned by the class at the other end is visually expressed with the help of a small filled circle (also called a "dot") at the end of the association line. This is illustrated in Figure 5.2 below, where the "dot" at the association end chair indicates that the association end represents a reference property chair in the class Committee having ClubMember as range.

Figure 5.2. A committee has a club member as chair expressed by an association end with a "dot"

A committee has a club member as chair expressed by an association end with a "dot"

Thus, the two diagrams shown in Figure 5.1 and Figure 5.2 express essentially equivalent models. When a reference property is modeled by an association end with a "dot", like chair in Figure 5.1, then the property's multiplicity is attached to the association end. Since in a design model, all association ends need to have a multiplicity, we also have to define a multiplicity for the other end at the side of the Committee class, which represents the inverse of the property. This multiplicity (of the inverse property) is not available in the original property description in the model shown in Figure 5.1, so it has to be added according to the intended semantics of the association. It can be obtained by answering the question "is it mandatory that any ClubMember is the chair of a Committee?" for finding the minimum cardinality and the question "can a ClubMember be the chair of more than one Committee?" for finding the maximum cardinality.

When the value of a property is a set of values from its range, the property is non-functional and its multiplicity is either 0..* or n..* where n > 0. Instead of 0..*, which means "neither mandatory nor functional", we can simply write the asterisk symbol *. The association shown in Figure 5.2 assigns at most one object of type ClubMember as chair to an object of type Committee. Consequently, it's an example of a functional association.

The following table provides an overview about the different cases of functionality of an association:

Table 5.3. Functionality types

Functionality type Meaning
one-to-one both functional and inverse functional
many-to-one functional
one-to-many inverse functional
many-to-many neither functional nor inverse functional


Notice that the directionality and the functionality type of an association are independent of each other. So, a unidirectional association can be either functional (one-to-one or many-to-one), or non-functional (one-to-many or many-to-many).

4. Representing Unidirectional Associations as Reference Properties

A unidirectional association between a source and a target class can be be represented as a reference property of the source class. For the case of a unidirectional one-to-one association, this is illustrated in Figure 5.3 below.

Figure 5.3. Representing the unidirectional association ClubMember has Committee as chairedCommittee as a reference property

Representing the unidirectional association ClubMember has Committee as chairedCommittee as a reference property
Representing the unidirectional association ClubMember has Committee as chairedCommittee as a reference property

5. Adding Directionality to a Non-Directed Association

When we make an information model in the form of a UML class diagram, we typically end up with a model containing one or more associations that do not have any ownership defined for their ends, as, for instance, in Figure 5.4 below. When there is no ownership dot at either end of an association, such as in this example, this means that the model does not specify how the association is to be represented (or realized) with the help of reference properties. Such an association does not have any direction. According to the UML 2.5 specification, the ends of such an association are "owned" by itself, and not by any of the classes participating in it.

Figure 5.4. A model of the Committee-has-ClubMember-as-chair association without ownership dots

A model of the Committee-has-ClubMember-as-chair association without ownership dots

A model without association end ownership dots is acceptable as a relational database design model, but it is incomplete as an information design model for classical object-oriented (OO) programming languages. For instance, the model of Figure 5.4 provides a relational database design with two entity tables, committees and clubmembers, and a separate one-to-one relationship table committee_has_clubmember_as_chair. But it does not provide a design for Java classes, since it does not specify how the association is to be implemented with the help of reference properties.

There are three options how to turn a model without association end ownership dots into a complete OO design model where all associations are either uni-directional or bi-directional: we can place an ownership dot at either end or at both ends of the association. Each of these three options defines a different way how to represent, or implement, the association with the help of reference properties. So, for the association shown in Figure 5.4 above, we have the following options:

  1. Place an ownership dot at the chair association end, leading to the model shown in Figure 5.2 above, which can be turned into the association-free model shown in Figure 5.1 above.

  2. Place an ownership dot at the chairedCommittee association end, leadig to the completed models shown in Figure 5.3 above.

  3. Make the association bi-directional by placing ownership dots at both association ends with the meaning that the association is implemented in a redundant manner by a pair of mutually inverse reference properties Committee::chair and ClubMember::chairedCommittee, as discussed in the next part of our 5-part tutorial.

    Figure 5.5. Modeling a bidirectional association between Committee and ClubMember

    Modeling a bidirectional association between Committee and ClubMember

So, whenever we have modeled an association, we have to make a choice, which of its ends represents a reference property and will therefore be marked with an ownership dot. It can be either one, or both. This decision also implies a decision about the navigability of the association. When an association end represents a reference property, this implies that it is navigable (via this property).

In the case of a functional association that is not one-to-one, the simplest design is obtained by defining the direction of the association according to its functionality, placing the association end ownership dot at the association end with the multiplicity 0..1 or 1 . For a non-directed one-to-one or many-to-many association, we can choose the direction, that is, at which association end to place the ownership dot.

6. Our Running Example

The model shown in Figure 5.6 below (about publishers and books) serves as our running example for a unidirectional functional association in this tutorial. Notice that it contains the unidirectional many-to-one association Book-has-Publisher.

Figure 5.6. The Publisher-Book information design model with a unidirectional association

The Publisher-Book information design model with a unidirectional association

We may also have to deal with a non-functional (multi-valued) reference property representing a unidirectional non-functional association. For instance, the unidirectional many-to-many association between Book and Author shown in Figure 5.7 below, models a multi-valued (non-functional) reference property authors.

Figure 5.7. The Publisher-Book-Author information design model with two unidirectional associations

The Publisher-Book-Author information design model with two unidirectional associations

7. Eliminating Unidirectional Associations

How to eliminate explicit unidirectional associations from an information design model by replacing them with reference properties

Since classical OO programming languages do not support assocations as first class citizens, but only classes and reference properties, which represent unidirectional associations (but without any explicit visual rendering), we have to eliminate all explicit associations for obtaining an OO design model.

7.1. The basic elimination procedure restricted to unidirectional associations

The starting point of our restricted association elimination procedure is an information design model with various kinds of uni-directional associations, such as the model shown in Figure 5.6 above. If the model still contains any non-directional associations, we first have to turn them into directional ones by making a decision on the ownership of their ends, as discussed in Section 5.

A uni-directional association connecting a source with a target class is replaced with a corresponding reference property in its source class having

  1. the same name as the association end, if there is any, otherwise it is set to the name of the target class (possibly pluralized, if the reference property is multi-valued);

  2. the target class as its range;

  3. the same multiplicity as the target association end,

  4. a uniqueness constraint if the uni-directional association is inverse functional.

This replacement procedure is illustrated for the case of a uni-directional one-to-one association in Figure 5.3 above.

For the case of a uni-directional one-to-many association, Figure 5.8 below provides an illustration of the association elimination procedure. Here, the non-functional association end at the target class Point is turned into a corresponding reference property with name points obtained as the pluralized form of the target class name

Figure 5.8. Turn a non-functional target association end into a corresponding reference property


7.2. Eliminating associations from the Publisher-Book-Author design model

In the case of our running example, the Publisher-Book-Author information design model, we have to replace both uni-directional associations with suitable reference properties. In the first step, we replace the many-to-one association Book-has-Publisher in the model of Figure 5.6 with a functional reference property publisher in the class Book, resulting in the following association-free model:

Figure 5.9. The association-free Publisher-Book design model

The association-free Publisher-Book design model

Notice that since the target association end of the Book-has-Publisher association has the multiplicity 0..1, we have to declare the new property publisher as optional by appending the multiplicity 0..1 to its name.

In the second step, we replace the many-to-many association Book-has-Author in the model of Figure 5.7 with a multi-valued reference property authors in the class Book, resulting in the following association-free model:

Figure 5.10. The association-free Publisher-Book-Author design model

The association-free Publisher-Book-Author design model

After the platform-independent association-free information design model has been completed, one or more platform-specific data models, for a choice of specific implementation platforms, can be derived from it. Such a platform-specific data model can still be expressed in the form of a UML class diagram, but it contains only modeling elements that can be directly encoded in the chosen platform. Thus, for any platform considered, the tutorial contains two sections: 1) how to make the platform-specific data model, and 2) how to encode this model.

8. Rendering Reference Properties in the User Interface

In an HTML-form-based user interface (UI), we have a correspondence between the different kinds of properties defined in the model classes of an app and the form controls used for the input and output of their values. We have to distinguish between various kinds of model class attributes, which are typically mapped to various types of HTML input fields, and reference properties, which are typically mapped to HTML select controls (selection lists). Representing reference properties in the UI with select controls, instead of input fields, prevents the user from entering invalid ID references, so it takes care of referential integrity.

In general, a single-valued reference property can always be represented by a single-select control in the UI, no matter how many objects populate the reference property's range, from which one specific choice is to be made. If the cardinality of the reference property's range is sufficiently small (say, not greater than 7), then we can also use a radio button group instead of a selection list.

A multi-valued reference property can be represented by a multiple-select control in the UI. However, this control is not really usable as soon as there are many (say, more than 20) different options to choose from because the way it renders the choice is visually too scattered. In the special case of having only a few (say, no more than 7) options, we can also use a checkbox group instead of a multiple-selection list. But for the general case of having in the UI an association list containing all associated objects choosen from the reference property's range class, we need to develop a special UI widget that allows to add (and remove) objects to (and from) a list of associated objects.

Such a selection list widget consists of

  1. a HTML list element containing the associated objects, where each list item contains a push button for removing the object from the association list;

  2. a single-select control that, in combination with a push button, allows to add a new associated object from the range of the multi-valued reference property.

Chapter 6. Bidirectional Associations

A bidirectional association is an association that is represented as a pair of mutually inverse reference properties.

The model shown in Figure 6.1 below (about publishers, books and their authors) serves as our running example in all other parts of the tutorial. Notice that it contains two bidirectional associations, as indicated by the ownership dots at both association ends.

Figure 6.1. The Publisher-Book-Author information design model with two bidirectional associations

The Publisher-Book-Author information design model with two bidirectional associations

1. Inverse Reference Properties

For being able to easily retrieve the committees that are chaired or co-chaired by a club member, we add two reference properties to our Committee-ClubMember example model: the property of a club member to chair a committee (ClubMember::chairedCommittee) and the property of a club member to co-chair a committee (ClubMember::coChairedCommittee). We assume that any club member may chair or co-chair at most one committee (where the disjunction is non-exclusive). So, we get the following model:

Notice that there is a close correspondence between the two reference properties Committee::chair and ClubMember::chairedCommittee. They are the inverse of each other: when the club member Tom is the chair of the budget committee, expressed by the tuple ("budget committee", "Tom"), then the budget committee is the committee chaired by the club member Tom, expressed by the inverse tuple ("Tom", "budget committee"). For expressing this inverse correspondence in the diagram, we append an inverse property constraint, inverse of chair, in curly braces to the declaration of the property ClubMember::chairedCommittee, and a similar one to the property Committee::chair, as shown in the following diagram:

We also call Committee::chair and ClubMember::chairedCommittee a pair of mutually inverse reference properties. Having such a pair in a model means having redundancy because each of the two involved reference properties can be derived from the other by inversion. This type of redundancy implies data storage overhead and update overhead, which is the price to pay for the bidirectional navigabilty that supports efficient object access in both directions.

For maintaining the duplicate information of a mutually inverse reference property pair, it is good practice to treat one of the two involved properties as the master, and the other one as the slave, and take this distinction into consideration in the code of the change methods (such as the property setters) of the affected model classes. We indicate the slave of an inverse reference property pair in a model diagram by declaring the slave property to be a derived property using the UML notation of a slash as a prefix of the property name as shown in the following diagram:

The property chairedCommittee in ClubMember now has a slash-prefix (/) indicating that it is derived.

In a UML class diagram, the derivation of a property can be specified, for instance, by an Object Constraint Language (OCL) expression that evaluates to the value of the derived property for the given object. In the case of a property being the inverse of another property, specified by the constraint expression {inverse of anotherProperty} appended to the property declaration, the derivation expression is implied. In our example, it evaluates to the committee object reference c such that c.chair = this.

There are two ways how to realize the derivation of a property: it may be derived on read via a read-time computation of its value, or it may be derived on update via an update-time computation performed whenever one of the variables in the derivation expression (typically, another property) changes its value. The latter case corresponds to a materialzed view in an SQL database. While a reference property that is derived on read may not guarantee efficient navigation, because the on-read computation may create unacceptable latencies, a reference property that is derived on update does provide effificient navigation.

In the case of a derived reference property, the derivation expresses life cycle dependencies. These dependencies require special consideration in the code of the affected model classes by providing a number of change management mechanisms based on the functionality type of the represented association (either one-to-one, many-to-one or many-to-many).

In our example of the derived inverse reference property ClubMember::chairedCommittee, which is single-valued and optional, this means that

  1. whenever a new committee object is created (with a mandatory chair assignment), the corresponding ClubMember::chairedCommittee property has to be assigned accordingly;

  2. whenever the chair property is updated (that is, a new chair is assigned to a committee), the corresponding ClubMember::chairedCommittee property has to be updated as well;

  3. whenever a committee object is destroyed, the corresponding ClubMember::chairedCommittee property has to be unassigned.

In the case of a derived inverse reference property that is multi-valued while its inverse base property is single-valued (like Publisher::publishedBooks in Figure 6.4 below being derived from Book::publisher), the life cycle dependencies imply that

  1. whenever a new 'base object' (such as a book) is created, the corresponding inverse property has to be updated by adding a reference to the new base object to its value set (like adding a reference to the new book object to Publisher::publishedBooks );

  2. whenever the base property is updated (e.g., a new publisher is assigned to a book), the corresponding inverse property (in our example, Publisher::publishedBooks) has to be updated as well by removing the old object reference from its value set and adding the new one;

  3. whenever a base object (such as a book) is destroyed, the corresponding inverse property has to be updated by removing the reference to the base object from its value set (like removing a reference to the book object to be destroyed from Publisher::publishedBooks ).

We use the slash-prefix (/) notation in the example above for indicating that the property ClubMember::chairedCommittee is derived on update from the corresponding committee object.

2. Making an Association-Free Information Design Model

How to eliminate explicit associations from a design model by replacing them with reference properties

Since classical OO programming languages do not support assocations as first class citizens, but only classes and reference properties representing implicit associations, we have to eliminate all explicit associations for obtaining an OO design model.

2.1. The basic procedure

The starting point of our association elimination procedure is an information design model with various kinds of uni-directional and bi-directional associations, such as the model shown in Figure 6.1 above. If the model still contains any non-directional associations, we first have to turn them into directional ones by making a decision on the ownership of their ends, which is typically based on navigability requirements.

Notice that both associations in the Publisher-Book-Author information design model, publisher-publishedBooks and authoredBooks-authors (or Authorship), are bi-directional as indicated by the ownership dots at both association ends. For eliminating all explicit associations from an information design model, we have to perform the following steps:

  1. Eliminate uni-directional associations, connecting a source with a target class, by replacing them with a reference property in the source class such that the target class is its range.

  2. Eliminate bi-directional associations by replacing them with a pair of mutually inverse reference properties.

2.2. How to eliminate uni-directional associations

A uni-directional association connecting a source with a target class is replaced with a corresponding reference property in its source class having the target class as its range. Its multiplicity is the same as the multiplicity of the target association end. Its name is the name of the association end, if there is any, otherwise it is set to the name of the target class (possibly pluralized, if the reference property is multi-valued).

2.3. How to eliminate bi-directional associations

A bi-directional association, such as Authorship in the model shown in Figure 6.1 above, is replaced with a pair of mutually inverse reference properties, such as Book::authors and Author::authoredBooks. Since both reference properties represent the same information (the same set of binary relationships), it's an option to conisder one of them being the master and the other one the slave, which is derived from the master. We discuss the two cases of a one-to-one and a many-to-many association

  1. In the case of a bi-directional one-to-one association, this leads to a pair of mutually inverse single-valued reference properties, one in each of the two associated classes. Since both of them represent essentially the same information (one of them is the inverse of the other), one has to choose which of them is considered the "master", and which of them is the "slave", where the "slave" property is considered to represent the inverse of the "master". In the slave class, the reference property representing the inverse association is designated as a derived property that is automatically updated whenever 1) a new master object is created, 2) the master reference property is updated, or 3) a master object is destroyed.

    Figure 6.2. Turn a bi-directional one-to-one association into a pair of mutually inverse single-valued reference properties


  2. A bi-directional many-to-many association is mapped to a pair of mutually inverse multi-valued reference properties, one in each of the two classes participating in the association. Again, in one of the two classes, the multi-valued reference property representing the (inverse) association is designated as a derived property that is automatically updated whenever the corresponding property in the other class (where the association is maintained) is updated.

    Figure 6.3. Turn a bi-directional many-to-many association into a pair of mutually inverse multi-valued reference properties


2.4. The resulting association-free design model

After replacing both bidirectional associations with reference properties, we obtain the following association-free design model:

Figure 6.4. The association-free design model

The association-free design model

Part IV. Inheritance in Class Hierarchies

Subtypes and inheritance are important elements of information models. Software applications have to implement them in a proper way, typically as part of their model layer within a model-view-controller (MVC) architecure. Unfortunately, application development frameworks do often not provide much support for dealing with subtypes and inheritance.

Chapter 7. Subtyping and Inheritance

The concept of a category, or subclass, is a fundamental concept in natural language, mathematics, and informatics. For instance, in English, we say that a bird is an animal, or the class of all birds is a subclass of the class of all animals. In linguistics, the noun bird is a hyponym of the noun animal.

An object type may be specialized by subtypes (for instance, Bird is specialized by Parrot) or generalized by supertypes (for instance, Bird and Mammal are generalized by Animal). Specialization and generalization are two sides of the same coin.

A category inherits all features from its supertypes. When a category inherits attributes, associations and constraints from a supertype, this means that these features need not be explicitly rendered for the category in the class diagram, but the reader of the diagram has to know that all features of a supertype also apply to its subtypes.

When an object type has more than one supertype, we have a case of multiple inheritance, which is common in conceptual modeling, but prohibited in many object-oriented programming languages, such as Java and C#, where subtyping leads to class hierarchies with a unique direct supertype for each object type.

1. Introducing Subtypes by Specialization

A new category may be introduced by specialization whenever new features of more specific types of objects have to be captured. We illustrate this for our example model where we want to capture text books and biographies as special cases of books. This means that text books and biographies also have an ISBN, a title and a publishing year, but in addition they have further features such as the attributes subjectArea of text books and about of biographies. Consequerntly, we introduce the object types TextBook and Biography by specializing the object type Book, that is, as subtypes of Book.

Figure 7.1. The object type Book is specialized by two subtypes: TextBook and Biography

The object type Book is specialized by two subtypes: TextBook and Biography

When specializing an object type, we define additional features for the newly added category. In many cases, these additional features are more specific properties. For instance, in the case of TextBook specializing Book, we define the additional attribute subjectArea. In some programming languages, such as in Java, it is therefore said that the category extends the supertype.

However, we can also specialize an object type without defining additional properties (or operations/methods), but by defining aditional constraints.

2. Introducing Supertypes by Generalization

We illustrate generalization with the following example, which extends the information model of Part 4 by adding the object type Employee and associating employees with publishers.

Figure 7.2. The object types Employee and Author share several attributes

The object types Employee and Author share several attributes

After adding the object type Employee we notice that Employee and Author share a number of attributes due to the fact that both employees and authors are people, and being an employee as well as being an author are roles played by people. So, we may generalize these two object types by adding a joint supertype Person, as shown in the following diagram.

Figure 7.3. The object types Employee and Author have been generalized by adding the common supertype Person

The object types Employee and Author have been generalized by adding the common supertype Person

When generalizing two or more object types, we move (and centralize) a set of features shared by them in the newly added supertype. In the case of Employee and Author, this set of shared features consists of the attributes name, dateOfBirth and dateOfDeath. In general, shared features may include attributes, associations and constraints.

Notice that since in an information design model, each top-level class needs to have a standard identifier, in the new class Person we have declared the standard identifier attribute personId, which is inherited by all subclasses. Therefore, we have to reconsider the attributes that had been declared to be standard identifiers in the subclasses before the generalization. In the case of Employee, we had declared the attribute employeeNo as a standard identifier. Since the employee number is an important business information item, we have to keep this attribute, even if it is no longer the standard idenifier. Because it is still an alternative idenifier (a "key"), we declare it to be unique. In the case of Author, we had declared the attribute authorId as a standard identifier. Assuming that this attribute represents a purely technical, rather than business, information item, we dropped it, since it's no longer needed as an identifier for authors. Consequently, we end up with a model which allows to identify employees either by their employee number or by their personId value, and to identify authors by their personId value.

We consider the following extension of our original example model, shown in Figure 7.4, where we have added two class hierarchies:

  1. the disjoint (but incomplete) segmentation of Book into TextBook and Biography,

  2. the overlapping and incomplete segmentation of Person into Author and Employee, which is further specialized by Manager.

Figure 7.4. The complete class model containing two inheritance hierarchies

The complete class model containing two inheritance hierarchies

3. Intension versus Extension

The intension of an object type is given by the set of its features, including attributes, associations and operations.

The extension of an object type is the set of all objects instantiating the object type. The extension of an object type is also called its population.

We have the following duality: while all features of a supertype are included in the features of its subtypes (intensional inclusion), all instances of a category are included in the instances of its supertypes (extensional inclusion). This formal structure has been investigated in Figure formal concept analysis.

Due to the intension/extension duality we can specialize a given type in two different ways:

  1. By extending the type's intension through adding features in the new category (such as adding the attribute subjectArea in the category TextBook).

  2. By restricting the type's extension through adding a constraint (such as defining a category MathTextBook as a TextBook where the attribute subjectArea has the specific value "Mathematics").

Typical OO programming languages, such as Java and C#, only support the first possibility (specializing a given type by extending its intension), while XML Schema and SQL99 also support the second possibility (specializing a given type by restricting its extension).

4. Type Hierarchies

A type hierarchy (or class hierarchy) consists of two or more types, one of them being the root (or top-level) type, and all others having at least one direct supertype. When all non-root types have a unique direct supertype, the type hierarchy is a single-inheritance hierarchy, otherwise it's a multiple-inheritance hierarchy. For instance, in 7.5 below, the class Vehicle is the root of a single-inheritance hierarchy, while Figure 7.6 shows an example of a multiple-inheritance hierarchy, due to the fact that AmphibianVehicle has two direct superclasses: LandVehicle and WaterVehicle.

Figure 7.5. A class hierarchy having the root class Vehicle

A class hierarchy having the root class Vehicle

The simplest case of a class hierarchy, which has only one level of subtyping, is called a generalization set in UML, but may be more naturally called segmentation. A segmentation is complete, if the union of all subclass extensions is equal to the extension of the superclass (or, in other words, if all instances of the superclass instantiate some subclass). A segmentation is disjoint, if all subclasses are pairwise disjoint (or, in other words, if no instance of the superclass instantiates more than one subclass). Otherwise, it is called overlapping. A complete and disjoint segmentation is a partition.

Figure 7.6. A multiple inheritance hierarchy

A multiple inheritance hierarchy

In a class diagram, we can express these constraints by annotating the shared generalization arrow with the keywords complete and disjoint enclosed in braces. For instance, the annotation of a segmentation with {complete, disjoint} indicates that it is a partition. By default, whenever a segmentation does not have any annotation, like the segmentation of Vehicle into LandVehicle and WaterVehicle in Figure 7.6 above, it is {incomplete, overlapping}.

An information model may contain any number of class hierarchies.

5. The Class Hierarchy Merge Design Pattern

Consider the simple class hierarchy of the design model in Figure 7.1 above, showing a disjoint segmentation of the class Book. In such a case, whenever there is only one level (or there are only a few levels) of subtyping and each category has only one (or a few) additional properties, it's an option to refactor the class model by merging all the additional properties of all subclasses into an expanded version of the root class such that these subclasses can be dropped from the model, leading to a simplified model.

This Class Hierarchy Merge design pattern comes in two forms. In its simplest form, the segmentations of the original class hierarchy are disjoint, which allows to use a single-valued category attribute for representing the specific category of each instance of the root class corresponding to the unique subclass instantiated by it. When the segmentations of the original class hierarchy are not disjoint, that is, when at least one of them is overlapping, we need to use a multi-valued category attribute for representing the set of types instantiated by an object. In this tutorial, we only discuss the simpler case of Class Hierarchy Merge refactoring for disjoint segmentations, where we take the following refactoring steps:

  1. Add an enumeration type that contains a corresponding enumeration literal for each segment subclass. In our example, we add the enumeration type BookCategoryEL.

  2. Add a category attribute to the root class with this enumeration as its range. The category attribute is mandatory [1], if the segmentation is complete, and optional [0..1], otherwise. In our example, we add a category attribute with range BookCategoryEL to the class Book. The category attribute is optional because the segmentation of Book into TextBook and Biography is incomplete.

  3. Whenever the segmentation is rigid (does not allow dynamic classification), we designate the category attribute as frozen, which means that it can only be assigned once by setting its value when creating a new object, but it cannot be changed later.

  4. Move the properties of the segment subclasses to the root class, and make them optional. We call these properties, which are typically listed below the category attribute, segment properties. In our example, we move the attribute subjectArea from TextBook and about from Biography to Book, making them optional, that is [0..1].

  5. Add a constraint (in an invariant box attached to the expanded root class rectangle) enforcing that the optional subclass properties have a value if and only if the instance of the root class instantiates the corresponding category. In our example, this means that an instance of Book is of category "TextBook" if and only if its attribute subjectArea has a value, and it is of category "Biography" if and only if its attribute about has a value.

  6. Drop the segment subclasses from the model.

In the case of our example, the result of this design refactoring is shown in Figure 7.7 below. Notice that the constraint (or "invariant") represents a logical sentence where the logical operator keyword "IFF" stands for the logical equivalence operator "if and only if" and the property condition prop=undefined tests if the property prop does not have a value.

Figure 7.7. The design model resulting from applying the Class Hierarchy Merge design pattern


6. Subtyping and Inheritance in Computational Languages

Subtyping and inheritance have been supported in Object-Oriented Programming (OOP), in database languages (such as SQL99), in the XML schema definition language XML Schema, and in other computational languages, in various ways and to different degrees. At its core, subtyping in computational languages is about defning type hierarchies and the inheritance of features: mainly properties and methods in OOP; table columns and constraints in SQL99; elements, attributes and constraints in XML Schema.

In general, it is desirable to have support for multiple classification and multiple inheritance in type hierarchies. Both language features are closely related and are considered to be advanced features, which may not be needed in many applications or can be dealt with by using workarounds.

Multiple classification means that an object has more than one direct type. This is mainly the case when an object plays multiple roles at the same time, and therefore directly instantiates multiple classes defining these roles. Multiple inheritance is typically also related to role classes. For instance, a student assistant is a person playing both the role of a student and the role of an academic staff member, so a corresponding OOP class StudentAssistant inherits from both role classes Student and AcademicStaffMember. In a similar way, in our example model above, an AmphibianVehicle inherits from both role classes LandVehicle and WaterVehicle.

6.1. Subtyping and Inheritance with OOP Classes

The minimum level of support for subtyping in OOP, as provided, for instance, by Java and C#, allows defining inheritance of propertes and methods in single-inheritance hierarchies, which can be inspected with the help of an is-instance-of predicate that allows testing if a class is the direct or an indirect type of an object. In addition, it is desirable to be able to inspect inheritance hierarchies with the help of

  1. a pre-defined instance-level property for retrieving the direct type of an object (or its direct types, if multiple classification is allowed);

  2. a pre-defined type-level property for retrieving the direct supertype of a type (or its direct supertypes, if multiple inheritance is allowed).

A special case of an OOP language is JavaScript, which does not (yet) have an explicit language element for classes, but only for constructors. Due to its dynamic programming features, JavaScript allows using various code patterns for implementing classes, subtyping and inheritance (as we discuss in the next section on JavaScript).

6.2. Subtyping and Inheritance with Database Tables

A standard DBMS stores information (objects) in the rows of tables, which have been conceived as set-theoretic relations in classical relational database systems. The relational database language SQL is used for defining, populating, updating and querying such databases. But there are also simpler data storage techniques that allow to store data in the form of table rows, but do not support SQL. In particular, key-value storage systems, such as JavaScript's Local Storage API, allow storing a serialization of a JSON table (a map of records) as the string value associated with the table name as a key.

While in the classical, and still dominating, version of SQL (SQL92) there is no support for subtyping and inheritance, this has been changed in SQL99. However, the subtyping-related language elements of SQL99 have only been implemented in some DBMS, for instance in the open source DBMS PostgreSQL. As a consequence, for making a design model that can be implemented with various frameworks using various SQL DBMSs (including weaker technologies such as MySQL and SQLite), we cannot use the SQL99 features for subtyping, but have to model inheritance hierarchies in database design models by means of plain tables and foreign key dependencies. This mapping from class hierarchies to relational tables (and back) is the business of Object-Relational-Mapping frameworks such as Hibernate (or any other JPA Provider) or the Active Record approach of the Rails framework.

There are essentially two alternative approaches how to represent a class hierarchy with relational tables:

  1. Single Table Inheritance is the simplest approach, where the entire class hierarchy is represented with a single table, containing columns for all attributes of the root class and of all its subclasses, and named after the name of the root class.

  2. Joined Tables Inheritance is a more logical approach, where each subclass is represented by a corresponding subtable connected to the supertable via its primary key referencing the primary key of the supertable.

Notice that the Single Table Inheritance approach is closely related to the Class Hierarchy Merge design pattern discussed in 5 above. Whenever this design pattern has already been applied in the design model, or the design model has already been refactored according to this design pattern, the class hierarchies concerned (their subclasses) have been eliminated in the design, and consequently also in the data model to be encoded in the form of class definitions in the app's model layer, so there is no need anymore to map class hierarchies to database tables. Otherwise, when the Class Hierarchy Merge design pattern does not get applied, we would get a corresponding class hierarchy in the app's model layer, and we would have to map it to database tables with the help of the Single Table Inheritance approach.

We illustrate both the Single Table Inheritance approach and the Joined Tables Inheritance with the help of two simple examples. The first example is the Book class hierarchy, which is shown in Figure 7.1 above. The second example is the class hierarchy of the Person roles Employee, Manager and Author, shown in the class diagram in Figure 7.8 below.

Figure 7.8. A class model with a Person roles hierarchy

A class model with a Person roles hierarchy

6.2.1. Single Table Inheritance

Consider the single-level class hierarchy shown in Figure 7.1 above, which is an incomplete disjoint segmentation of the class Book, as the design for the model classes of an MVC app. In such a case, whenever we have a model class hierarchy with only one level (or only a few levels) of subtyping and each category has only one (or a few) additional properties, it's preferable to use Single Table Inheritance, so we model a single table containing columns for all attributes such that the columns representing additional attributes of subclasses are optional, as shown in the SQL table model in Figure Figure 7.9, “An SQL table model with a single table representing the Book class hierarchy” below.

Figure 7.9. An SQL table model with a single table representing the Book class hierarchy

An SQL table model with a single table representing the Book class hierarchy

Notice that it is good practice to add a special column, which is called discriminator column in JPA and often has the name category, for representing the category of each row corresponding to the subclass instantiated by the represented object. Such a category column would normally be string-valued, but constrained to one of the names of the subclasses. If the DBMS supports enumerations, it could also be enumeration-valued.

Based on the category of a book, we have to enforce that if and only if it is "TextBook", its attribute subjectArea has a value, and if and only if it is "Biography", its attribute about has a value. This implied constraint is expressed in the invariant box attached to the Book table class in the class diagram above, where the logical operator keyword "IFF" represents the logical equivalence operator "if and only if". It needs to be implemented in the database, e.g., with an SQL table CHECK clause or with SQL triggers.

Consider the class hierarchy shown in Figure 7.8 above. With only three additional attributes defined in the subclasses Employee, Manager and Author, this class hierarchy could again be implemented with the Single Table Inheritance approach. In the SQL table model, we can express this as shown in Figure 7.10 below.

Figure 7.10. An SQL table model with a single table representing the Person roles hierarchy

An SQL table model with a single table representing the Person roles hierarchy

In the case of a multi-level class hierarchy where the subclasses have little in common, the Single Table Inheritance approach does not lead to a good representation.

6.2.2. Joined Tables Inheritance

In a more realistic model, the subclasses of Person shown in Figure 7.8 above would have many more attributes, so the Single Table Inheritance approach would be no longer feasible, and the Joined Tables Inheritance approach would be needed. In this approach we get the SQL data model shown in Figure 7.11 below. This SQL table model connects subtables to their supertables by defining their primary key attribute(s) to be at the same time a foreign key referencing their supertable. Notice that foreign keys are visuallized in the form of UML dependency arrows stereotyped with «fkey» and annotated at their source table side with the name of the foreign key column.

Figure 7.11. An SQL table model with the table Person as the root of a table hierarchy

An SQL table model with the table Person as the root of a table hierarchy

The main disadvantage of the Joined Tables Inheritance approach is that for querying any subclass join queries are required, which may create a performance problem.

Glossary

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.

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.

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.