11Implementing Enumeration Attributes in a Plain JS Web App

In this chapter, we show how to build a front-end web application with enumeration attributes, using plain JavaScript. In addition to the topic of enumeration attributes, we also show how to deal with multi-valued attributes because in many cases, enumeration attributes are multi-valued.

11.1New Issues

Compared to the Validation App137 discussed in Part 2, we now deal with the following new issues:

  1. We replace the ES5 constructor-based class definition of our model class Book with a corresponding ES6 class definition.
  2. Instead of defining explicit setters we now make use of the ES5 feature of defining implicit get/set methods for properties.
  3. Enumeration datatypes have to be defined in a suitable way as part of the model code.
  4. Enumeration attributes have to be defined in model classes and handled in the user interface with the help of suitable choice widgets.

In terms of coding, the new issues are:

1.In the model code we now have to take care of

a.defining an ES6 class instead of a constructor function (with additional method slots of its prototype object) for Book;

b.defining get/set methods for all properties of the Book class definition;

c.defining the enumerations with the help of a utility (meta‐)class Enumeration, which is discussed below;

d.defining the single-valued enumeration attributes originalLanguage and category together with their check functions checkOriginalLanguage and checkCategory;

e.defining the multi-valued enumeration attributes otherAvailableLanguages and publicationForms together with their checks checkOtherAvailableLanguages and checkPublicationForms;

f.extending the methods Book.update, and Book.prototype.toString such that they take care of the added enumeration attributes.

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

a.adding new table columns in retrieveAndListAllBooks.html and suitable choice widgets in createBook.html and upateBook.html;

b.creating output for the new attributes in the setupUserInterface() method of pl.v.retrieveAndListAllBooks;

c.allowing input for the new attributes in the setupUserInterface() methods of pl.v.createBook and pl.v.updateBook.

11.2Make a JavaScript Class Model

Using the information design model shown in Figure 10.2 above as the starting point, we make a JavaScript class model, essentially by decorating attributes with a “get/ set” stereotype (implying that they have implicit getters and setters), and by adding (class-level) check methods:

Figure 11.1 A JavaScript class model for the object type Book

Notice that, for any multi-valued enumeration attribute (like someThings) we add a class-level check function for single values (like checkSomeThing) and another one for value sets (like checkSomeThings) both returning an object of type ConstraintViolation.

The implicit getters and setters implied by the “get/set” stereotype are a special feature of ES5, allowing to define methods for getting and setting the value of a property p while keeping the simple syntax of getting its value with “… = p” and setting it with “p = …”. They require to define another, internal, property (like _p) for storing the value of p because the name “p” does not refer to a normal property, but rather to a pair of get/set methods.

The most common reason for using implicit getters and setters is the need to always check constraints before setting a property. This is also the reason why we will use them.

11.3Add the Library File Enumeration. js

The folder structure of our enumeration app extends the structure of the validation app by adding the file Enumeration. js in the lib folder. Thus, we get the following folder structure with four files in the lib folder:

In the Enumeration. js file, discussed in the next section, we define a meta-class Enumeration for creating enumerations as instances of this meta-class with the help of statements like GenderEL = new Enumeration(["male", "female", "undetermined"]).

11.4The Meta-Class Enumeration

We define an Enumeration meta-class, which supports both simple enumerations and code lists (but not record enumerations). While a simple enumeration is defined by a list of labels in the form of a JS array as the constructor argument such that the labels are used for the names of the enumeration literals, a code list is defined as a special kind of key-value map in the form of a JS object as the constructor argument such that the codes are used for the names of the enumeration literals. Consequently, the constructor needs to test if the invocation argument is a JS array or not. The following first part of the code shows how simple enumerations are created:

After setting the MAX property of the newly created enumeration, the enumeration literals are created in a loop as further properties of the newly created enumeration such that the property name is the normalized label string and the value is the index, or sequence number, starting with 1. Notice that a label string like “text book” or “text-book” is normalized to the enumeration literal name “TEXT_BOOK”, following a widely used convention for constant names. Finally, by invoking Object.freeze on the newly created enumeration, all its properties become ’unwritable’ (or read-only).

The following second part of the code shows how code list enumerations are created:

Notice that the code list labels in this.labels are extended by appending their codes in parenthesis.

11.5Write the Model Code

11.5.1Code the enumerations

Enumerations are coded in the following way with the help of the meta-class Enumeration:

Notice that LanguageEL defines a code list enumeration, while PublicationFormEL defines a simple enumeration.

11.5.2Code the model class as an ES6 class

We want to check if a new property value satisfies all constraints of a property whenever the value of a property is set. A best practice approach for making sure that new values are validated before assigned is to use a setter method for assigning a property, and invoke the check in the setter. We can either define an explicit setter method (like setIsbn) for a property (like isbn), or we can use JavaScript’s implicit getters and setters in combination with an internal property name (like _isbn). We have used explicit setters in the validation app. Now, in the Book class definition for the enumeration app, we use JavaScript’s implicit getters and setters because they offer a more user-friendly syntax and can be conveniently defined in an ES6 class definition.

The class Book is coded in the form of an ES6 class such that all its properties are defined with an internal property name format (prefixed with _) and assigned with values from corresponding key-value slots of a slots parameter in the constructor:

11.5.3Code the implicit getters and setters

For each property, we define implicit getters and setters using the predefined JS keywords get and set:

Notice that the implicit getters and setters access the corresponding internal property, like _isbn. This approach is based on the assumption that this internal property is normally not accessed directly, but only via its getter or setter. Since we can normally assume that developers comply with this rule (and that there is no malicious developer in the team), this approach is normally safe enough. However, there is also a proposal to increase the safety (for avoiding direct access) by generating random names for the internal properties with the help of ES6 Symbols.

11.5.4Code the enumeration attribute checks

Code the enumeration attribute checks in the form of class-level (’static’) functions that check if the argument is a valid enumeration index not smaller than 1 and not greater than the enumeration’s MAX value. For instance, for the checkOriginalLanguage function we obtain the following code:

For a multi-valued enumeration attribute, such as publicationForms, we break down the validation code into two check functions, one for checking if a value is a valid enumeration index (checkPublicationForm), and another one for checking if all members of a set of values are valid enumeration indexes (checkPublicationForms). The first check is coded as follows:

The second check first tests if the argument is a non-empty array (representing a collection with at least one element) and then checks all elements of the array in a loop:

11.5.5Write a serialization function

The object serialization function toString() now needs to include the values of enumeration attributes:

Notice that for multi-valued enumeration attributes we call the toString() function that is predefined for JS arrays.

11.5.6Data management operations

There are only two new issues in the data management operations compared to the validation app:

1.We have to make sure that the util.cloneObject method, which is used in Book. update, takes care of copying array-valued attributes, which we didn’t have before (in the validation app).

In the Book.update method we now have to check if the values of array-valued attributes have changed, which requires to test if two arrays are equal or not. For code readability, we add an array equality test method to Array.prototype in browserShims.js, like so:

11.5.7Creating test data

In the test data records that are created by Book. createTestData(), we now have to provide values for single- and multi-valued enumeration attributes. For readability, we use enumeration literals instead of enumeration indexes:

11.6Write the View Code

The example app’s user interface for creating a new book record looks as in Figure 11.2 below.

Figure 11.2 The UI for creating a new book record with enumeration attributes

Notice that the UI contains four choice widgets:

  1. a single selection list for the attribute originalLanguage,
  2. a multiple selection list for the attribute otherAvailableLanguages,
  3. a radio button group for the attribute category, and
  4. a checkbox group for the attribute publicationForms.

11.6.1Selection lists

We use HTML selection lists for rendering the enumeration attributes originalLanguage and otherAvailableLanguages in the HTML forms in createBook.html and upateBook.html. Since the attribute otherAvailableLanguages is multi-valued, we need a multiple selection list for it, as shown in the following HTML code:

While we define the select container elements for these selection lists in the HTML code of createBook.html and upateBook.html, we fill in their option child elements dynamically in the setupUserInterface methods in v/createBook.js and v/updateBook.js with the help of the utility method util.fillSelectWithOptions.

In the case of a single select element, the user’s single-valued selection can be retrieved from the value attribute of the select element, while in the case of a multiple select element, the user’s multi-valued selection can be retrieved from the selectedOptions attribute of the select element.

Notice that the div element containing the multiple selection list for otherAvailableLanguages has the class value “multi-sel”, which is used for defining specific CSS rules that adjust the element’s size.

11.6.2Radio button and checkbox groups

Since the enumeration attributes category and publicationForms do not have more than seven possible values, we can use a radio button group and a checkbox group for rendering them in an HTML-form-based UI. These choice widgets are formed with the help of the container element fieldset and its child element legend as shown in the following HTML fragment:

Notice that we use a custom attribute data-bind for indicating to which attribute of the underlying model class the choice widget is bound.

In the same way as the option child elements of a selection list, also the labeled input child elements of a choice widget are created dynamically with the help of the utility method util.createChoiceWidget in the setupUserInterface methods in v/createBook.js and v/updateBook.js.

Notice that like a selection list implemented with the HTML select element that provides the user’s selection in the value or selectedOptions attribute, our choice widgets also need a DOM attribute that holds the user’s single- or multi-valued choice. We dynamically add a custom attribute data-value to the choice widget’s fieldset element for this purpose in util.createChoiceWidget.

11.6.3Responsive validation for choice widgets

Since choice widgets do not allow arbitrary user input, we do not have to check constraints such as range constraints or pattern constraints on user input, but only mandatory value constraints. This allows simplifying responsive validation in the UI.

In our example app, the enumeration attributes originalLanguage, category and publicationForms are mandatory, while otherAvailableLanguages is optional.

In the case of a mandatory single-valued enumeration attribute like originalLanguage rendered as a single selection list, we can enforce a choice, and thus the mandatory value constraint, by not offering an empty or void option among the option sub-elements of the select element. If the attribute is rendered as a radio button group, we can enforce a choice, and thus the mandatory value constraint, in the create use case by initially setting the checked attribute of the first radio button to true and not allowing the user to directly uncheck a button. In this way, if the user doesn’t check any button, the first one is the default choice.

In the case of an optional single-valued enumeration attribute rendered as a single-selection list, we need to include an empty or void option (e. g., in the form of a string like “—”). If the attribute is rendered as a radio button group, we do not check any button initially and we need to allow the user to directly uncheck a button with a mouse click in a click event listener.

In the case of a mandatory multi-valued enumeration attribute like publicationForms rendered as a multiple-selection list or checkbox group, we need to check if the user has chosen at least one option. Whenever the user selects or unselects an option in a select element, a change event is raised by the browser, so we can implement the responsive mandatory value constraint validation as an event listener for change events on the select element, by testing if the list of selectedOptions is empty. If the attribute is rendered as a checkbox group, we need an event listener for click events added on the fieldset element and testing if the widget’s value set is non-empty, as shown in the following example code fragment:

Notice that the HTML5 constraint validation API does not allow to indicate a constraint violation on a fieldset element (as the container element of a choice widget). As a workaround, we use the first checkbox element of the publicationForms choice widget, which can be accessed with formEl.publicationForms[0], for invoking the setCustomValidity method that indicates a constraint violation if its argument is a non-empty (message) string.

11.7Run the App and Get the Code

You can run the enumeration app138 from our server or download the code139 as a ZIP archive file.

11.8Practice Projects

Compared to the validation app, you now have to take care of

  1. defining enumeration data types with the help of the meta-class Enumeration and defining any enumeration attribute together with a static check function and implicit get/set methods in the model code,
  2. adding suitable choice widgets for the enumeration attributes in the Create and Update user interfaces in the view code.

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

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

11.8.1Project 1Adding ratings and genres as enumeration attributes

The purpose of the app to be built is managing information about movies. The app deals with just one object type, Movie, and with two enumerations, as depicted in the following class diagram.

Compared to the corresponding validation app project, two attributes have been added: the optional single-valued enumeration attribute rating, and the multi-valued enumeration attribute genres.

Following the tutorial, you have to take care of

  1. defining the enumeration data types MovieRatingEL and GenreEL with the help of the meta-class Enumeration;
  2. defining the single-valued enumeration attribute Movie::rating;
  3. defining the multi-valued enumeration attributes Movie::genres;
  4. extending the methods Movie.update, and Movie.prototype.toString such that they take care of the added enumeration attributes.

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

  1. adding new table columns in retrieveAndListAllMovies.html and suitable choice widgets in createMovie.html and upateMovie.html;
  2. creating output for the enumeration attributes in the method v. retrieveAndListAllMovies.setupUserInterface();
  3. allowing input for the enumeration attributes in the methods v. createMovie. setupUserInterface() and v. upateMovie.setupUserInterface().

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

Table 11.1 Sample data about movies with rating and genres

More movie data can be found on the IMDb website144.

11.8.2Project 2Adding country codes and religions as enumeration attributes

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

Compared to the corresponding validation app project, two attributes have been added: the single-valued enumeration attribute code, which is a “key” attribute (implying a uniqueness constraint), and the multi-valued enumeration attribute religions.

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

Table 11.2 Sample data about countries with country code and religions

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

11.9Quiz Questions

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

11.9.1Question 1: Checking the range constraint of an enumeration attribute

Complete the following code fragment for checking the range constraint of the enumeration attribute gender with range GenderEL:

11.9.2Question 2: Computing MAX

Complete the following code fragment for computing the special enumeration literal MAX as in the proposed Enumeration class:

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

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