Complete Tutorial
Complete Tutorial
iii
Table of Contents
Foreword ..................................................................................................................... xi
I. Getting Started ........................................................................................................... 1
1. A Quick Tour of the Foundations of Web Apps ...................................................... 3
The World Wide Web (WWW) ....................................................................... 3
HTML and XML ........................................................................................... 3
XML documents .................................................................................... 3
Unicode and UTF-8 ............................................................................... 3
XML namespaces .................................................................................. 4
Correct XML documents ......................................................................... 4
The evolution of HTML ......................................................................... 5
HTML forms ........................................................................................ 5
JavaScript ..................................................................................................... 7
Types and data literals in JavaScript ......................................................... 7
Variable scope ...................................................................................... 8
Strict Mode .......................................................................................... 8
Different kinds of objects ........................................................................ 8
Array lists ........................................................................................... 10
Maps .................................................................................................. 10
JavaScript supports four types of basic data structures ................................ 11
Defining and using classes .................................................................... 11
JavaScript as an object-oriented language ................................................. 14
Further reading about JavaScript ............................................................. 14
2. Building a Minimal JavaScript Front-End App in Seven Steps .................................. 15
Step 1 - Set up the Folder Structure ................................................................ 15
Step 2 - Write the Model Code ...................................................................... 16
Representing the collection of all Book instances ....................................... 17
Loading all Book instances .................................................................... 18
Saving all Book instances ...................................................................... 18
Creating a new Book instance ................................................................ 19
Updating an existing Book instance ........................................................ 19
Deleting an existing Book instance ......................................................... 19
Creating test data ................................................................................. 20
Clearing all data .................................................................................. 20
Step 3 - Initialize the Application ................................................................... 20
Step 4 - Implement the List Objects Use Case ................................................... 20
Step 5 - Implement the Create Object Use Case ................................................ 22
Step 6 - Implement the Upate Object Use Case ................................................. 23
Step 7 - Implement the Delete Object Use Case ................................................ 24
Run the App and Get the Code ...................................................................... 26
Possible Variations and Extensions ................................................................. 26
Using IndexedDB as an Alternative to LocalStorage ................................... 26
Expressing Date/Time Information with the <time> Element ........................ 26
Points of Attention ....................................................................................... 27
II. Integrity Constraints ................................................................................................. 28
3. Integrity Constraints and Data Validation ............................................................. 30
String Length Constraints .............................................................................. 31
Mandatory Value Constraints ......................................................................... 31
Range Constraints ........................................................................................ 32
Interval Constraints ...................................................................................... 32
Pattern Constraints ....................................................................................... 33
Cardinality Constraints .................................................................................. 34
Uniqueness Constraints ................................................................................. 34
Standard Identifiers (Primary Keys) ................................................................ 35
Referential Integrity Constraints ..................................................................... 35
Frozen Value Constraints .............................................................................. 35
iv
JavaScript Front-End Web Apps
v
JavaScript Front-End Web Apps
vi
JavaScript Front-End Web Apps
vii
List of Figures
2.1. The object type Book. ............................................................................................ 15
2.2. The minimal app's start page index.html. .............................................................. 16
3.1. The object type Person with an interval constraint ..................................................... 32
3.2. The object type Book with a pattern constraint ........................................................... 33
3.3. Two object types with cardinality constraints .............................................................. 34
3.4. The object type Book with a uniqueness constraint ...................................................... 34
3.5. The object type Book with a standard identifier declaration ........................................... 35
4.1. A platform-independent design model with the object type Book and two invariants ........... 38
4.2. Deriving a JavaScript data model from an information design model ............................... 40
4.3. The validation app's start page index.html. ............................................................ 42
5.1. A committee has a club member as chair expressed by the reference property chair ......... 60
5.2. A committee has a club member as chair expressed by an association end with a "dot" ......... 62
5.3. Representing the unidirectional association ClubMember has Committee as
chairedCommittee as a reference property ........................................................................ 62
5.4. A model of the Committee-has-ClubMember-as-chair association without ownership
dots ........................................................................................................................... 63
5.5. Modeling a bidirectional association between Committee and ClubMember ...................... 63
5.6. The Publisher-Book information design model with a unidirectional association ................. 64
5.7. The Publisher-Book-Author information design model with two unidirectional
associations ................................................................................................................. 64
5.8. Turn a non-functional target association end into a corresponding reference property .......... 65
5.9. The association-free Publisher-Book design model ....................................................... 65
5.10. The association-free Publisher-Book-Author design model ........................................... 66
7.1. The complete JavaScript data model ......................................................................... 71
9.1. The Publisher-Book-Author information design model with two bidirectional
associations ................................................................................................................. 90
9.2. Turn a bi-directional one-to-one association into a pair of mutually inverse single-valued
reference properties ...................................................................................................... 93
9.3. Turn a bi-directional many-to-many association into a pair of mutually inverse multi-
valued reference properties ............................................................................................ 93
9.4. The association-free design model ............................................................................ 94
10.1. The JavaScript data model without Book ................................................................. 96
11.1. The object type Book is specialized by two subtypes: TextBook and Biography ....... 103
11.2. The object types Employee and Author share several attributes .............................. 104
11.3. The object types Employee and Author have been generalized by adding the
common supertype Person ........................................................................................ 104
11.4. The complete class model containing two inheritance hierarchies ................................. 105
11.5. A class hierarchy having the root class Vehicle .................................................... 106
11.6. A multiple inheritance hierarchy ........................................................................... 106
11.7. The design model resulting from applying the Class Hierarchy Merge design pattern ....... 107
11.8. A class model with a Person roles hierarchy ......................................................... 109
11.9. An SQL table model with a single table representing the Book class hierarchy ............... 109
11.10. An SQL table model with a single table representing the Person roles hierarchy ......... 110
11.11. An SQL table model with the table Person as the root of a table hierarchy ................. 110
12.1. A class model containing two inheritance hierarchies ................................................ 111
12.2. The object type Book as the root of a disjoint segmentation ....................................... 112
12.3. The Person roles hierarchy ................................................................................ 112
12.4. Student is a category of Person ...................................................................... 113
12.5. The built-in JavaScript classes Object and Function. .......................................... 114
12.6. The simplified information design model obtained by applying the Class Hierarchy
Merge design pattern .................................................................................................. 114
12.7. The JavaScript data model ................................................................................... 115
12.8. The JavaScript data model of the Person class hierarchy ............................................ 121
12.9. The JSON table model of the Person class hierarchy ................................................. 122
13.1. The meta-class mODELcLASS ............................................................................. 130
viii
JavaScript Front-End Web Apps
14.1. A platform-independent design model with the class Book and two invariants ............... 133
14.2. The mODELcLASS validation app's start page index.html. ................................... 135
ix
List of Tables
1.1. Constructor-based versus factory-based classes ............................................................ 12
2.1. A JSON table representing a collection of books ......................................................... 17
2.2. A collection of book objects represented as a table ...................................................... 17
4.1. Datatype mapping .................................................................................................. 43
4.2. Evaluation ............................................................................................................ 52
5.1. An example of an association table ........................................................................... 59
5.2. Different terminologies ........................................................................................... 59
5.3. Functionality types ................................................................................................. 62
14.1. Evaluation ......................................................................................................... 136
x
Foreword
This textbook shows how to build front-end web applications with plain JavaScript, not using any
(third-party) framework or library. 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.
xi
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.
Table of Contents
1. A Quick Tour of the Foundations of Web Apps .............................................................. 3
The World Wide Web (WWW) ............................................................................... 3
HTML and XML ................................................................................................... 3
XML documents ............................................................................................ 3
Unicode and UTF-8 ....................................................................................... 3
XML namespaces .......................................................................................... 4
Correct XML documents ................................................................................. 4
The evolution of HTML ................................................................................. 5
HTML forms ................................................................................................ 5
JavaScript ............................................................................................................. 7
Types and data literals in JavaScript ................................................................. 7
Variable scope .............................................................................................. 8
Strict Mode .................................................................................................. 8
Different kinds of objects ................................................................................ 8
Array lists ................................................................................................... 10
Maps .......................................................................................................... 10
JavaScript supports four types of basic data structures ........................................ 11
Defining and using classes ............................................................................ 11
JavaScript as an object-oriented language ......................................................... 14
Further reading about JavaScript ..................................................................... 14
2. Building a Minimal JavaScript Front-End App in Seven Steps .......................................... 15
Step 1 - Set up the Folder Structure ........................................................................ 15
Step 2 - Write the Model Code .............................................................................. 16
Representing the collection of all Book instances ............................................... 17
Loading all Book instances ............................................................................ 18
Saving all Book instances .............................................................................. 18
Creating a new Book instance ........................................................................ 19
Updating an existing Book instance ................................................................ 19
Deleting an existing Book instance ................................................................. 19
Creating test data ......................................................................................... 20
Clearing all data .......................................................................................... 20
Step 3 - Initialize the Application ........................................................................... 20
Step 4 - Implement the List Objects Use Case ........................................................... 20
Step 5 - Implement the Create Object Use Case ........................................................ 22
Step 6 - Implement the Upate Object Use Case ......................................................... 23
Step 7 - Implement the Delete Object Use Case ........................................................ 24
Run the App and Get the Code .............................................................................. 26
Possible Variations and Extensions ......................................................................... 26
Using IndexedDB as an Alternative to LocalStorage .......................................... 26
Expressing Date/Time Information with the <time> Element ................................ 26
Points of Attention ............................................................................................... 27
2
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.
• the Hypertext Markup Language (HTML) as well as the Extensible Markup Language (XML), and
• web server programs, acting as HTTP servers, as well as web 'user agents' (such as browsers), acting
as HTTP clients.
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 [http://www.w3.org/TR/soap12-
part0/]
Unicode includes legacy character sets like ASCII and ISO-8859-1 (Latin-1) as subsets.
3
A Quick Tour of the
Foundations of Web Apps
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).
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. Each element has a start tag and an end tag; however, empty elements can be closed as <phone/
> instead of <phone></phone>.
<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:
4
A Quick Tour of the
Foundations of Web Apps
An XML document is called valid against a particular grammar (such as a DTD or an XML Schema), if
1. it is well-formed,
• (X)HTML5 in cooperation (and competition) with the WHAT working group [http://
en.wikipedia.org/wiki/WHATWG] led by Ian Hickson [http://en.wikipedia.org/wiki/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.
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>
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
5
A Quick Tour of the
Foundations of Web Apps
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"). Notice that implicit labels are, in 2014, still not well supported
by CSS libraries and assistive technologies. Therefore, explicit labels seem preferable, despite the fact
that they imply quite some overhead and repetitive code.
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,
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.
6
A Quick Tour of the
Foundations of Web Apps
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#.
4. Implementing a frontend component for a distributed web application with remote data storage
managed by a backend component (server-side program).
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.
7
A Quick Tour of the
Foundations of Web Apps
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 freqently 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 keyowrd let.
Strict Mode
Starting from ECMAScript 5, we can use strict mode [http://speakingjs.com/es5/
ch07.html#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 generallly recommended that you use strict mode, except your code depends on libraries that are
incompatible with strict mode.
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:
8
A Quick Tour of the
Foundations of Web Apps
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.
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.
person1.lastName = "Smith"
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:
2. A map (or 'associative array') supports look-ups of values based on keys like, for instance,
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:
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 ???
9
A Quick Tour of the
Foundations of Web Apps
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'.
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:
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:
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,
10
A Quick Tour of the
Foundations of Web Apps
For adding a new entry to a map, we associate the new value with its key as in:
For deleting an entry from a map, we can use the pre-defined delete operator as in:
For testing if a map m has an entry for a certain key k, we can check the following:
if (m[k]) ...
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.
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.
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 [http://chimera.labs.oreilly.com/
books/1234000000262/ch03.html#fluentstyle_javascript] has argued that factory-based classes are
11
A Quick Tour of the
Foundations of Web Apps
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.
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:
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
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:
12
A Quick Tour of the
Foundations of Web Apps
The method getInitials is invoked on the object pers1 of type Person by using the 'dot
notation':
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).
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:
The method getInitials is invoked on the object pers1 of type Person by using the 'dot
notation', like in the constructor-based approach:
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.
13
A Quick Tour of the
Foundations of Web Apps
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.
14
Chapter 2. Building a Minimal
JavaScript Front-End App in Seven
Steps
In this chapter, we build a minimal front-end web application with plain JavaScript and Local Storage.
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 2.1.
Book
isbn : String
title : String
year : Integer
What do we need for such an information management application? There are four standard use cases,
which have to be supported by the application:
1. Create: Enter the data of a book that is to be added to the collection of managed books.
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 data objects, we need a storage technology that allows to keep data
objects in persistent records on a secondary storage device, such as a harddisk or a solid state disk.
Modern web browsers provide two such technologies: the simpler one is called Local Storage, and the
more powerful one is called IndexDB. For our minimal example app, we use Local Storage.
publicLibrary
src
ctrl
model
view
index.html
The start page of the app loads the Book.js model class file and provides a menu for choosing
one of the CRUD data management operations performed by a corresponding page such as,
for instance, createBook.html, or for creating test data with the help of the procedure
Book.createTestData() in line 17, or clearing all data with Book.clearData() in line 18:
15
Building a Minimal JavaScript
Front-End App in Seven Steps
In the information design model shown in Figure 2.1 above, there is only one class, representing the
object type Book. So, in the folder src/model, we create a file Book.js that initially contains
the following code:
The model class Book is encoded as a JavaScript constructor function with a single slots parameter,
which is supposed to be a record object with properties isbn, title and year, representing values
for the ISBN, the title and the year attributes of the class Book. Therefore, in the constructor function,
the values of the slots properties are assigned to the corresponding attributes whenever a new object
is created as an instance of this class.
16
Building a Minimal JavaScript
Front-End App in Seven Steps
In addition to defining the model class in the form of a constructor function, we also define the
following items in the Book.js file:
2. A class-level method Book.loadAll for loading all managed Book instances from the persistent
data store.
3. A class-level method Book.saveAll for saving all managed Book instances to the persistent
data store.
Book.instances = {};
So, initially our collection of books is empty. In fact, it's defined as an empty object literal, since we
want to represent it in the form of a JSON table (a map of records) where an ISBN is a key for accessing
the corresponding book record (as the value associated with the key). We can visualize the structure
of a JSON table in the form of a lookup table, as shown in Table 2.1, “A JSON table representing a
collection of books”.
Notice that the values of such a map are records corresponding to table rows. Consequently, we could
also represent them in a simple table, as shown in Table 2.2, “A collection of book objects represented
as a table”.
17
Building a Minimal JavaScript
Front-End App in Seven Steps
1. Retrieving the book table that has been stored as a large string with the key "books" from Local
Storage with the help of the assignment
booksString = localStorage["books"];
2. Converting the book table string into a corresponding JSON table books with book rows as
elements, with the help of the built-in procedure JSON.parse:
This conversion, performed in line 11 of the program listing below, is called deserialization.
3. Converting each row of books, representing a record (an untyped object), into a corresponding
object of type Book stored as an element of the JSON table Book.instances, with the help of
the procedure convertRow2Obj defined as a "static" (class-level) method in the Book class:
Book.loadAll = function () {
var key="", keys=[],
booksString="", books={};
try {
if (localStorage["books"]) {
booksString = localStorage["books"];
}
} catch (e) {
alert("Error when reading from Local Storage\n" + e);
}
if (booksString) {
books = JSON.parse( booksString);
keys = Object.keys( books);
console.log( keys.length +" books loaded.");
for (i=0; i < keys.length; i++) {
key = keys[i];
Book.instances[key] = Book.convertRow2Obj( books[key]);
}
}
};
Notice that since an input operation like localStorage["books"] may fail, we perform it in a
try-catch block, where we can follow up with an error message whenever the input operation fails.
18
Building a Minimal JavaScript
Front-End App in Seven Steps
1. Converting the JSON table Book.instances into a string with the help of the predefined
JavaScript procedure JSON.stringify:
2. Writing the resulting string as the value of the key "books" to Local Storage:
localStorage["books"] = booksString;
These two steps are performed in line 5 and in line 6 of the following program listing:
Book.saveAll = function () {
var booksString="", error=false,
nmrOfBooks = Object.keys( Book.instances).length;
try {
booksString = JSON.stringify( Book.instances);
localStorage["books"] = booksString;
} catch (e) {
alert("Error when writing to Local Storage\n" + e);
error = true;
}
if (!error) console.log( nmrOfBooks + " books saved.");
};
Notice that in the case of a numeric attribute (such as year), we have to make sure that the value
of the corresponding input parameter (y), which is typically obtained from user input via an HTML
form, is converted from String to Number with one of the two type conversion functions parseInt
or parseFloat.
19
Building a Minimal JavaScript
Front-End App in Seven Steps
Book.createTestData = function () {
Book.instances["006251587X"] = new Book(
{isbn:"006251587X", title:"Weaving the Web", year:2000});
Book.instances["0465026567"] = new Book(
{isbn:"0465026567", title:"Gödel, Escher, Bach", year:1999});
Book.instances["0465030793"] = new Book(
{isbn:"0465030793", title:"I Am A Strange Loop", year:2008});
Book.saveAll();
};
Book.clearData = function () {
if (confirm("Do you really want to delete all book data?")) {
localStorage["books"] = "{}";
}
};
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. We put this code in a separate
file initialize.js in the ctrl folder, because such a namespace definition belongs to the
controller part of the application code.
20
Building a Minimal JavaScript
Front-End App in Seven Steps
The user interface for this use case is provided by the following HTML page containing an HTML table
for displaying the book objects. For our example app, this page would be called listBooks.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>Simple JS Front-End App Example</title>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
<script src="src/view/listBooks.js"></script>
<script>
window.addEventListener( "load",
pl.view.listBooks.setupUserInterface);
</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>
Notice that this HTML file loads three JavaScript files: the controller file src/ctrl/
initialize.js, the model file src/model/Book.js and the view file src/view/
listBooks.js. The first two files contain the code for initializing the app and for the model
class Book as explained above, and the third one, which represents the UI code of the "list books"
operation, is developed now. In fact, for this operation, we just need a procedure for setting up the
data management context and the UI, called setupUserInterface:
pl.view.listBooks = {
setupUserInterface: function () {
var tableBodyEl = document.querySelector("table#books>tbody");
var keys=[], key="", row={};
// load all book objects
Book.loadAll();
keys = Object.keys( Book.instances);
// for each book, create a table row with cells for the 3 attributes
for (i=0; i < keys.length; i++) {
key = keys[i];
row = tableBodyEl.insertRow();
row.insertCell(-1).textContent = Book.instances[key].isbn;
row.insertCell(-1).textContent = Book.instances[key].title;
row.insertCell(-1).textContent = Book.instances[key].year;
}
}
};
1. Read the collection of all objects from the persistent data store (in line 6).
2. Display each object as a row in a HTML table on the screen (in the loop starting in line 9).
More specifically, the procedure setupUserInterface first creates the book objects from the
corresponding rows retrieved from Local Storage by invoking Book.loadAll() and then creates
21
Building a Minimal JavaScript
Front-End App in Seven Steps
the view table in a loop over all key-value slots of the JSON table Book.instances where each
value represents a book object. In each step of this loop, a new row is created in the table body element
with the help of the JavaScript DOM operation insertRow(), and then three cells are created in
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. 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.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8" />
<title>Minimal JS Front-End App Example</title>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
<script src="src/view/createBook.js"></script>
<script>
window.addEventListener("load",
pl.view.createBook.setupUserInterface);
</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>
<nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>
1. setupUserInterface takes care of retrieveing the collection of all objects from the persistent
data store and setting up an event handler (handleSaveButtonClickEvent) on the save
button for handling click button events by saving the user input data;
2. handleSaveButtonClickEvent reads the user input data from the form fields and then saves
this data by calling the Book.saveRow procedure.
pl.view.createBook = {
setupUserInterface: function () {
var saveButton = document.forms['Book'].commit;
// load all book objects
Book.loadAll();
// Set an event handler for the save/submit button
saveButton.addEventListener("click",
pl.view.createBook.handleSaveButtonClickEvent);
22
Building a Minimal JavaScript
Front-End App in Seven Steps
window.addEventListener("beforeunload", function () {
Book.saveAll();
});
},
// save user input data
handleSaveButtonClickEvent: function () {
var formEl = document.forms['Book'];
var slots = { isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value};
Book.add( slots);
formEl.reset();
}
};
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8" />
<title>Minimal JS Front-End App Example</title>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
<script src="src/view/updateBook.js"></script>
<script>
window.addEventListener("load", pl.view.updateBook.setupUserInterface);
</script>
</head>
<body>
<h1>Public Library: Update a book record</h1>
<form id="Book" action="">
<p>
<label>Select book:
<select name="selectBook"><option value=""> --- </option></select>
</label>
</p>
<p><label>ISBN: <input name="isbn" readonly="readonly" /></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>
<nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>
Notice that we include a kind of empty option element, with a value of "" and a display text of ---,
as a default choice in the selectBook selection list element. So, by default, the selectBook form
control's value is undefined, requiring the user to choose one of the available options for defining a
value for this element.
23
Building a Minimal JavaScript
Front-End App in Seven Steps
The setupUserInterface procedure now has to populate the select element's option list by
loading the collection of all book objects from the persistent data store and creating an option element
for each book object:
pl.view.updateBook = {
setupUserInterface: function () {
var formEl = document.forms['Book'],
saveButton = formEl.commit,
selectBookEl = formEl.selectBook;
var key="", keys=[], book=null, optionEl=null;
// load all book objects
Book.loadAll();
// populate the selection list with books
keys = Object.keys( Book.instances);
for (i=0; i < keys.length; i++) {
key = keys[i];
book = Book.instances[key];
optionEl = document.createElement("option");
optionEl.text = book.title;
optionEl.value = book.isbn;
selectBookEl.add( optionEl, null);
}
// when a book is selected, populate the form with the book data
selectBookEl.addEventListener("change", function () {
var book=null, key = selectBookEl.value;
if (key) {
book = Book.instances[key];
formEl.isbn.value = book.isbn;
formEl.title.value = book.title;
formEl.year.value = book.year;
} else {
formEl.reset();
}
});
saveButton.addEventListener("click",
pl.view.updateBook.handleUpdateButtonClickEvent);
window.addEventListener("beforeunload", function () {
Book.saveAll();
});
},
handleUpdateButtonClickEvent: function () {
var formEl = document.forms['Book'];
var slots = { isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value
};
Book.update( slots);
formEl.reset();
}
};
24
Building a Minimal JavaScript
Front-End App in Seven Steps
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8" />
<title>Minimal JS Front-End App Example</title>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
<script src="src/view/deleteBook.js"></script>
<script>
window.addEventListener("load", pl.view.deleteBook.setupUserInterface);
</script>
</head>
<body>
<h1>Public Library: Delete a book record</h1>
<form id="Book">
<p>
<label>Select book:
<select name="selectBook"><option value=""> --- </option></select>
</label>
</p>
<p><button type="button" name="commit">Delete</button></p>
</form>
<nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>
pl.view.deleteBook = {
setupUserInterface: function () {
var deleteButton = document.forms['Book'].commit;
var selectEl = document.forms['Book'].selectBook;
var key="", keys=[], book=null, optionEl=null;
// load all book objects
Book.loadAll();
keys = Object.keys( Book.instances);
// populate the selection list with books
for (i=0; i < keys.length; i++) {
key = keys[i];
book = Book.instances[key];
optionEl = document.createElement("option");
optionEl.text = book.title;
optionEl.value = book.isbn;
selectEl.add( optionEl, null);
}
deleteButton.addEventListener("click",
pl.view.deleteBook.handleDeleteButtonClickEvent);
window.addEventListener("beforeunload", function () {
Book.saveAll();
});
},
handleDeleteButtonClickEvent: function () {
var selectEl = document.forms['Book'].selectBook;
var isbn = selectEl.value;
if (isbn) {
Book.destroy( isbn);
selectEl.remove( selectEl.selectedIndex);
}
25
Building a Minimal JavaScript
Front-End App in Seven Steps
}
};
Alternatively, for remotely storing the application data with the help of a web API one can either
use a back-end solution component or a cloud storage service. The remote storage approach allows
managing larger databases and supports multi-user apps.
2. Internally, for annotating localized date/time strings, or externally, for displaying a date/time value
in a standard form, as an ISO standard date/time string, e.g., with the help of toISOString().
3. Externally, for displaying a date/time value in a localized form, as a localized date/time string, e.g.,
with the help of toLocaleDateString().
When a date/time value is to be included in a web page, we can use the <time> element that allows to
display a human-readable representation (typically a localized date/time string) that is annotated with
a standard (machine-readable) form of the date/time value.
We illustrate the use of the <time> element with the following example of a web page that includes
two <time> elements: one for displaying a fixed date, and another (initially empty) element for
displaying the date of today, which is computed with the help of a JavaScript function. In both cases we
use the datetime attribute for annotating the displayed human-readable date with the corresponding
machine-readable representation.
26
Building a Minimal JavaScript
Front-End App in Seven Steps
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="UTF-8" />
<title>Using the HTML5 Time Element</title>
<script src="assignDate.js"></script>
<script>window.addEventListener("load", assignDate);</script>
</head>
<body>
<h1>HTML5 Time Element</h1>
<p>HTML 2.0 was published on <time datetime="1995-11-24">November 24, 1995</tim
<p>Today is <time id="today" datetime=""></time>.</p>
</body>
</html>
This web page loads and executes the following JavaScript function for computing today's date as
a Date value and assigning its ISO standard representation and its localized representation to the
<time> element:
function assignDate() {
var dateEl = document.getElementById("today");
var today = new Date();
dateEl.textContent = today.toLocaleDateString();
dateEl.setAttribute("datetime", today.toISOString());
}
Points of Attention
The code of this app should be extended by
• adding some CSS styling for the user interface pages and
We show how to do this in the follow-up tutorial JavaScript Front-End Web Apps Tutorial Part 2:
Adding Constraint Validation [http://web-engineering.info/JsFrontendApp/validation-tutorial.html].
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 map Book.instances). This approach only
works in the case of local data storage of smaller databases, say, with not more than 2 MB of data,
roughgly corresponding to 10 tables with an average population of 1000 rows, each having an average
size of 200 Bytes. When larger databases are to be managed, or when data is stored remotely, 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 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.
27
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.
Table of Contents
3. Integrity Constraints and Data Validation ..................................................................... 30
String Length Constraints ...................................................................................... 31
Mandatory Value Constraints ................................................................................. 31
Range Constraints ................................................................................................ 32
Interval Constraints .............................................................................................. 32
Pattern Constraints ............................................................................................... 33
Cardinality Constraints .......................................................................................... 34
Uniqueness Constraints ......................................................................................... 34
Standard Identifiers (Primary Keys) ........................................................................ 35
Referential Integrity Constraints ............................................................................. 35
Frozen Value Constraints ...................................................................................... 35
Constraint Validation in MVC Applications .............................................................. 36
4. Constraint Validation in a JavaScript Front-End Web App ............................................... 38
Using the HTML5 Form Validation API .................................................................. 38
New Issues ......................................................................................................... 39
Make a JavaScript Data Model ............................................................................... 39
Set up the folder structure and create four initial files ................................................. 40
Style the user interface with CSS .................................................................... 41
Provide general utility functions and JavaScript fixes in library files ...................... 41
Create a start page ....................................................................................... 41
Write the Model Code .......................................................................................... 42
Summary .................................................................................................... 42
Encode the model class as a constructor function ............................................... 43
Encode the property checks ........................................................................... 43
Encode the property setters ............................................................................ 44
Add a serialization function ........................................................................... 44
Data management operations .......................................................................... 45
The View and Controller Layers ............................................................................. 46
The data management UI pages ...................................................................... 47
Initialize the app .......................................................................................... 48
Initialize the data management use cases .......................................................... 48
Set up the user interface ................................................................................ 49
Run the App and Get the Code .............................................................................. 52
Evaluation ........................................................................................................... 52
Possible Variations and Extensions ......................................................................... 52
Simplifying forms with implicit labels ............................................................. 52
Enumerations and enumeration attributes ......................................................... 53
Points of Attention ............................................................................................... 55
29
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.
30
Integrity Constraints
and Data Validation
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.
Person
name[1] : String
age[0..1] : Integer
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:
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:
31
Integrity Constraints
and Data Validation
} else {
...
}
};
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:
Person
name[1] : NonEmptyString
age[0..1] : NonNegativeInteger
In JavaScript, we can encode a range constraint by a check function, as illustrated in the following
example:
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.
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.
Person
«invariant»
name[1] : String {age in [0,120]}
age[0..1] : Integer
32
Integrity Constraints
and Data Validation
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:
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.
Book «invariant»
isbn : String {isbn must be a 10-digit string
title : String or a 9-digit string followed by "X"}
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:
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
[http://www.postgresql.org/docs/9.0/static/functions-matching.html]).
In JavaScript, we can encode a pattern constraint by using the built-in regular expression function
test, as illustrated in the following example:
33
Integrity Constraints
and Data Validation
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.
Person
Team
name[1] : String
name[1] : String
age[0..1] : Integer
members[3..5] : Person
nickNames[0..3] : String
The attribute definition nickNames[0..3] in the class Person specificies 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 specificies
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:
With Java Bean Validation annotations, we can specify @Size( max=3) List<String>
nickNames or @Size( min=3, max=5) List<Person> members.
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.
Book
isbn : String {unique}
title : String
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:
34
Integrity Constraints
and Data Validation
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.
Figure 3.5. The object type Book with a standard identifier declaration
Book
«stdid» isbn : String
title : String
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:
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.
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.
35
Integrity Constraints
and Data Validation
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.
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 backend
component running on a remote web server. Only this backend 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 [http://www.html5rocks.com/en/tutorials/forms/constraintvalidation/
] supports constraint validation in the UI. Alternatively, the jQuery Validation Plugin [http://
jqueryvalidation.org/] 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 Persistenvy and Bean Validation
Annotations or ASP.NET Data Annotations) and generate the backend/model and front-end/UI
validation code both in a backend 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 backend, 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 backend application programming language (such as with NodeJS), then you
can encode your validation functions in your JavaScript model classes on the backend and execute
them both before committing changes on the backend and on user input and form submission in
the UI on the front-end side.
36
Integrity Constraints
and Data Validation
The simplest, and most responsive, solution is the third one, using only JavaScript both in the backend
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?
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?
37
Chapter 4. Constraint Validation in a
JavaScript Front-End Web App
The minimal JavaScript front-end web 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 JavaScript model class, and how to perform
constraint validation both in the model part of the app and in the user interface built with HTML5.
We 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 object type Book and
two invariants
Book «invariant»
«stdid» isbn[1] : NonEmptyString {isbn must be a 10-digit string
title[1] : NonEmptyString(50) or a 9-digit string followed by "X"}
year[1] : Integer
edition[0..1] : PositiveInteger
«invariant»
{year > 1454 AND
year <= nextYear()}
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, as indicated by its multiplicity expression [1], and has a string
length constraint requiring its values to have at most 50 characters.
4. The year attribute is mandatory and has an interval constraint, however, of a special form
since the maximum is not fixed, but provided by the calendaric function nextYear(), which we
implement as a utility function.
Notice that the edition attribute is not mandatory, but optional, as indicated by its multiplicity
expression [0..1]. In addition to the constraints described in this list, there are the implicit range
constraints defined by assigning the datatype NonEmptyString as range to isbn and title,
Integer to year, and PositiveInteger to edition. In our plain JavaScript approach, all
these property constraints are encoded in the model class within property-specific check functions.
38
Constraint Validation in a
JavaScript Front-End Web App
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.
New Issues
Compared to the minimal app [http://web-engineering.info/JsFrontendApp/MinimalApp/index.html]
discussed in Part 1 (Minimal App Tutorial [http://web-engineering.info/JsFrontendApp/minimal-
tutorial.html]) we have to deal with a number of new issues:
a. adding for every property of a class a check function that can be invoked for validating the
constraints defined for the property, and a setter method that invokes the check function and is
to be used for setting the value of the property,
b. responsive validation on user input for providing immediate feedback to the user,
c. validation on form submission for preventing the submission of flawed data to the model layer.
For improving the break-down of the view code, we introduce a utility method (in lib/util.js)
that fills a select form control with option elements the contents of which is retrieved from
a JSON table such as Book.instances. This method is used in the setupUserInterface
method of both the updateBook and the deleteBook use cases.
Checking the constraints in the user interface 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 code before saving/
sending data to the persistent datastore.
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 on user input
and on form submission, as well as in the create and update data management methods of the model
class via invoking the setters. Notice that 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.
1. Create a check operation for each non-derived property in order to have a central place for
implementing all the constraints that have been defined for a property in the design model. For
a standard identifier (or primary key) attribute, such as Book::isbn, two check operations are
needed:
39
Constraint Validation in a
JavaScript Front-End Web App
a. A check operation, such as checkIsbn, for checking all basic constraints of an identifier
attribute, except the mandatory value and the uniqueness constraints.
b. A check operation, such as checkIsbnAsId, for checking in addition to the basic constraints
the mandatory value and uniqueness constraints that are required for an identifier attribute.
The checkIsbnAsId operation is invoked on user input for the isbn form field in the create
book form, and also in the setIsbn method, while the checkIsbn operation can be used for
testing if a value satisfies the syntactic constraints defined for an ISBN.
2. Create a setter operation for each non-derived single-valued property. In the setter, the
corresponding check operation is invoked and the property is only set, if the check does not detect
any constraint violation.
3. Create add and remove operations for each non-derived multi-valued property (if there are any).
This leads to the JavaScript data model shown on the right hand side of the mapping arrow in the
following figure.
Figure 4.2. Deriving a JavaScript data model from an information design model
Book
«stdid» isbn[1] : NonEmptyString Book
title[1] : NonEmptyString(50) «stdid» isbn[1] : string
year[1] : Integer title[1] : string {NonEmptyString(50)}
edition[0..1] : PositiveInteger year[1] : number {Integer}
edition[0..1] : number {PositiveInteger}
checkIsbn(in isbn : String) : ConstraintViolation
«invariant» checkIsbnAsId(in isbn : String) : ConstraintViolation
{isbn must be a 10-digit string setIsbn(in isbn : String)
or a 9-digit string followed by "X"} checkTitle(in title : String) : ConstraintViolation
setTitle(in title : String)
checkYear(in year : Integer) : ConstraintViolation
«invariant» setYear(in year : Integer)
{year > 1454 AND checkEdition(in ed : PositiveInteger) : ConstraintViolation
year <= nextYear()} setEdition(in ed : PositiveInteger)
Esssentially, the JavaScript data model extends the design model by adding checks and setters for
each property. The attached invariants have been dropped since they are taken care of in the checks.
Property ranges have been turned into JavaScript datatypes (with a reminder to their real range in curly
braces). Notice that the names of check functions are underlined, since this is the convention in UML
for class-level (as opposed to instance-level) operations.
publicLibrary
css
main.css
lib
browserShims.js
errorTypes.js
40
Constraint Validation in a
JavaScript Front-End Web App
util.js
src
ctrl
model
view
index.html
We discuss the contents of the four initial files in the following sections.
2. browserShims.js contains a definition of the string trim function for older browsers that don't
support this function (which was only added to JavaScript in ECMAScript Edition 5, defined in
2009). More browser shims for other recently defined functions, such as querySelector and
classList, could also be added to browserShims.js.
3. errorTypes.js defines general classes for error (or exception) types: NoConstraintViolation,
MandatoryValueConstraintViolation, RangeConstraintViolation, IntervalConstraintViolation,
PatternConstraintViolation, UniquenessConstraintViolation, OtherConstraintViolation.
2. initialize.js from the src/ctrl folder, defining the app's MVC namespaces, as discussed
in Part 1 (the minimal app tutorial).
4. Book.js from the src/model folder, a model class file that provides data management and
other functions discussed in Section .
41
Constraint Validation in a
JavaScript Front-End Web App
The JavaScript 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 JavaScript front-end app. These steps are summarized
in the following section.
Summary
1. Encode the model class as a JavaScript constructor function.
2. Encode the check functions, such as checkIsbn or checkTitle, in the form of class-level
('static') methods. Take care that all constraints, as specified in the JavaScript data model, are
properly encoded in the check functions.
4. Encode the add and remove operations, if there are any, as instance-level methods.
42
Constraint Validation in a
JavaScript Front-End Web App
In the constructor body, we first assign default values to the class properties. These values will be used
when the constuctor is invoked as a default constructor (without arguments), or when it is invoked
with only some arguments. It is helpful to indicate the range of a property in a comment. This requires
to map the platform-independent data types of the information design model to the corresponding
implicit JavaScript data types according to the following table.
Since the setters may throw constraint violation errors, the constructor function, and any setter, should
be called in a try-catch block where the catch clause takes care of processing errors (at least logging
suitable error messages).
As in the minimal app, we add a class-level property Book.instances representing the collection
of all Book instances managed by the application in the form of a JSON table:
Book.instances = {};
43
Constraint Validation in a
JavaScript Front-End Web App
For instance, for the checkIsbn operation we obtain the following code:
Notice that, since isbn is the standard identifier attribute of Book, we only check the syntactic
constraints in checkIsbn, but we check the mandatory value and uniqueness constraints in
checkIsbnAsId, which itself first invokes checkIsbn:
There are similar setters for the other properties (title, year and edition).
44
Constraint Validation in a
JavaScript Front-End Web App
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 +"}";
};
1. Book.convertRow2Obj and Book.loadAll for loading all managed Book instances from
the persistent data store.
2. Book.saveAll for saving all managed Book instances to the persistent data store.
6. Book.createTestData for creating a few example book records to be used as test data.
All of these methods essentially have the same code as in our minimal app discussed in Part 1, except
that now
1. we may have to catch constraint violations in suitable try-catch blocks in the procedures
Book.convertRow2Obj, Book.add, Book.update and Book.createTestData; and
2. we can use the toString() function for serializing an object in status and error messages.
Notice that for the change operations create and update, we need to implement an all-or-nothing
policy: as soon as there is a constraint violation for a property, no new object must be created and no
(partial) update of the affected object must be performed.
When a constraint violation is detected in one of the setters called when new Book(...) is invoked
in Book.add, the object creation attempt fails, and instead a constraint violation error message is
created in line 6. Otherwise, the new book object is added to Book.instances and a status message
is creatred in lines 10 and 11, as shown in the following program listing:
45
Constraint Validation in a
JavaScript Front-End Web App
};
Likewise, when a constraint violation is detected in one of the setters invoked in Book.update, 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:
After loading the Pure [http://purecss.io/] 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 lib/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.
46
Constraint Validation in a
JavaScript Front-End Web App
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 Front-End 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="lib/errorTypes.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><th>Edition</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 Front-End 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="lib/errorTypes.js"></script>
<script src="src/ctrl/initialize.js"></script>
<script src="src/model/Book.js"></script>
<script src="src/view/createBook.js"></script>
47
Constraint Validation in a
JavaScript Front-End Web App
<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-control-group">
<label for="edition">Edition</label>
<input id="edition" name="edition" />
</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 [http://purecss.io/] 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.
pl.ctrl.createBook = {
initialize: function () {
pl.ctrl.createBook.loadData();
48
Constraint Validation in a
JavaScript Front-End Web App
pl.view.createBook.setupUserInterface();
},
loadData: function () {
Book.loadAll();
}
};
All other data management use cases (read/list, update, delete) are handled in the same way.
pl.view.listBooks = {
setupUserInterface: function () {
var tableBodyEl = document.querySelector("table#books>tbody");
var i=0, book=null, row={}, key="",
keys = Object.keys( Book.instances);
for (i=0; i < keys.length; i++) {
key = keys[i];
book = Book.instances[key];
row = tableBodyEl.insertRow(-1);
row.insertCell(-1).textContent = book.isbn;
row.insertCell(-1).textContent = book.title;
row.insertCell(-1).textContent = book.year;
if (book.edition) {
row.insertCell(-1).textContent = book.edition;
}
}
}
};
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 28 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);
49
Constraint Validation in a
JavaScript Front-End Web App
});
formEl.year.addEventListener("input", function () {
formEl.year.setCustomValidity(
Book.checkYear( formEl.year.value).message);
});
formEl.edition.addEventListener("input", function () {
formEl.edition.setCustomValidity(
Book.checkEdition( formEl.edition.value).message);
});
// neutralize the submit event
formEl.addEventListener( 'submit', function (e) {
e.preventDefault();;
formEl.reset();
});
window.addEventListener("beforeunload", function () {
Book.saveAll();
});
},
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,
edition: formEl.edition.value
};
// set 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);
formEl.edition.setCustomValidity(
Book.checkEdition( formEl.edition.value).message);
// save the input data only if all of the form fields are valid
if (formEl.checkValidity()) {
Book.add( slots);
}
}
By invoking checkValidity() on the form element, we make sure that the form
data is only saved (by Book.add), if there is no constraint violation. After this
50
Constraint Validation in a
JavaScript Front-End Web App
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.fillSelectWithOptions( Book.instances,
selectBookEl, "isbn", "title");
// 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;
if (book.edition) formEl.edition.value = book.edition;
} else {
formEl.reset();
}
});
formEl.title.addEventListener("input", function () {
formEl.title.setCustomValidity(
Book.checkTitle( formEl.title.value).message);
});
formEl.year.addEventListener("input", function () {
formEl.year.setCustomValidity(
Book.checkYear( formEl.year.value).message);
});
formEl.edition.addEventListener("input", function () {
formEl.edition.setCustomValidity(
Book.checkEdition( formEl.edition.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.saveAll();
});
},
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():
51
Constraint Validation in a
JavaScript Front-End Web App
handleSubmitButtonClickEvent: function () {
var formEl = document.forms['Book'];
var slots = { isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value,
edition: formEl.edition.value
};
// set error messages in case of constraint violations
formEl.title.setCustomValidity(
Book.checkTitle( slots.title).message);
formEl.year.setCustomValidity(
Book.checkYear( slots.year).message);
formEl.edition.setCustomValidity(
Book.checkEdition( formEl.edition.value).message);
if (formEl.checkValidity()) {
Book.update( slots);
}
}
The logic of the setupUserInterface method for the delete use case is similar.
Evaluation
We evaluate the approach presented in this chapter of the tutorial according to the criteria defined in
the previous chapter.
<div class="pure-control-group">
<label for="isbn">ISBN:</label>
<input id="isbn" name="isbn" />
52
Constraint Validation in a
JavaScript Front-End Web App
</div>
This technique for associating a label with a form field is getting quite inconvenient when we have
many form fields on a page bceuase 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.
We can implement an enumeration in the form of a special JavaScript object definition using the
Object.defineProperties method:
Notice how this definition of an enumeration of book categories takes care of the requirement
that enumeration literals like BookCategoryEL.NOVEL are constants, the value of which cannot
be changed during program execution. This is achieved with the help of the property descriptor
writable: false in the Object.defineProperties statement.
We can also use a generic approach and define an Enumeration class for creating enumerations:
53
Constraint Validation in a
JavaScript Front-End Web App
Using the Enumeration class allows to define a new enumeration in the following simplified way:
Having an enumeration like BookCategoryEL, we can then check if an enumeration attribute like
category has an admissible value by testing if its value is not smaller than 1 and not greater than
BookCategoryEL.MAX.
We consider the following model class Book with the enumeration attribute category:
For validating input values for the enumeration attribute category, we can use the following check
function:
Notice how the range constraint defined by the enumeration BookCategoryEL is checked: it is
tested if the input value c is a positive integer and if it is not greater than BookCategoryEL.MAX.
In the user interface, an output field for an enumeration attribute would display the enumeration label,
rather than the enumeration integer. The label can be retrieved in the following way:
formEl.category.value = BookCategoryEL.labels[this.category];
54
Constraint Validation in a
JavaScript Front-End Web App
For user input to a single-valued enumeration attribute like Book::category, a radio button
group could be used if the number of enumeration literals is sufficiently small, otherwise a single
selection list would be used. If the selection list is implemented with an HTML select element, the
enumeration labels would be used as the text content of the option elements, while the enumeration
integers would be used as their values.
For user input to a multi-valued enumeration attribute, a checkbox group could be used if the number
of enumeration literals is sufficiently small, otherwise a multiple selection list would be used. For
usability, the multiple selection list can only be implemented with an HTML select element, if the
number of enumeration literals does not exceed a certain threshold, which depends on the number of
options the user can see on the screen without scrolling.
Enumerations may have further features. For instance, we may want to be able to define a
new enumeration by extending an existing enumeration. In programming languages and in other
computational languages, enumerations are implemented with different features in different ways. See
also the Wikipedia article on enumerations [http://en.wikipedia.org/wiki/Enumerated_type].
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.
55
Part III. Associations
Associations 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
associations.
Table of Contents
5. Reference Properties and Unidirectional Associations ..................................................... 59
References and Reference Properties ....................................................................... 60
Referential Integrity .............................................................................................. 61
Modeling Reference Properties as Unidirectional Associations ..................................... 61
Representing Unidirectional Associations as Reference Properties ................................. 62
Adding Directionality to a Non-Directed Association ................................................. 63
Our Running Example .......................................................................................... 64
Eliminating Unidirectional Associations ................................................................... 64
The basic elimination procedure restricted to unidirectional associations ................. 64
Eliminating associations from the Publisher-Book-Author design model ................. 65
Rendering Reference Properties in the User Interface ................................................. 66
6. Part-Whole Associations ............................................................................................ 67
Composition ........................................................................................................ 67
Aggregation ........................................................................................................ 67
7. Implementing Unidirectional Functional Associations with Plain JavaScript ........................ 69
Implementing Single-Valued Reference Properties in JavaScript ................................... 69
Make a JavaScript Data Model ............................................................................... 70
New issues ......................................................................................................... 71
Write the Model Code .......................................................................................... 72
Summary .................................................................................................... 72
Encode each class of the JavaScript data model as a constructor function ................ 72
Encode the property checks ........................................................................... 73
Encode the property setters ............................................................................ 74
Encode the add and remove operations ............................................................ 75
Implement a deletion policy ........................................................................... 75
Serialization and De-Serialization ................................................................... 75
The View and Controller Layers ............................................................................. 76
Initialize the app .......................................................................................... 76
Show information about associated objects in the List Objects use case .................. 76
Allow selecting associated objects in the create and update use cases .................... 77
8. Implementing Unidirectional Non-Functional Associations with Plain JavaScript .................. 79
Implementing Multi-Valued Reference Properties in JavaScript .................................... 79
Make a JavaScript Data Model ............................................................................... 80
New issues ......................................................................................................... 81
Write the Model Code .......................................................................................... 82
Encode the add and remove operations ............................................................ 82
Implement a deletion policy ........................................................................... 83
Serialization and De-Serialization ................................................................... 83
Write the User Interface Code ................................................................................ 84
Show information about associated objects in the List Objects use case .................. 84
Allow selecting associated objects in the create use case ..................................... 85
Allow selecting associated objects in the update use case .................................... 86
Run the App and Get the Code .............................................................................. 89
Points of Attention ............................................................................................... 89
9. Bidirectional Associations .......................................................................................... 90
Inverse Reference Properties .................................................................................. 90
Making an Association-Free Information Design Model .............................................. 92
The basic procedure ..................................................................................... 92
How to eliminate uni-directional associations .................................................... 92
How to eliminate bi-directional associations ..................................................... 92
The resulting association-free design model ...................................................... 93
10. Implementing Bidirectional Associations with Plain JavaScript ....................................... 95
Make a JavaScript Data Model ............................................................................... 95
Write the Model Code .......................................................................................... 96
New issues ................................................................................................. 96
57
Associations
Summary .................................................................................................... 97
Encode each class of the JavaScript data model as a constructor function ................ 98
Encode the property checks ........................................................................... 98
Encode the setter operations .......................................................................... 98
Encode the add and remove operations ............................................................ 99
Take care of deletion dependencies ................................................................. 99
Exploiting Derived Inverse Reference Properties in the User Interface .......................... 100
Show information about published books in the List Publishers use case ............... 100
58
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 Club-has-ClubMember-as-chair, which is visualized as a connection line
in the class diagram shown in Figure 5.2 below, classifies the relationships SantaFeSoccerClub-has-
PeterMiller-as-chair, CaliforniaHikerClub-has-SusanSmith-as-chair and BerlinRotaryClub-has-
SarahAnderson-as-chair, where the objects PeterMiller, SusanSmith and SarahAnderson are of type
ClubMember, and the objects SantaFeSoccerClub, CaliforniaHikerClub and BerlinRotaryClub are
of type Club.
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'.
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:
59
Reference Properties and
Unidirectional Associations
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.
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
Committee
name : String ClubMember
chair : ClubMember
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.
60
Reference Properties and
Unidirectional Associations
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.
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. 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
b. 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.
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.
61
Reference Properties and
Unidirectional Associations
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 Club. Consequently, it's an
example of a functional association.
The following table provides an overview about the different cases of functionality of an association:
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).
Committee ClubMember
name : String chairedCommittee[0..1] : Committee
62
Reference Properties and
Unidirectional Associations
chairedCommittee chair
Committee
ClubMember
name : String
0..1 1
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.
chairedCommittee chair
Committee
ClubMember
name : String
0..1 1
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).
63
Reference Properties and
Unidirectional Associations
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.
Book
Publisher
«stdid» isbn : String
«stdid» name : String
title : String
address : String 0..1 * year : Integer
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.
Author
Book «enumeration»
Publisher «stdid» personId[1] : Integer GenderEL
«stdid» isbn : String name[1] : String
«stdid» name : String FEMALE
title : String gender[1] : GenderEL
address : String 0..1 * * * MALE
year : Integer dateOfBirth[1] : Date
dateOfDeath[0..1] : Date
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.
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);
64
Reference Properties and
Unidirectional Associations
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
Point Point
Polygon
Polygon x : Integer x : Integer
points[3..*] : Point
1 3..* y : Integer y : Integer
Publisher Book
«stdid» name : String «stdid» isbn[1] : String
adress : String title[1] : String
year[1] : Integer
«invariant» publisher[0..1] : Publisher
{year >= 1454
and year <= nextYear}
«invariant»
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
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:
65
Reference Properties and
Unidirectional Associations
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.
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.
66
Chapter 6. Part-Whole Associations
A part-whole association is an association that represents a relationship between a part type and a
whole type. Its instances are part-whole relationships between two objects where one of them is a part
of the other.
Composition
As Martin Fowler has explained [http://martinfowler.com/bliki/AggregationAndComposition.html],
the main issue for characterizing composition is that "an object can only be the part of one
composition relationship". This is also explained in the excellent blog post UML Composition
vs Aggregation vs Association [http://bellekens.com/2010/12/20/uml-composition-vs-aggregation-
vs-association/] by Geert Bellekens. In addition to this defining characteristic of a composition (to
have exclusive, or non-shareable, parts), a composition may also come with a life-cycle dependency
between the whole and its parts implying that when a whole is destroyed, all of its parts are destroyed
with it. However, this only applies to some cases of composition, and not to others, and it is therefore
not a defining characteristic.
For instance, in the Course-Lecture composition shown in the following diagram, we have a
lifecycle dependency between courses and lectures such that when a course is dropped (from a
curriculum), all its lectures are dropped/deleted as well. This is implied by the exactly one multiplicity
at the composite side of the composition line.
Course Lecture
id : String sequenceNo : Integer
name : String 1 * title : String
:The UML spec states: "A part may be removed from a composite instance before the composite
instance is deleted, and thus not be deleted as part of the composite instance." In the example of a
Car-Engine composition, as shown in the following diagram, it's clearly the case that the engine can
be detached from the car before the car is destroyed, in which case the engine is not destroyed and can
be re-used. This is implied by the zero or one multiplicity at the composite side of the composition line.
Car Engine
licenseNo : String id : String
color : String 0..1 0..1 cylinderCapacity : Decimal
Tthe multiplicity of a composition's association end at the whole side is either 1 or 0..1, depending
on the fact if parts are separable, that is, if they can be detached and exist on their own.
Aggregation
An aggregation is another special form of association with the intended meaning of a part-whole-
relationship, where the parts of a whole can be shared with other wholes. For instance, we can model an
aggregation between the classes DegreeProgram and Course, as shown in the following diagram,
since a course is part of a degree program and a course can be shared among two or more degree
programs (e.g. an engineering degree could share a C programmin course with a computer science
degree)..
Course
DegreeProgram
id : String
name : String
* * name : String
However, this characteristic of shareable parts doesn't mean much, really, so the UML concept
of an aggregation doesn't have much semantics (the UML spec says: "Precise semantics of shared
aggregation varies by application area and modeler").
67
Part-Whole Associations
The multiplicity of an aggregation's association end at the whole side may be any number (*) because
a part may belong to, or shared among, any number of wholes.
68
Chapter 7. Implementing
Unidirectional Functional
Associations with Plain JavaScript
The two example apps that we have discussed in previous chapters, the minimal app and the validation
app, have been limited to managing the data of one object type only. A real app, however, has
to manage the data of several object types, which are typically related to each other in various
ways. In particular, there may be associations and subtype (inheritance) relationships between object
types. Handling associations and subtype relationships are advanced issues in software application
engineering. They are often not sufficiently discussed in software development text books and not
well supported by application development frameworks. In this part of the tutorial, we show how to
deal with unidirectional associations, while bidirectional associations and subtype relationships are
covered in parts 4 and 5.
We adopt the approach of model-based development, which provides a general methodology for
engineering all kinds of artifacts, including data management apps. For being able to understand this
tutorial, you need to understand the underlying concepts and theory. Either you first read the theory
chapter on reference properties and associations, before you continue to read this tutorial chapter, or
you start reading this tutorial chapter and consult the theory chapter only on demand, e.g., when you
stumble upon a term that you don't know.
1. how to derive a JavaScript data model from an association-free information design model with
single-valued reference properties representing unidirectional functional associations,
2. how to encode the JavaScript data model in the form of JavaScript model classes,
3. how to write the view and controller code based on the model code.
69
Implementing Unidirectional
Functional Associations
with Plain JavaScript
}
}
Notice that, for flexibility, the constructor parameter slots may contain either a publisher slot
representing an (internal) JavaScript object reference or a publisherIdRef slot representing an
(external) ID reference (or foreign key). We handle the resulting ambiguity in the property setter by
checking the type of the argument as shown in the following code fragment:
Notice that the name of a publisher is used as an ID reference (or froeign key), since this is the standard
identifier (or primary key) of the Book class.
Publisher Book
«stdid» name : String «stdid» isbn[1] : String
adress : String title[1] : String
year[1] : Integer
«invariant» publisher[0..1] : Publisher
{year >= 1454
and year <= nextYear}
«invariant»
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
How to make this design model has been discussed in the previous chapter (about unidirectional
associations). We now show how to derive a JavaScript data model from this design model in three
steps.
1. Create a check operation for each non-derived property in order to have a central place for
implementing property constraints. For a standard identifier property (such as Book::isbn),
three check operations are needed:
a. A basic check operationm, such as checkIsbn, for checking all syntactic constraints, but not
the mandatory value and the uniqueness constraints.
b. A standard ID check operation, such as checkIsbnAsId, for checking the mandatory value
and uniqueness constraints that are required for an identifier (or primary key) attribute.
2. Create a setter operation for each non-derived single-valued property. In the setter, the
corresponding check operation is invoked and the property is only set, if the check does not detect
any constraint violation.
70
Implementing Unidirectional
Functional Associations
with Plain JavaScript
3. Create add and remove operations for each non-derived multi-valued property.
This leads to the following JavaScript data model class Book, where the class-level ('static') methods
are shown underlined:
Book
«stdid» isbn[1] : String Book
title[1] : String «stdid» isbn[1] : String
year[1] : Integer title[1] : String
publisher[0..1] : Publisher year[1] : Integer
publisher[0..1] : Publisher
checkIsbn(in isbn : String) : ConstraintViolation
checkIsbnAsId(in isbn : String) : ConstraintViolation
checkIsbnAsIdRef(in isbn : String) : ConstraintViolation
setIsbn(in isbn : String) : void
checkTitle(in title : String) : ConstraintViolation
setTitle(in title : String) : void
checkYear(in year : Integer) : ConstraintViolation
setYear(in year : Integer) : void
checkPublisher(in publisher : Publisher) : ConstraintViolation
setPublisher(in publisher : Publisher) : void
We have to perform a similar transformation also for the class Publisher. This gives us the complete
JavaScript data model derived from the above association-free model, as depicted in the following
class diagram.
Publisher Book
«stdid» name : String «stdid» isbn[1] : String
adress : String title[1] : String
checkName(in n : String) : ConstraintViolation year[1] : Integer
checkNameAsId(in n : String) : ConstraintViolation publisher[0..1]
checkNameAsIdRef(in n : String) : ConstraintViolation checkIsbn(in isbn : String) : ConstraintViolation
setName(in n : String) : void checkIsbnAsId(in isbn : String) : ConstraintViolation
checkAddress(in a : String) : ConstraintViolation checkIsbnAsIdRef(in isbn : String) : ConstraintViolation
setAddress(in a : String) : void setIsbn(in isbn : String) : void
checkTitle(in title : String) : ConstraintViolation
setTitle(in title : String) : void
checkYear(in year : Integer) : ConstraintViolation
setYear(in year : Integer) : void
«invariant»
checkPublisher(in publisher) : ConstraintViolation
{year >= 1454 and
setPublisher(in publisher) : void
year <= nextYear()}
«invariant»
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
New issues
Compared to the single-class app discussed in Part 2 (Validation Tutorial [validation-tutorial.html]),
we have to deal with a number of new technical issues:
1. In the model code we now have to take care of reference properties that require
b. conversion between (internal) object references and (external) ID references in the serialization
and de-serialization procedures.
71
Implementing Unidirectional
Functional Associations
with Plain JavaScript
2. In the user interface "(vie"w) code we now have to take care of
a. showing information about associated objects in the list objects use case;
b. allowing to select an associated object from a list of all existing instances of the target class in
the create object and update object use cases.
The last issue, allowing to select an associated object from a list of all existing instances of some class,
can be solved with the help of an HTML select form element.
The JavaScript data model can be directly encoded for getting the code of the model layer of our
JavaScript frontend app.
Summary
1. Encode each model class as a JavaScript constructor function.
2. Encode the property checks in the form of class-level ('static') methods. Take care that all constraints
of a property as specified in the JavaScript data model are properly encoded in the property checks.
3. Encode the property setters as (instance-level) methods. In each setter, the corresponding property
check is invoked and the property is only set, if the check does not detect any constraint violation.
4. Encode the add/remove operations as (instance-level) methods that invoke the corresponding
property checks.
In the constructor body, we first assign default values to all properties. These values will be used when
the constuctor is invoked as a default constructor, that is, without any argument. If the constructor
is invoked with arguments, the default values may be overwritten by calling the setter methods for
all properties.
For instance, the Publisher class from the JavaScript data model is encoded in the following way:
72
Implementing Unidirectional
Functional Associations
with Plain JavaScript
Since the setters may throw constraint violation exceptions, the constructor function, and any setter,
should be called in a try-catch block where the catch clause takes care of logging suitable error
messages.
For each model class C, we define a class-level property C.instances representing the collection
of all C instances managed by the application in the form of a JSON table (a map of records): This
property is initially set to {}. For instance, in the case of the model class Publisher, we define:
Publisher.instances = {};
Notice that the Book constructor can be invoked either with an object reference slots.publisher
or with an ID reference slots.publisherIdRef.
For instance, for the checkName operation we obtain the following code:
Notice that, since the name attribute is the standard ID attribute of Publisher, we only check
syntactic constraints in checkName, and check the mandatory value and uniqueness constraints in
checkNameAsId, which invokes checkName:
73
Implementing Unidirectional
Functional Associations
with Plain JavaScript
constraintViolation = new UniquenessConstraintViolation(
"There is already a publisher record with this name!");
} else {
constraintViolation = new NoConstraintViolation();
}
}
return constraintViolation;
};
Since for any standard ID attribute, we may have to deal with ID references (foreign keys) in other
classes, we need to provide a further check function, called checkNameAsIdRef, for checking the
referential integrity constraint, as illustrated in the following example:
74
Implementing Unidirectional
Functional Associations
with Plain JavaScript
// create the new publisher reference
this.publisher = Publisher.instances[ publisherIdRef];
} else {
throw constraintViolation;
}
};
2. dropping from all books published by the deleted publisher the reference to the deleted publisher.
We go for the second option. This is shown in the following code of the Publisher.destroy
method where for all concerned book objects book the property book.publisher is cleared:
Book.prototype.convertObj2Row = function () {
var bookRow = util.cloneObject(this), keys=[];
if (this.publisher) {
// create publisher ID reference
bookRow.publisherIdRef = this.publisher.name;
}
return bookRow;
};
75
Implementing Unidirectional
Functional Associations
with Plain JavaScript
Book.convertRow2Obj = function (bookRow) {
var book={}, persKey="";
var publisher = Publisher.instances[bookRow.publisherIdRef];
// replace the publisher ID reference with object reference
delete bookRow.publisherIdRef;
bookRow.publisher = publisher;
try {
book = new Book( bookRow);
} catch (e) {
console.log( e.name + " while deserializing a book row: " + e.message);
}
return book;
};
pl.ctrl.books.manage = {
initialize: function () {
Publisher.loadAll();
Book.loadAll();
pl.view.books.manage.setUpUserInterface();
}
};
The initialize method for managing book data loads the publisher table and the book table since
the book data management UI needs to provide selection list for both object types. Then the menu for
book data managemetn options is set up by the setUpUserInterface method.
pl.view.books.list = {
setupUserInterface: function () {
var tableBodyEl = document.querySelector(
"section#Book-R>table>tbody");
var keys = Object.keys( Book.instances);
var row=null, listEl=null, book=null;
tableBodyEl.innerHTML = "";
for (var i=0; i < keys.length; i++) {
book = Book.instances[keys[i]];
76
Implementing Unidirectional
Functional Associations
with Plain JavaScript
row = tableBodyEl.insertRow(-1);
row.insertCell(-1).textContent = book.isbn;
row.insertCell(-1).textContent = book.title;
row.insertCell(-1).textContent = book.year;
row.insertCell(-1).textContent =
book.publisher ? book.publisher.name : "";
}
document.getElementById("Book-M").style.display = "none";
document.getElementById("Book-R").style.display = "block";
}
};
For a multi-valued reference property, the table cell would have to be filled with a list of all associated
objects referenced by the property.
pl.view.books.create = {
setupUserInterface: function () {
var formEl = document.querySelector("section#Book-C > form"),
publisherSelectEl = formEl.selectPublisher,
submitButton = formEl.commit;
// define event handlers for responsive validation
formEl.isbn.addEventListener("input", function () {
formEl.isbn.setCustomValidity(
Book.checkIsbnAsId( formEl.isbn.value).message);
});
// set up the publisher selection list
util.fillSelectWithOptions( publisherSelectEl, Publisher.instances, "name");
// define event handler for submitButton click events
submitButton.addEventListener("click", this.handleSubmitButtonClickEvent);
// define event handler for neutralizing the submit event
formEl.addEventListener( 'submit', function (e) {
e.preventDefault();
formEl.reset();
});
// replace the manageBooks form with the Book-C form
document.getElementById("Book-M").style.display = "none";
document.getElementById("Book-C").style.display = "block";
formEl.reset();
},
handleSubmitButtonClickEvent: function () {
...
}
};
When the user clicks the submit button, all form control values, including the value of the select
control, are copied to a slots list, which is used as the argument for invoking the add method after
all form fields have been checked for validity, as shown in the following program listing:
handleSubmitButtonClickEvent: function () {
var formEl = document.querySelector("section#Book-C > form");
77
Implementing Unidirectional
Functional Associations
with Plain JavaScript
var slots = {
isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value,
publisherIdRef: formEl.selectPublisher.value
};
// check input fields and show constraint violation error messages
formEl.isbn.setCustomValidity( Book.checkIsbnAsId( slots.isbn).message);
/* ... (do the same with title and year) */
// save the input data only if all of the form fields are valid
if (formEl.checkValidity()) {
Book.add( slots);
}
}
The setupUserInterface code for the update book use case is similar.
78
Chapter 8. Implementing
Unidirectional Non-Functional
Associations with Plain JavaScript
A unidirectional non-functional association is either one-to-many or many-to-many. In both cases such
an association is represented, or implemented, with the help of a multi-valued reference property.
1. how to derive a JavaScript data model from an association-free information design model with
multi-valued reference properties representing unidirectional non-functional associations,
2. how to encode the JavaScript data model in the form of JavaScript model classes,
3. how to write the view and controller code based on the model code.
79
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
} else { // map of object references
keys = Object.keys( a);
for (i=0; i < keys.length; i++) {
this.addAuthor( a[keys[i]]);
}
}
};
A set-valued reference property can be implemented as a property with either an array, or a map, of
object references, as its value. We prefer using maps for implementing set-valued reference properties
since they guarantee that each element is unique, while with an array we would have to prevent
duplicate elements. Also, an element of a map can be easily deleted (with the help of the delete
operator), while this requires more effort in the case of an array. But for implementing list-valued
reference properties, we need to use arrays.
We use the standard identifiers of the referenced objects as keys. If the standard identifier is an integer,
we must take special care in converting ID values to strings for using them as keys.
We now show how to derive a JavaScript data model from this design model in three steps.
1. Create a check operation for each non-derived property in order to have a central place
for implementing property constraints. For any reference property, no matter if single-valued
(like Book::publisher) or multi-valued (like Book::authors), the check operation
(checkPublisher or checkAuthor) has to check the corresponding referential integrity
constraint, which requires that all references reference an existing object, and possibly also a
mandatory value constraint, if the property is mandatory.
2. Create a set operation for each non-derived single-valued property. In the setter, the corresponding
check operation is invoked and the property is only set, if the check does not detect any constraint
violation.
3. Create an add, a remove and a set operation for each non-derived multi-valued property.
In the case of the Book::authors property, we would create the operations addAuthor,
removeAuthor and setAuthors in the Book class rectangle.
This leads to the following JavaScript data model, where we only show the classes Book and Author,
while the missing class Publisher is the same as in Figure 7.1:
80
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
Book Author
«stdid» isbn[1] : String «stdid» personId[1] : PositiveInteger
title[1] : String name[1] : String
year[1] : PositiveInteger dateOfBirth[1] : Date
publisher[0..1] : Publisher dateOfDeath[0..1] : Date
authors[*] : Author checkPersonId(in pId : Integer) : ConstraintViolation
checkIsbn(in isbn : String) : ConstraintViolation checkPersonIdAsId(in pId : Integer) : ConstraintViolation
checkIsbnAsId(in isbn : String) : ConstraintViolation checkPersonIdAsIdRef(in pId : Integer) : ConstraintViolation
checkIsbnAsIdRef(in isbn : String) : ConstraintViolation setPersonId(in pId : Integer) : void
setIsbn(in isbn : String) : void checkName(in name : String) : ConstraintViolation
checkTitle(in title : String) : ConstraintViolation setName(in name : String) : void
setTitle(in title : String) : void checkDateOfBirth(in d : Date) : ConstraintViolation
checkYear(in year : Integer) : ConstraintViolation setDateOfBirth(in d : String) : void
setYear(in year : Integer) : void checkDateOfDeath(in d : String) : ConstraintViolation
checkPublisher(in p : Publisher) : ConstraintViolation setDateOfDeath(in d : String) : void
setPublisher(in p : Publisher) : void
checkAuthor(in author : Author) : ConstraintViolation
addAuthor(in author : Author) : void «invariant»
removeAuthor(in author : Author) : void «invariant»
{year >= 1454 and
setAuthors(in authors : Author) {dateOfDeath >= dateOfBirth}
year <= nextYear()}
«invariant»
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
Notice that, for simplicity, we do not include the code for all validation checks in the code of the
example app.
New issues
Compared to dealing with a unidirectional functional association, as discussed in the previous chapter,
we have to deal with the following new technical issues:
1. In the model code we now have to take care of multi-valued reference properties that require
a. implementing an add and a remove operation, as well as a setter for assigning a set of references
with the help of the add operation.
a. showing information about a set of associated objects in the list objects use case;
b. allowing to select a set of associated objects from a list of all existing instances of the target
class in the create object and update object use cases.
The last issue, allowing to select a set of associated objects from a list of all existing instances of
some class, can, in general, not be solved with the help of an HTML select multiple form
element because of usability problems. Whenever the set of selectable options is greater than a certain
threshold (defined by the number of options that can be seen on the screen without scrolling), the
HTML select multiple element is no longer usable, and an alternative multi-selection widget
has to be used.
81
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
82
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
this.addAuthor( a[keys[i]]);
}
}
};
2. dropping from all books (co-)authored by the deleted author the reference to the deleted author.
We go for the second option. This is shown in the following code of the Author.destroy method
where for all concerned book objects book the author reference book.authors[authorKey]
is dropped:
Book.prototype.convertObj2Row = function () {
var bookRow = util.cloneObject(this), keys=[];
// create authors ID references
bookRow.authorsIdRef = [];
keys = Object.keys( this.authors);
for (i=0; i < keys.length; i++) {
bookRow.authorsIdRef.push( parseInt( keys[i]));
}
if (this.publisher) {
// create publisher ID reference
bookRow.publisherIdRef = this.publisher.name;
}
return bookRow;
};
83
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
var book=null, authorKey="",
publisher = Publisher.instances[bookRow.publisherIdRef];
// replace the "authorsIdRef" array of ID references
// with a map "authors" of object references
bookRow.authors = {};
for (i=0; i < bookRow.authorsIdRef.length; i++) {
authorKey = bookRow.authorsIdRef[i].toString();
bookRow.authors[authorKey] = Author.instances[authorKey];
}
delete bookRow.authorsIdRef;
// replace publisher ID reference with object reference
delete bookRow.publisherIdRef;
bookRow.publisher = publisher;
try {
book = new Book( bookRow);
} catch (e) {
console.log( e.constructor.name +
" while deserializing a book row: " + e.message);
}
return book;
};
pl.view.books.list = {
setupUserInterface: function () {
var tableBodyEl = document.querySelector(
"section#Book-R>table>tbody");
var row=null, book=null, listEl=null,
keys = Object.keys( Book.instances);
tableBodyEl.innerHTML = ""; // drop old contents
for (i=0; i < keys.length; i++) {
book = Book.instances[keys[i]];
row = tableBodyEl.insertRow(-1);
row.insertCell(-1).textContent = book.isbn;
row.insertCell(-1).textContent = book.title;
row.insertCell(-1).textContent = book.year;
// create list of authors
listEl = util.createListFromMap(
book.authors, "name");
row.insertCell(-1).appendChild( listEl);
row.insertCell(-1).textContent =
book.publisher ? book.publisher.name : "";
}
document.getElementById("Book-M").style.display = "none";
document.getElementById("Book-R").style.display = "block";
}
};
84
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
The utility function util.createListFromMap has the following code:
The create book UI is set up by populating selection lists for selecting the authors and the publisher
with the help of a utility method fillSelectWithOptions as shown in the following program
listing:
85
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
pl.view.books.create = {
setupUserInterface: function () {
var formEl = document.querySelector("section#Book-C > form"),
publisherSelectEl = formEl.selectPublisher,
submitButton = formEl.commit;
// define event handlers for form field input events
...
// set up the (multiple) authors selection list
util.fillSelectWithOptions( authorsSelectEl,
Author.instances, "authorId", {displayProp:"name"});
// set up the publisher selection list
util.fillSelectWithOptions( publisherSelectEl,
Publisher.instances, "name");
...
},
handleSubmitButtonClickEvent: function () {
...
}
};
When the user clicks the submit button, all form control values, including the value of any single-
select control, are copied to a corresponding slots record variable, which is used as the argument
for invoking the add method after all form fields have been checked for validity. Before invoking
add, we first have to create (in the authorsIdRef slot) a list of author ID references from the
selected options of the multiple author selection list, as shown in the following program listing:
handleSubmitButtonClickEvent: function () {
var i=0,
formEl = document.querySelector("section#Book-C > form"),
selectedAuthorsOptions = formEl.selectAuthors.selectedOptions;
var slots = {
isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value,
authorsIdRef: [],
publisherIdRef: formEl.selectPublisher.value
};
// check all input fields
...
// save the input data only if all of the form fields are valid
if (formEl.checkValidity()) {
// construct the list of author ID references
for (i=0; i < selectedAuthorsOptions.length; i++) {
slots.authorsIdRef.push( selectedAuthorsOptions[i].value);
}
Book.add( slots);
}
}
86
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
as discussed in . In order to show how this widget can replace the multiple-selection list discussed in
the previous section, we use it now in the update book use case.
For allowing to maintain the set of authors associated with the currently edited book in the update
book use case, an association list widget as shown in the HTML code below, is populated with the
instances of the Author class.
The update book UI is set up (in the setupUserInterface procedure shown below) by populating
1. the selection list for selecting the book to be updated with the help of the utility method
fillSelectWithOptions, and
2. the selection list for updating the publisher with the help of the utility method
fillSelectWithOptions,
while the association list widget for updating the associated authors of the book is only populated (in
handleSubmitButtonClickEvent) when a book to be updated has been chosen.
pl.view.books.update = {
setupUserInterface: function () {
var formEl = document.querySelector("section#Book-U > form"),
bookSelectEl = formEl.selectBook,
publisherSelectEl = formEl.selectPublisher,
submitButton = formEl.commit;
// set up the book selection list
util.fillSelectWithOptions( bookSelectEl, Book.instances,
"isbn", {displayProp:"title"});
bookSelectEl.addEventListener("change", this.handleBookSelectChangeEvent);
... // define event handlers for title and year input events
87
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
// set up the associated publisher selection list
util.fillSelectWithOptions( publisherSelectEl, Publisher.instances, "name");
// define event handler for submitButton click events
submitButton.addEventListener("click", this.handleSubmitButtonClickEvent);
// define event handler for neutralizing the submit event and reseting the f
formEl.addEventListener( 'submit', function (e) {
var authorsSelWidget = document.querySelector(
"section#Book-U > form .MultiSelectionWidget");
e.preventDefault();
authorsSelWidget.innerHTML = "";
formEl.reset();
});
document.getElementById("Book-M").style.display = "none";
document.getElementById("Book-U").style.display = "block";
formEl.reset();
},
When a book to be updated has been chosen, the form input fields isbn, title and year, and the
select control for updating the publisher, are assigned corresponding values from the chosen book,
and the associated authors selection widget is set up:
handleBookSelectChangeEvent: function () {
var formEl = document.querySelector("section#Book-U > form"),
authorsSelWidget = formEl.querySelector(
".MultiSelectionWidget"),
key = formEl.selectBook.value,
book=null;
if (key !== "") {
book = Book.instances[key];
formEl.isbn.value = book.isbn;
formEl.title.value = book.title;
formEl.year.value = book.year;
// set up the associated authors selection widget
util.createMultiSelectionWidget( authorsSelWidget,
book.authors, Author.instances, "authorId", "name");
// assign associated publisher to index of select element
formEl.selectPublisher.selectedIndex =
(book.publisher) ? book.publisher.index : 0;
} else {
formEl.reset();
formEl.selectPublisher.selectedIndex = 0;
}
},
When the user, after updating some values, finally clicks the submit button, all form control
values, including the value of the single-select control for assigning a publisher, are copied to
corresponding slots in a slots record variable, which is used as the argument for invoking the
update method after all values have been checked for validity. Before invoking update, a list of ID
references to authors to be added, and another list of ID references to authors to be removed, is created
(in the authorsIdRefToAdd and authorsIdRefToRemove slots) from the updates that have
been recorded in the associated authors selection widget with the help of classList values, as
shown in the following program listing:
handleSubmitButtonClickEvent: function () {
var i=0, assocAuthorListItemEl=null,
authorsIdRefToAdd=[], authorsIdRefToRemove=[],
formEl = document.querySelector("section#Book-U > form"),
authorsSelWidget =
formEl.querySelector(".MultiSelectionWidget"),
88
Implementing Unidirectional
Non-Functional Associations
with Plain JavaScript
authorsAssocListEl = authorsSelWidget.firstElementChild;
var slots = { isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value,
publisherIdRef: formEl.selectPublisher.value
};
// commit the update only if all of the form fields values are valid
if (formEl.checkValidity()) {
// construct authorsIdRefToAdd and authorsIdRefToRemove
for (i=0; i < authorsAssocListEl.children.length; i++) {
assocAuthorListItemEl = authorsAssocListEl.children[i];
if (assocAuthorListItemEl.classList.contains("removed")) {
authorsIdRefToRemove.push(
assocAuthorListItemEl.getAttribute("data-value"));
}
if (assocAuthorListItemEl.classList.contains("added")) {
authorsIdRefToAdd.push(
assocAuthorListItemEl.getAttribute("data-value"));
}
}
// if the add/remove list is non-empty create a corresponding slot
if (authorsIdRefToRemove.length > 0) {
slots.authorsIdRefToRemove = authorsIdRefToRemove;
}
if (authorsIdRefToAdd.length > 0) {
slots.authorsIdRefToAdd = authorsIdRefToAdd;
}
Book.update( slots);
}
}
Points of Attention
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 map Book.instances). This approach only
works in the case of local data storage of smaller databases, say, with not more than 2 MB of data,
roughgly corresponding to 10 tables with an average population of 1000 rows, each having an average
size of 200 Bytes. When larger databases are to be managed, or when data is stored remotely, 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.
We have still included the repetitive code structures (called boilerplate code) 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.
89
Chapter 9. Bidirectional Associations
A bidirectional association is an association that is represented as a pair of mutually inverse reference
properties.
The model shown in Figure 9.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.
Author
published Book authored
Publisher Books Books «stdid» personId[1] : Integer
«stdid» isbn : String name[1] : String
«stdid» name : String
title : String gender[1] : GenderEL
address : String 0..1 * * Authorship *
year : Integer dateOfBirth[1] : Date
dateOfDeath[0..1] : Date
«invariant»
{year >= 1454 and «enumeration»
year <= nextYear} GenderEL «invariant»
FEMALE {dateOfDeath >= dateOfBirth}
«invariant» MALE
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
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 committe"e,
"To"m), then the budget committee is the committee chaired by the club member Tom, expressed
by the inverse tuple "(To"m, "budget committe"e). 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:
Committee ClubMember
name : String chairedCommittee[0..1] : Committee {inverse of chair}
chair : ClubMember {inverse of chairedCommittee} coChairedCommittee[0..1] : Committee
90
Bidirectional Associations
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:
Committee ClubMember
name : String /chairedCommittee[0..1] : Committee {inverse of chair}
chair : ClubMember {inverse of chairedCommittee} coChairedCommittee[0..1] : Committee
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).
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;
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 9.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 ).
91
Bidirectional Associations
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.
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.
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.
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
92
Bidirectional Associations
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.
chairedCommittee chair
Committee
ClubMember
name : String
0..1 1
Committee ClubMember
name : String /chairedCommittee[0..1] : Committee {inverse of chair}
chair : ClubMember {inverse of chairedCommittee} coChairedCommittee[0..1] : Committee
authored authors
Book Books Person
«stdid» isbn : String «stdid» personID : Integer
title : String * Authorship * name : String
Book Person
«stdid» isbn[1] : String «stdid» personID[1] : PositiveInteger
title[1] : String name[1] : String
authors[*] : Person {inverse of authoredBooks} /authoredBooks[*] : Book {inverse of authors}
93
Bidirectional Associations
Author
Publisher Book «stdid» personId[1] : PositiveInteger
«stdid» name : String «stdid» isbn[1] : String name[1] : String
adress : String title[1] : String gender[1] : GenderEL
/publishedBooks : Book {inverse of publisher} year[1] : Integer dateOfBirth[1] : Date
publisher[0..1] : Publisher dateOfDeath[0..1] : Date
authors[*] : Author /authoredBooks[*] : Book {inverse of authors}
«invariant»
{year >= 1454
and year <= nextYear} «enumeration»
«invariant» GenderEL
{dateOfDeath >= dateOfBirth} FEMALE
«invariant» MALE
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
94
Chapter 10. Implementing
Bidirectional Associations with Plain
JavaScript
In this chapter of our tutorial, we show
1. how to derive a JavaScript data model from an association-free information design model with
derived inverse reference properties,
2. how to encode the JavaScript data model in the form of JavaScript model classes,
3. how to write the view and controller code based on the model code.
Author
Publisher Book «stdid» personId[1] : PositiveInteger
«stdid» name : String «stdid» isbn[1] : String name[1] : String
adress : String title[1] : String gender[1] : GenderEL
/publishedBooks : Book {inverse of publisher} year[1] : Integer dateOfBirth[1] : Date
publisher[0..1] : Publisher dateOfDeath[0..1] : Date
authors[*] : Author /authoredBooks[*] : Book {inverse of authors}
«invariant»
{year >= 1454
and year <= nextYear} «enumeration»
«invariant» GenderEL
{dateOfDeath >= dateOfBirth} FEMALE
«invariant» MALE
{isbn must be a 10-digit string
or a 9-digit string followed by "X"}
Notice that there are two derived inverse reference properties: Publisher::/publishedBooks
and Author::/authoredBooks. We now show how to derive a JavaScript data model from this
design model in three steps.
1. Create a check operation for each non-derived property. This step has been discussed in detail in
the previous parts of the tutorial (about data validation and about unidirectional associations).
2. Create a set operation for each non-derived single-valued property. In the setter, the corresponding
check operation is invoked and the property is only set, if the check does not detect any constraint
violation.
3. Create an add, a remove and a set operation for each non-derived multi-valued property.
This leads to the following JavaScript data model classes Publisher and Author. Notice that
we don't show the model class Book, since it is the same as in the data model for the unidirectional
association app discussed in the previous Part of our tutorial.
95
Implementing Bidirectional
Associations with Plain JavaScript
Publisher
«stdid» name[1] : NonEmptyString
adress[1] : NonEmptyString
/publishedBooks[*] : Book {inverse of publisher}
checkName(in n : String) : ConstraintViolation
checkNameAsId(in n : String) : ConstraintViolation
checkNameAsIdRef(in n : String) : ConstraintViolation
setName(in n : String)
checkAddress(in a : String) : ConstraintViolation
setAddress(in a : String)
Author
«stdid» personId[1] : PositiveInteger
name[1] : NonEmptyString
gender[1] : GenderEL
dateOfBirth[1] : Date
dateOfDeath[0..1] : Date
/authoredBooks[*] : Book {inverse of authors}
checkPersonId(in pId : Integer) : ConstraintViolation
checkPersonIdAsId(in pId : Integer) : ConstraintViolation
checkPersonIdAsIdRef(in pId : Integer) : ConstraintViolation
setPersonId(in pId : Integer)
checkName(in name : String) : ConstraintViolation
setName(in name : String)
checkGender(in d : String) : ConstraintViolation
setGender(in g : GenderEL)
checkDateOfBirth(in d : Date) : ConstraintViolation
setDateOfBirth(in d : String)
checkDateOfDeath(in d : String) : ConstraintViolation
setDateOfDeath(in d : String)
«enumeration»
GenderEL
«invariant»
FEMALE {dateOfDeath >= dateOfBirth}
MALE
The JavaScript data model can be directly encoded for getting the code of the model layer of our
JavaScript frontend app.
New issues
Compared to the unidirectional association app discussed in a previous tutorial, we have to deal with
a number of new technical issues:
1. In the model code you now have to take care of maintaining the derived inverse reference properties
by maintaining the derived (sets of) inverse references that form the values of a derived inverse
reference property. This requires in particular that
96
Implementing Bidirectional
Associations with Plain JavaScript
b. whenever the value of an optional single-valued master reference property is unset (e.g. by
assigning null to b.publisher for a Book instance b), the inverse reference has to be
removed from the corresponding value of the derived inverse reference property (such as
removing b from p.publishedBooks), if the derived inverse reference property is multi-
valued, otherwise the corresponding value of the derived inverse reference property has to be
unset or updated;
c. whenever a reference is added to the value of a multi-valued master reference property with
the help of an add method (such as adding a reference to an Author instance a to b.authors
for a Book instance b), an inverse reference has to be assigned or added to the corresponding
value of the derived inverse reference property (such as adding b to a.authoredBooks);
d. whenever a reference is removed from the value of a multi-valued master reference property
with the help of a remove method (such as removing a reference to an Author instance
a from b.authors for a Book instance b), the inverse reference has to be removed from
the corresponding value of the derived inverse reference property (such as removing b from
a.authoredBooks), if the derived inverse reference property is multi-valued, otherwise the
corresponding value of the derived inverse reference property has to be unset or updated;
e. whenever an object with a single reference or with multiple references as the value of a
master reference property is destroyed (e.g., when a Book instance b with a single reference
b.publisher to a Publisher instance p is destroyed), the derived inverse refences have
to be removed first (e.g., by removing b from p.publishedBooks).
Notice that when a new object is created with a single reference or with multiple references as
the value of a master reference property (e.g., a new Book instance b with a single reference
b.publisher), its setter or add method will be invoked and will take care of creating the derived
inverse references.
2. In the UI code we can now exploit the inverse reference properties for more efficiently creating a
list of inversely associated objects in the list objects use case. For instance, we can more efficiently
create a list of all published books for each publisher. However, we do not allow updating the set of
inversely associated objects in the update object use case (e.g. updating the set of published books
in the update publisher use case). Rather, such an update has to be done via updating the master
objects (in our example, the books) concerned.
Summary
1. Encode each model class as a JavaScript constructor function.
2. Encode the property checks in the form of class-level ('static') methods. Take care that all constraints
of a property as specified in the JavaScript data model are properly encoded in the property checks.
3. Encode the property setters as (instance-level) methods. In each setter, the corresponding property
check is invoked and the property is only set, if the check does not detect any constraint violation. If
the property is the inverse of a derived reference property (representing a bi-directional association),
make sure that the setter also assigns (or adds) corresponding references to (the value set of) the
inverse property.
4. Encode the add/remove operations as (instance-level) methods that invoke the corresponding
property checks. If the multi-valued reference property is the inverse of a derived reference property
(representing a bi-directional association), make sure that both the add and the remove operation
also assign/add/remove corresponding references to/from (the value set of) the inverse property.
These six steps are discussed in more detail in the following sections.
97
Implementing Bidirectional
Associations with Plain JavaScript
Notice that we have added the (derived) multi-valued reference property publishedBooks, but
we do not assign it in the constructor function because it will be assigned when the inverse reference
property Book::publisher will be assigned.
98
Implementing Bidirectional
Associations with Plain JavaScript
For instance, for the multi-valued reference property Book::authors that is coupled to the
derived inverse reference propertiy Author:authoredBooks for implementing the bidirectional
authorship association between Book and Author, the addAuthor method is encoded in the
following way:
Book.prototype.addAuthor = function( a) {
var constraintViolation=null, authorIdRef=0, authorIdRefStr="";
// an author can be given as ...
if (typeof( a) !== "object") { // an ID reference or
authorIdRef = parseInt( a);
} else { // an object reference
authorIdRef = a.authorId;
}
constraintViolation = Book.checkAuthor( authorIdRef);
if (authorIdRef && constraintViolation instanceof NoConstraintViolation) {
authorIdRefStr = String( authorIdRef);
// add the new author reference
this.authors[ authorIdRefStr] = Author.instances[ authorIdRefStr];
// automatically add the derived inverse reference
this.authors[ authorIdRefStr].authoredBooks[ this.isbn] = this;
}
};
Book.prototype.removeAuthor = function( a) {
var constraintViolation=null, authorIdRef=0, authorIdRefStr="";
// an author can be given as an ID reference or an object reference
if (typeof(a) !== "object") {
authorIdRef = parseInt( a);
} else {
authorIdRef = a.authorId;
}
constraintViolation = Book.checkAuthor( authorIdRef);
if (authorIdRef && constraintViolation instanceof NoConstraintViolation) {
authorIdRefStr = String( authorIdRef);
// automatically delete the derived inverse reference
delete this.authors[ authorIdRefStr].authoredBooks[ this.isbn];
// delete the author reference
delete this.authors[ authorIdRefStr];
}
};
99
Implementing Bidirectional
Associations with Plain JavaScript
pl.view.publishers.list = {
setupUserInterface: function () {
var tableBodyEl = document.querySelector("div#listPublishers>table>tbody");
var pKeys = Object.keys( Publisher.instances);
var row=null, publisher=null, listEl=null;
tableBodyEl.innerHTML = "";
for (var i=0; i < pKeys.length; i++) {
publisher = Publisher.instances[pKeys[i]];
row = tableBodyEl.insertRow(-1);
row.insertCell(-1).textContent = publisher.name;
row.insertCell(-1).textContent = publisher.address;
// create list of books published by this publisher
listEl = util.createListFromAssocArray( publisher.publishedBooks, "title")
row.insertCell(-1).appendChild( listEl);
}
document.getElementById("managePublishers").style.display = "none";
document.getElementById("listPublishers").style.display = "block";
}
};
100
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.
Table of Contents
11. Subtyping and Inheritance ...................................................................................... 103
Introducing Subtypes by Specialization .................................................................. 103
Introducing Supertypes by Generalization ............................................................... 103
Intension versus Extension ................................................................................... 105
Type Hierarchies ................................................................................................ 105
The Class Hierarchy Merge Design Pattern ............................................................ 106
Subtyping and Inheritance in Computational Languages ............................................ 107
Subtyping and Inheritance with OOP Classes .................................................. 108
Subtyping and Inheritance with Database Tables .............................................. 108
12. Subtyping in a Plain JavaScript Frontend App ............................................................ 111
Constructor-Based Subtyping in JavaScript ............................................................. 112
Case Study 1: Eliminating a Class Hierarchy .......................................................... 114
Make the JavaScript data model .................................................................... 114
New issues ................................................................................................ 115
Encode the model classes of the JavaScript data model ..................................... 116
Write the View and Controller Code .............................................................. 119
Case Study 2: Implementing a Class Hierarchy ........................................................ 120
Make the JavaScript data model .................................................................... 121
Make the JSON table model ........................................................................ 121
New issues ................................................................................................ 122
Encode the model classes of the JavaScript data model ..................................... 123
102
Chapter 11. 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.
Figure 11.1. The object type Book is specialized by two subtypes: TextBook
and Biography
Book
isbn : String
title : String
year : Integer
{disjoint}
TextBook Biography
subjectArea : String about : String
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.
103
Subtyping and Inheritance
Figure 11.2. The object types Employee and Author share several attributes
Employee
«stdid» employeeNo : Integer
name : String
dateOfBirth : Date
salary : Decimal
*
0..1
Author
published Book authored
Publisher Books Books «stdid» authorId[1] : Integer
«stdid» isbn : String name[1] : String
«stdid» name : String
title : String dateOfBirth[1] : Date
address : String 0..1 * * *
year : Integer dateOfDeath[0..1] : Date
biography[1] : Text
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 11.3. The object types Employee and Author have been generalized by
adding the common supertype Person
Person
Employee «stdid» personId : Integer
employeeNo : Integer {unique} name : String
salary : Decimal dateOfBirth : Date
dateOfDeath : Date
*
0..1
published Book authored
Publisher Books Books
«stdid» isbn : String Author
«stdid» name : String
title : String biography[1] : Text
address : String 0..1 * * *
year : Integer
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 11.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.
104
Subtyping and Inheritance
Figure 11.4. The complete class model containing two inheritance hierarchies
Person
Employee «stdid» personId[1] : Integer
employeeNo : Integer {unique} name[1] : String
salary : Decimal dateOfBirth[1] : Date
dateOfDeath[0..1] : Date
*
0..1
published Book authored
Publisher Books Books
«stdid» isbn : String Author
«stdid» name : String
title : String biography : Text
address : String 0..1 * * *
year : Integer
managed
Publisher 0..1
{disjoint}
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 [http://en.wikipedia.org/wiki/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).
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 11.5 below, the class Vehicle is the root
of a single-inheritance hierarchy, while Figure 11.6 shows an example of a multiple-inheritance
hierarchy, due to the fact that AmphibianVehicle has two direct superclasses: LandVehicle
and WaterVehicle.
105
Subtyping and Inheritance
Vehicle
LandVehicle WaterVehicle
{disjoint} {disjoint}
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.
LandVehicle WaterVehicle
{disjoint} {disjoint}
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 11.6 above, it is {incomplete, overlapping}.
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.
106
Subtyping and Inheritance
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.
In the case of our example, the result of this design refactoring is shown in Figure 11.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 11.7. The design model resulting from applying the Class Hierarchy
Merge design pattern
Book
isbn[1] : String «enumeration»
title[1] : String BookTypeEL
year[1] : Integer
TextBook
subtype[0..1] : BookTypeEL {frozen}
Biography
subjectArea[0..1] : String
about[0..1] : String
«invariant»
{(type="TextBook" IFF NOT subjectArea=undefined) AND
(type="Biography" IFF NOT about=undefined)}
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
107
Subtyping and Inheritance
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.
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).
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 [http://en.wikipedia.org/wiki/Hibernate_%28Java%29] (or
any other JPA [http://en.wikibooks.org/wiki/Java_Persistence/What_is_JPA%3F] Provider) or the
Active Record [http://guides.rubyonrails.org/association_basics.html] approach of the Rails [http://
rubyonrails.org/] framework.
There are essentially two alternative approaches how to represent a class hierarchy with relational
tables:
108
Subtyping and Inheritance
Notice that the Single Table Inheritance approach is closely related to the Class Hierarchy Merge
design pattern discussed in 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
11.1 above. The second example is the class hierarchy of the Person roles Employee, Manager
and Author, shown in the class diagram in Figure 11.8 below.
Person
«stdid» personId : Integer
name : String
Employee Author
empNo : Integer {unique} biography : String
Manager
department : String
Figure 11.9. An SQL table model with a single table representing the Book class
hierarchy
«table»
Book
«pkey» isbn[1] : VARCHAR
title[1] : VARCHAR
year[1] : INTEGER
subtype[1] : VARCHAR ["TextBook","Biography"]
subjectArea[0..1] : VARCHAR
about[0..1] : VARCHAR
«invariant»
{(type="TextBook" IFF notNULL(subjectArea)) AND
(type="Biography" IFF notNULL(about))}
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.
109
Subtyping and Inheritance
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 11.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 11.10 below.
Figure 11.10. An SQL table model with a single table representing the Person
roles hierarchy
«table»
Person
«pkey» personId[1] : INTEGER
name[1] : VARCHAR
subtype[0..1] : VARCHAR ["Employee","Manager","Author"]
empNo[0..1] : INTEGER {unique}
biography[0..1] : VARCHAR
department[0..1] : VARCHAR
«invariant»
{(type="Employee" IFF notNULL(empNo)) AND
(type="Manager" IFF notNULL(empNo) && notNULL(department)) AND
(type="Author" IFF notNULL(biography))}
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.
Figure 11.11. An SQL table model with the table Person as the root of a table
hierarchy
«table» «table»
personId Employee Author
«pkey» personId : INTEGER «pkey» personId : INTEGER personId
empNo : INTEGER {unique} biography : VARCHAR
<<fkey>>
«table»
Manager
personId «pkey» personId : INTEGER
department : VARCHAR
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.
110
Chapter 12. Subtyping in a Plain
JavaScript Frontend App
Whenever an app has to manage the data of a larger number of object types, there may be various
category (inheritance) relationships between some of the object types. Handling category relationships
is an advanced issue in software application engineering. It is often not well supported by application
development frameworks.
Person
Employee «stdid» personId[1] : Integer
employeeNo : Integer {unique} name[1] : String
salary : Decimal dateOfBirth[1] : Date
dateOfDeath[0..1] : Date
*
0..1
published Book authored
Publisher Books Books
«stdid» isbn : String Author
«stdid» name : String
title : String biography : Text
address : String 0..1 * * *
year : Integer
managed
Publisher 0..1
{disjoint}
In this chapter of our tutorial, we first explain the general approach to constructor-based subtyping
in JavaScript before presenting two case studies based on fragments of the information model of our
running example, the Public Library app, shown above:
1. In the first case study, we consider the single-level class hierarchy with root Book shown in Figure
12.2 below (which is an incomplete disjoint segmentation) . We use the Class Hierarchy Merge
design pattern (discussed in Section ) for refactoring this class hierarchy to a single class that is
mapped to a persistent database table stored with JavaScript's Local Storage.
2. In the second case study, we consider the multi-level class hierarchy consisting of the Person
roles Employee, Manager and Author, shown in Figure 12.2 below. We use the Joined Tables
Inheritance approach for mapping this class hierarchy to a set of database tables that are related
with each other via foreign key dependencies.
1. how to derive a JavaScript data model, and a corresponding JSON table model, from the class
hierarchiy (representing an information design model),
2. how to encode the JavaScript data model in the form of JavaScript model classes,
3. how to write the view and controller code based on the model code.
111
Subtyping in a Plain
JavaScript Frontend App
Figure 12.2. The object type Book as the root of a disjoint segmentation
Book
isbn : String
title : String
year : Integer
{disjoint}
TextBook Biography
subjectArea : String about : String
Person
«stdid» personId : Integer
name : String
Employee Author
empNo : Integer {unique} biography : String
Manager
department : String
As we have explained in Part 1 [minimal-tutorial.html] of this tutorial, classes can be defined in two
alternative ways: constructor-based and factory-based. Both approaches have their own way of
implementing inheritance. In this part of our tutorial we only discuss subtyping and inheritance for
constructor-based classes, while in our JavaScript Frontend Apps [complete-tutorial.html] book we
also discuss subtyping and inheritance for factory-based classes
For defining a category in a constructor-based class hierarchy, we use a 3-part code pattern
recommended by Mozilla in their JavaScript Guide [https://developer.mozilla.org/en-US/docs/Web/
JavaScript/Guide/Details_of_the_Object_Model]. We use the example shown in 12.4 below. We first
define (the constructor function of) a base class Person:
112
Subtyping in a Plain
JavaScript Frontend App
Person
firstName : String
lastName : String
Student
studNo : Integer
By invoking the supertype constructor with Person.call( this, ...) for the new object
created (referenced by this) as an instance of the category Student, we achieve that the property
slots created in the supertype constructor (firstName and lastName) are also created for the
category instance, along the entire chain of supertypes within a given class hierarchy. In this way we
set up a property inheritance mechanism that makes sure that the own properties defined for an object
on creation include the own properties defined by the superclass contructors.
In addition to this property inheritance mechanism, we set up a mechanism for method inheritance
via the constructor's prototype property. We assign a new object created from the supertype's
prototype object to the prototype property of the category constructor and reset the prototype's
constructor property:
By assigning an empty supertype instance to the prototype property of the category constructor, we
achieve that the methods defined in, and inherited by, the supertype are also available for objects
instantiating the category. This mechanism of chaining the prototypes takes care of method inheritance.
Notice that setting Student.prototype to Object.create( Person.prototype),
which creates a new object with its prototype set to Person.prototype and without any own
properties, is preferable over setting it to new Person(), which was the way to achieve the same
in the time before ECMAScript 5.
Finally, we define the additional methods of the subclass as method slots of its prototype object:
When an object is constructed, the built-in reference property __proto__ (with a double underscore
prefix and suffix) is set to the value of the constructor's prototype property. For instance,
after creating a new object with f = new Foo(), it holds that f.__proto__ is equal to
Foo.prototype. Consequently, changes to the slots of Foo.prototype affect all objects that
were created with new Foo(). While every object has a __proto__ reference property (except
Object), every function has a prototype reference property.
113
Subtyping in a Plain
JavaScript Frontend App
Object
__proto__ : Object
hasOwnProperty(in p : String) : Boolean
isPrototypeOf(in obj : Object) : Boolean
create(in proto : Object, in prop-def : Object) : Object
*
defineProperties(in obj : Object, in prop-def : Object)
keys(in obj : Object) : Array
getPrototypeOf(in obj : Object) : Object
Function
name[0..1] : String 0..1
prototype[1] : Object
length[1] : Integer
apply(in thisObj : Object, in arguments : Array) constructor
call(in thisObj : Object, in arg1, in arg2, in ...)
bind(in thisObj : Object, in arg1, in arg2, in ...)
Figure 12.6. The simplified information design model obtained by applying the
Class Hierarchy Merge design pattern
Book
isbn[1] : String «enumeration»
title[1] : String BookTypeEL
year[1] : Integer
TextBook
subtype[0..1] : BookTypeEL {frozen}
Biography
subjectArea[0..1] : String
about[0..1] : String
«invariant»
{(type="TextBook" IFF NOT subjectArea=undefined) AND
(type="Biography" IFF NOT about=undefined)}
We can now derive a JavaScript data model from this design model.
1. Turn the design model's enumeration type, which contains an enumeration literal for each segment
subclass, into a corresponding JavaScript map where the enumeration literals are (by convention
uppercase) keys associated with an integer value that enumerates the literal. For instance, for the
first enumeration literal "TextBook" we get the key-value pair TEXTBOOK=1.
2. Turn the plaform-independent datatypes (defined as the ranges of attributes) into JavaScript
datatypes. This includes the case of enumeration-valued attributes, such as category, which are
turned into numeric attributes restricted to the enumeration integers of the underlying enumeration
type.
3. Add property checks and setters, as described in Part 2 of this tutorial. The checkSubtype and
setCategory methods, as well as the checks and setters of the segment properties need special
114
Subtyping in a Plain
JavaScript Frontend App
consideration according to their implied sematnics. In particular, a segment property's check and
setter methods must ensure that the property can only be assigned if the category attribute has
a value representing the corresponding segment. We explain this implied validation semantics in
more detail below when we discuss how the JavaScript data model is encoded.
This leads to the JavaScript data model shown in Figure 12.7, where the class-level ('static') methods
are underlined:
Book
isbn[1] : String
title[1] : String
year[1] : Number
type[0..1] : Number {from BookTypeEL, frozen}
subjectArea[0..1] : String
about[0..1] : String
checkIsbn(in isbn : String) : ConstraintViolation «enumeration»
checkIsbnAsId(in isbn : String) : ConstraintViolation BookTypeEL
setIsbn(in isbn : String) TEXTBOOK = 1
checkTitle(in title : String) : ConstraintViolation BIOGRAPHY = 2
setTitle(in title : String)
checkYear(in year : Number) : ConstraintViolation
setYear(in year : Number)
checkType(in type : Number) : ConstraintViolation
setType(in type : Number)
checkSubjectArea(in subjectArea : String) : ConstraintViolation
setSubjectArea(in subjectArea : String)
checkAbout(in about : String) : ConstraintViolation
setAbout(in about : String)
New issues
Compared to the validation app [ValidationApp/index.html] discussed in Part 2 of this tutorial, we
have to deal with a number of new issues:
b. Encoding the enumeration type to be used as the range of the category attribute
(BookCategoryEL in our example).
c. Encoding the checkSubtype and setCategory methods for the category attribute.
In our example this attribute is optional, due to the fact that the book types segmentation is
incomplete. If the segmentation, to which the Class Hierarchy Merge pattern is applied, is
complete, then the category attribute is mandatory.
d. Encoding the checks and setters for all segment properties such that the check methods take the
category as a second parameter for being able to test if the segment property concerned applies
to a given instance.
f. Implementing the Frozen Value Constraint for the category attribute in Book.update by
updating the category of a book only if it has not yet been defined. This means it cannot be
updated anymore as soon as it has been defined.
115
Subtyping in a Plain
JavaScript Frontend App
a. Adding a "Special type" column to the display table of the "List all books" use
case in books.html. A book without a special category will have an empty table
cell, while for all other books their category will be shown in this cell, along with
other segment-specific attribute values. This requires a corresponding switch statement in
pl.view.books.list.setupUserInterface in the books.js view code file.
b. Adding a "Special type" select control, and corresponding form fields for all segment properties,
in the forms of the "Create book" and "Update book" use cases in books.html. Segment
property form fields are only displayed, and their validation event handlers set, when a
corresponding book category has been selected. Such an approach of rendering specific form
fields only on certain conditions is sometimes called "dynamic forms".
Summary
1. Encode the enumeration type (to be used as the range of the category attribute) as a special
JavaScript object mapping upper-case keys, representing enumeration literals, to corresponding
enumeration integers.
2. Encode the model class (obtained by applying the Class Hierarchy Merge pattern) in the form of
a JavaScript constructor function with class-level check methods attached to it, and with instance-
level setter methods attached to its prototype.
Notice that the names of the enumeration literals are stored in the names property of
BookCategoryEL such that they can be easily retrieved, for instance, for showing the category of
a book on a form.
116
Subtyping in a Plain
JavaScript Frontend App
We encode the checkSubtype and setCategory methods for the category attribute in the
following way:
While the setters for segment properties follow the standard pattern, their checks have to make sure
that the attribute applies to the category of the instance being checked. This is achieved by checking
a combination of a property value and a category, as in the following example:
117
Subtyping in a Plain
JavaScript Frontend App
In the serialization function toString, we serialize the category attribute and the segment properties
in a switch statement:
Book.prototype.toString = function () {
var bookStr = "Book{ ISBN:"+ this.isbn +", title:"+ this.title +
", year:"+ this.year;
switch (this.category) {
case BookCategoryEL.TEXTBOOK:
bookStr += ", textbook subject area:"+ this.subjectArea;
break;
case BookCategoryEL.BIOGRAPHY:
bookStr += ", biography about: "+ this.about;
break;
}
return bookStr +" }";
};
In the update method of a model class, we only set a property if it is to be updated (that is, if there
is a corresponding slot in the slots parameter) and if the new value is different from the old value.
In the special case of a category attribute with a Frozen Value Constraint, we need to make sure
that it can only be updated, along with an accompanying set of segement properties, if it has not yet
been assigned. Thus, in the Book.update method, we perform the special test if book.category
=== undefined for handling the special case of an initial assignment, while we handle updates of
the optional segment properties subjectArea and about in a more standard way:
118
Subtyping in a Plain
JavaScript Frontend App
Summary
We have to take care of handling the category attribute and the subjectArea and about
segment properties both in the "List all books" use case as well as in the "Create book" and "Update
book" use cases by
1. Adding a segment information column ("Special type") to the display table of the "List all books"
use case in books.html.
2. Adding a "Special type" select control, and corresponding form fields for all segment properties, in
the forms of the "Create book" and "Update book" use cases in books.html. Segment property
form fields are only displayed, and their validation event handlers set, when a corresponding book
category has been selected. Such an approach of rendering specific form fields only on certain
conditions is sometimes called "dynamic forms".
<table id="books">
<thead><tr><th>ISBN</th><th>Title</th><th>Year</th><th>Special type</th></tr><
<tbody></tbody>
</table>
A book without a special category will have an empty table cell in this column, while for all other
books their category will be shown in this column, along with other segment-specific information. This
requires a corresponding switch statement in pl.view.books.list.setupUserInterface
in the view/books.js file:
if (book.category) {
switch (book.category) {
case BookCategoryEL.TEXTBOOK:
row.insertCell(-1).textContent = book.subjectArea + " textbook";
break;
case BookCategoryEL.BIOGRAPHY:
row.insertCell(-1).textContent = "Biography about "+ book.about;
break;
}
}
119
Subtyping in a Plain
JavaScript Frontend App
<p class="pure-control-group">
<label for="creBookType">Special type: </label>
<select id="creBookType" name="category"></select>
</p>
<p class="pure-control-group Textbook">
<label for="creSubjectArea">Subject area: </label><input id="creSubjectArea" n
</p>
<p class="pure-control-group Biography">
<label for="creAbout">About: </label><input id="creAbout" name="about" />
</p>
Notice that we have added "Textbook" and "Biography" as additional class values to the HTML
class attributes of the p elements containing the corresponding form controls. This allows what is
called dynamic forms: easy rendering and un-rendering of "Textbook" and "Biography" form controls,
depending on the value of the category attribute.
120
Subtyping in a Plain
JavaScript Frontend App
point for our case study is the design model shown in Figure 12.3 above. In the following sections,
we derive a JavaScript data model and a JSON table model from the design model. The JSON table
model is used as a design for the object-to-JSON mapping that we need for storing the objects of our
app in Local Storage.
1. We have declared the segmentation of Person into Employee and Author to be complete, that
is, any person is an employee or an author (or both).
2. We have turned Person into an abstract class (indicated by its name written in italics in the
class rectangle), which means that it cannot have direct instances, but only indirect ones via its
subclasses Employee and Author, implying that we do not need to maintain its extension (in a
map like Person.instances), as we do for all other non-abstract classes. This technical design
decision is compatible with the fact that any Person is an Employee or an Author (or both),
and consequently there is no need for any instance to instantiate Person diretly.
Figure 12.8. The JavaScript data model of the Person class hierarchy
Person
«stdid» personId : Integer
name : String
checkPersonId(in id : String) : ConstraintViolation
checkPersonIdAsId(in id : String, in type) : ConstraintViolation
setPersonId(in id : String)
checkName(in n : String) : ConstraintViolation
setName(in n : String)
«enumeration»
EmployeeTypeEL
MANAGER = 1
{complete}
Employee Author
empNo : Integer {unique} biography : String
type : EmployeeTypeEL
department : String checkBiography(in b : String) : ConstraintViolation
setBiography(in b : String)
checkEmpNo(in n : Integer) : ConstraintViolation
setEmpNo(in n : Integer)
checkDepartment(in d : String) : ConstraintViolation
setDepartment(in d : String)
We design a set of suitable JSON tables and the structure of their records in the form of a JSON table
model that we derive from the design model by following certain rules. We basically have two choices
how to organize our JSON data store and how to derive a corresponding JSON table model: either
according to the Single Table Inheritance approach, where a segmentation or an entire class hierarchy
121
Subtyping in a Plain
JavaScript Frontend App
is represented with a single table, or according to the Joined Tables Inheritance approach, where we
have a separate table for each model class of the class hierarchy. Both approaches, which are discussed
in section , can be combined for the same design model.
In our example it seems natural to apply the Single Table Inheritance approach to the incomplete
segmentation of Employee with just one segment subclass Manager, while we apply the Joined
Tables Inheritance approach to the complete segmentation of Person into Employee and Author.
This results in the model shown in Figure 12.9 below.
Figure 12.9. The JSON table model of the Person class hierarchy
personId «enumeration»
«JSON table» PersonTypeEL
Employee «enumeration» EMPLOYEE = 1
«pkey» personId[1] : Integer EmployeeTypeEL AUTHOR = 2
empNo[1] : Integer {unique} MANAGER = 1
type[0..1] : EmployeeTypeEL
department[0..1] : String
«JSON table»
Author
«invariant» «pkey» personId : Integer personId
{type="MANAGER" IFF biography : String
NOT department=undefined}
Notice that we have replaced the «stdid» stereotype with «pkey» for indicating that the attributes
concerned act as primary keys in combination with foreign keys expressed by the dashed dependency
arrows stereotyped with «fkey» and annotated with the foreign key attribute (personId) at their
source end. An example of an admissible population for this table model is the following:
Notice the mismatch between the JavaScript data model shown in Figure 12.7 above, which is the basis
for the model classes Person, Employee and Author as well as for the main memory database
consisting of Employee.instances and Author.instances on one hand side, and the JSON
table model, shown in Figure 12.9 above on the other hand side. While we do not have any Person
records in the main memory database, we do have them in the persistent datastore based on the JSON
table model. This mismatch results from the complete structure of JavaScript subclass instances, which
include all property slots, as opposed to the fragmented structure of database tables based on the Joined
Tables Inheritance approach.
New issues
Compared to the model of our first case study, shown in Figure 12.7 above, we have to deal with a
number of new issues in the model code:
122
Subtyping in a Plain
JavaScript Frontend App
1. Defining the category relationships between Employee and Person, as well as between Author
and Person, using the JavaScript code pattern for constructor-based inheritance discussed in
Section .
2. When loading the instances of a category from persistent storage (as in Employee.loadAll
and Author.loadAll), their slots for inherited supertype properties, except for the standard
identifier attribute, have to be reconstructed from corresponding rows of the supertable (persons).
3. When saving the instances of Employee and Author as records of the JSON tables employees
and authors to persistent storage (as in pl.view.employees.manage.exit and
pl.view.authors.manage.exit), we also need to save the records of the supertable
persons by extracting their data from corresponding Employee or Author instances.
Employee.loadAll = function () {
var key="", keys=[], persons={}, employees={}, employeeRow={}, i=0;
if (!localStorage["employees"]) {
localStorage.setItem("employees", JSON.stringify({}));
}
try {
persons = JSON.parse( localStorage["persons"]);
employees = JSON.parse( localStorage["employees"]);
123
Subtyping in a Plain
JavaScript Frontend App
} catch (e) {
console.log("Error when reading from Local Storage\n" + e);
}
keys = Object.keys( employees);
console.log( keys.length +" employees loaded.");
for (i=0; i < keys.length; i++) {
key = keys[i];
employeeRow = employees[key];
// complete record by adding slots ("name") from supertable
employeeRow.name = persons[key].name;
Employee.instances[key] = Employee.convertRow2Obj( employeeRow);
}
};
Person.saveAll = function () {
var key="", keys=[], persons={}, i=0, n=0;
keys = Object.keys( Employee.instances);
for (i=0; i < keys.length; i++) {
key = keys[i];
emp = Employee.instances[key];
persons[key] = {personId: emp.personId, name:emp.name};
}
keys = Object.keys( Author.instances);
for (i=0; i < keys.length; i++) {
key = keys[i];
if (!persons[key]) {
author = Author.instances[key];
persons[key] = {personId: author.personId, name: author.name};
}
}
try {
localStorage["persons"] = JSON.stringify( persons);
n = Object.keys( persons).length;
console.log( n +" persons saved.");
} catch (e) {
alert("Error when writing to Local Storage\n" + e);
}
};
124
Part V. Using the Model-
Based Development
Framework mODELcLASS
In this last part we show how to avoid repetitive code structures ("boilerplate code") by using the model-based
development framework mODELcLASS, which has been developed as a result of writing this book.
Table of Contents
13. The Model-Based Development Framework mODELcLASSjs ....................................... 127
Model-Based Development .................................................................................. 128
The Philosophy and Features of mODELcLASSjs .................................................... 129
The Check Method ............................................................................................. 130
14. Constraint Validation with mODELcLASSjs .............................................................. 133
Encoding the Design Model ................................................................................. 133
Project Set-Up ................................................................................................... 134
The View and Controller Layers ........................................................................... 135
Run the App and Get the Code ............................................................................. 136
Evaluation ......................................................................................................... 136
Concluding Remarks ........................................................................................... 136
15. Associations and Subtyping with mODELcLASS ....................................................... 137
126
Chapter 13. The Model-Based
Development Framework
mODELcLASSjs
In the Model-View-Controller (MVC) paradigm, the UI is called 'view', and the term 'controller'
denotes the glue code needed for integrating the UI code with the model classes, or, in MVC jargon, for
integrating the 'view' with the 'model'. Using a model-based development approach, the model classes
of an app are obtained by encoding the app's data model, which is typically expressed in the form
of a UML class diagram. Since it is the task of the model layer to define and validate constraints and to
manage data storage, we need reusable model code taking care of this in a generic manner for avoiding
per-class and per-property boilerplate code for constraint validation, and per-class boilerplate code for
data storage management. This is where mODELcLASSjs comes in handy. It provides
2. the generic storage management methods add, update and destroy for creating new (persistent)
objects/rows, for updating existing objects/rows and for deleting them.
For using the functionality of mODELcLASSjs in your app, you have to include its code, either by
downloading mODELcLASS.js [https://bitbucket.org/gwagner57/entitytypejs/downloads] to the lib
folder and use a local script loading element like the following:
<script src="lib/mODELcLASS.js"></script>
or with the help of a remote script loading element like the following:
<script src="http://web-engineering.info/JsFrontendApp/mODELcLASS.js"></script>
Then you can create your app's model classes (with property and method declarations) as instances
of the meta-class mODELcLASS:
Notice that the declaration of a property includes the constraints that apply to it. For instance, the
declaration of the property isbn includes a pattern constraint reqiuring that the ISBN must be a 10-
digit string or a 9-digit string followed by "X".
After defining a model class, you can create new 'model objects' instantiating it by invoking the
create method provided by mODELcLASS:
You can then apply the following properties and methods, all pre-defined by mODELcLASS
127
The Model-Based Development
Framework mODELcLASSjs
3. the method set( prop, val) for setting an object property after checking all property constraints,
You can also invoke the generic check method provided by mODELcLASS in the user interface code.
For instance, for responsive validation with the HTML5 constraint validation API, you may do the
following:
Here we define an event handler for input events on the ISBN input field. It invokes the
setCustomValidity method of the HTML5 constraint validation API for setting a validation
error message that results from invoking Book.check for validating the constraints defined for the
isbn property for the user input value from the form field formEl.isbn. The check method
returns a constraint violation object with a message property. If no constraint is violated, the message
is an empty string, so nothing happens. Notice that you don't have to write the code of the check
method, as it is provided by mODELcLASSjs.
You can also use the methods provided by any mODELcLASS for managing data storage, like, for
instance,
By default, data is stored locally with the help of JavaScript's Local Storage API in the form of
'stringified' JSON tables. However, mODELcLASSjs will also provide the option to store data locally
with IndexedDB, instead of Local Storage, or to store data remotely with the help of XHR messaging,
for more demanding data management apps.
Model-Based Development
We must not confuse the term 'model' as used in the MVC paradigm, and adopted by many web
development frameworks, and the term 'model' as used in UML and other modeling languages. While
the former refers to the model classes of an app, the latter refers to the concept of a model either as a
simplified description of some part of the real world, or as a design blueprint for construction.
In model-based engineering, models are the basis for designing and implementing a system, no matter
if the system to be built is a software system or another kind of complex system such as a manufacturing
machine, a car, or an organisation.
128
The Model-Based Development
Framework mODELcLASSjs
1. solution-independent domain models describing a specific part of the real-world and resulting from
requirements and domain engineering in the system analysis, or inception, phase of a development
project;
2. platform-independent design models specifying a logical system design resulting from the design
activities in the elaboration phase;
Domain models are the basis for designing a software system by making a platform-independent design
model, which is in turn the basis for implementing a system by making an implementation model
and encoding it in the language of the chosen platform. Concerning information modeling, we first
make a domain information model, then we derive an information design model from it, and finally
map the information design model to a data model for the chosen platform. With an object-oriented
programming (OOP) approach, we encode this data model in the form of model classes, which are
the basis for designing and implementing a data management user interface (UI).
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.
Since we normally need to define class hierarchies, and not just single classes, these two alternative
approaches cannot be mixed within a class hierarchy, and we have to make a choice whenever we
build an app. With mODELcLASSjs, you choose the second approach with the following benefits:
1. Properties are declared (with a property label, a range and many other constraints)
These benefits come with a price: objects are created with lower performance, mainly due to the fact
that Object.create is slower than new. On the other hand, for apps with lots of object creation
and destruction, such as games and simulations, mODELcLASSjs provides object pools for avoiding
performance problems due to garbage collection.
The properties and methods of the meta-class mODELcLASS are listed in the following class diagram:
129
The Model-Based Development
Framework mODELcLASSjs
mODELcLASS
typeName[1] : String
supertype[0..1] : mODELcLASS
supertypes[*] : mODELcLASS
properties[1] : Map
methods[1] : Map
instances[*] : oBJECT (Map)
stdid[0..1] : String
create(in initSlots : Record) : oBJECT
check(in key : String, in value) : ConstraintViolation
add(in rec : Record)
update(in rec : Record)
destroy(in id)
convertRow2Obj(in row : Record) : oBJECT
loadAll()
saveAll()
clearData()
isSubTypeOf(in type : mODELcLASS) : Boolean
Notice that in the class diagram, we use the type oBJECT for values representing those special
JavaScript objects that instantiate a model class created with mODELcLASS. These objects have
a pre-defined property type referencing the model class they instantiate as their direct type. The
values of the pre-defined property instances are maps of oBJECTs representing the extension (or
population) of a model class.
Notice that the definition of a model class comes with a set of poperty declarations in properties.
An example of such a poperty declaration is the declaration of the attribute title:
In this example we have the property declaration parameters range, min, max and label. In
lines 2-10 above, all these property declaration parameters are copied to local variables for having
convenient shortcuts.
In the check method, the first check is concerned with mandatory value constraints:
130
The Model-Based Development
Framework mODELcLASSjs
switch (range) {
case "String":
if (typeof( val) !== "string") {
return new RangeConstraintViolation("The "+ label +
" must be a string!");
}
break;
case "NonEmptyString":
if (typeof(val) !== "string" || val.trim() === "") {
return new RangeConstraintViolation("The "+ label +
" must be a non-empty string!");
}
break;
... // other cases
case "Boolean":
if (typeof( val) !== "boolean") {
return new RangeConstraintViolation("The value of "+ label +
" must be either 'true' or 'false'!");
}
break;
}
Then there are several range-specific checks concerning (1) string length constraints and pattern
constraints:
Then the next check is concerned with cardinality constraints, which may apply to list-valued or
map-valued properties.
131
The Model-Based Development
Framework mODELcLASSjs
Then the next check is concerned with uniqueness constraints, which can only be checked by
inspecting the entire population of the model class. Assuming that this population has been loaded into
the main memory collection modelclass.instances, the following code is used:
Finally, the mandatory value constraints and the uniqueness constraints implied by a standard
identifier declaration are checked:
if (propDeclParams.isStandardId) {
if (val === undefined) {
return new MandatoryValueConstraintViolation("A value for the " +
"standard identifier attribute "+ label +" is required!");
} else if (this.instances && this.instances[val]) {
return new UniquenessConstraintViolation("There is already a "+
this.typeName +" with a(n) "+ label +" value "+ val +"!");
}
}
132
Chapter 14. Constraint Validation with
mODELcLASSjs
In this part of the tutorial, we show how to build a single-class app with constraint validation using
the model-based development framework mODELcLASSjs for avoiding boilerplate model code.
Compared to the app discussed in Part 2, we deal with the same issues: showing 1) how to define
constraints in a model class, 2) how to perform responsive validation in the user interface based on
the constraints defined in the model classes. The main difference when using mODELcLASSjs is that
defining constraints becomes much simpler. The check methods used in Part 2 are no longer needed.
Since constraints are defined in a purely declarative manner, their textual encoding corresponds
directly to their expression in the information design model. This implies that we can directly encode
the information design model without first creating a data model from it.
As in Part 2, the purpose of our app is to manage information about books. The information items and
constraints are described in the information design model shown in Figure 14.1 below.
Figure 14.1. A platform-independent design model with the class Book and two
invariants
Book «invariant»
«stdid» isbn[1] : NonEmptyString {isbn must be a 10-digit string
title[1] : NonEmptyString(50) or a 9-digit string followed by "X"}
year[1] : Integer
edition[0..1] : PositiveInteger
«invariant»
{year > 1454 AND
year <= nextYear()}
1. For the first three of the four properties defined in the Book class, we have a mandatory value
constraint, indicated by the multiplicity expression [1]. However, since properties are mandatory
by default in mODELcLASSjs, we don't have to encode anything for them. Only for the property
edition, we need to encode that it is optional with the key-value pair optional: true, as
shown in the edition property declaration in the class definition below.
2. The isbn attribute is declared to be the standard identifier of Book. We encode this (and
the implied uniqueness constraint) in the isbn property declaration with the key-value pair
isStandardId: true, as shown in the class definition below.
3. 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". We encode this with the key-value
pair pattern:/\b\d{9}(\d|X)\b/ and the special constraint violation message defined by
patternMessage:"The ISBN must be a 10-digit string or a 9-digit
string followed by 'X'!".
4. The title attribute has an string length constraint with a maximum of 50 characters. This is
encoded with max: 50.
5. The year attribute has an interval constraint with a minimum of 1459 and a maximum that is not
fixed, but provided by the utility function nextYear(). We can encode this constraint with the
key-value pairs min: 1459 and max: util.nextYear().
133
Constraint Validation
with mODELcLASSjs
6. Finally, there are four range constraints, one for each property. We encode them with corresponding
key-value pairs, like range:"NonEmptyString".
For such a model class definition, mODELcLASSjs provides generic data management operations
(Book.add, Book.update, Book.destroy, etc.) as well as property checks and setters
(Book.check and bookObject.set).
Project Set-Up
The MVC folder structure of this project is the same as in the plain JavaScript validation app
project discussed in Part 2 of the tutorial [http://web-engineering.info/JsFrontendApp/validation-
tutorial.html] (or in Chapter 3 of the book). Also, the same library files are used.
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 several JavaScript library files (in lines 8-12), including the mODELcLASS file, the app
initialization script initialize.js from the src/ctrl folder and the model class Book.js
from the src/model folder.
134
Constraint Validation
with mODELcLASSjs
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.check("isbn", formEl.isbn.value).message);
});
formEl.title.addEventListener("input", function () {
formEl.title.setCustomValidity(
Book.check("title", formEl.title.value).message);
});
...
},
};
While the validation on user input enhances the usability of the UI by providing immediate feedback
to the user, validation on form submission is even more important for catching invalid data. Therefore,
135
Constraint Validation
with mODELcLASSjs
handleSubmitButtonClickEvent: function () {
var formEl = document.forms['Book'];
var slots = { isbn: formEl.isbn.value,
title: formEl.title.value,
year: formEl.year.value,
edition: formEl.edition.value
};
// set error messages in case of constraint violations
formEl.isbn.setCustomValidity( Book.check( "isbn", slots.isbn).message);
formEl.title.setCustomValidity( Book.check( "title", slots.title).message);
formEl.year.setCustomValidity( Book.check( "year", slots.year).message);
formEl.edition.setCustomValidity( Book.check( "edition", slots.edition).messag
// save the input data only if all of the form fields are valid
if (formEl.checkValidity()) {
Book.add( slots);
}
}
Evaluation
We evaluate the approach presented in this chapter of the tutorial according to the criteria defined in
Part 2.
Concluding Remarks
After eliminating the repetitive code structures (called boilerplate code) needed in the model layer for
constraint validation and for the data storage management methods, there is still a lot of boilerplate
code needed in the UI. In a follow-up article of our tutorial series, we will present an approach how
to avoid this UI boilerplate code.
136
Chapter 15. Associations and
Subtyping with mODELcLASS
Coming Soon!
137
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.
138