Chapter 10. Implementing Unidirectional Non-Functional Associations with Plain JS

Table of Contents

1. Implementing Multi-Valued Reference Properties
2. Make a JS Class Model
3. New issues
4. Code the Model
4.1. Summary
4.2. Code the add and remove operations
4.3. Choose and implement a deletion policy
4.4. Serialization functions
5. Code the View
5.1. Setting up the Retrieve/List All user interface
5.2. Selecting associated objects in the Create user interface
5.3. Selecting associated objects in the Update user interface
6. Points of Attention
7. Quiz Questions
7.1. Question 1: Collection-valued properties
7.2. Question 2: Implementing the CASCADE deletion policy
7.3. Question 3: JS class model

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.

In this chapter, we show

  1. how to derive a JS class model from an OO class model with multi-valued reference properties representing unidirectional non-functional associations,

  2. how to code the JS class model in the form of JavaScript model classes,

  3. how to write the view and controller code based on the model code.

1. Implementing Multi-Valued Reference Properties

A multi-valued reference property, such as the property Book::authors, allows storing a collection of references to objects of some type, such as Author objects. When creating a new object of type Book, the constructor function needs to have a parameter for providing a suitable value for this property. We can allow this value to be either a collection of internal object references or of ID references, as shown in the following example:

class Book {
  constructor ({isbn, title, year, authors, authorIdRefs, 
      publisher, publisher_id}) {
    this.isbn = isbn;
    this.title = title;
    this.year = year;
    // assign object reference or ID reference
    this.authors = authors || authorIdRefs;
    if (publisher || publisher_id) {
      this.publisher = publisher || publisher_id;
    }
  }
  ...
}

Notice that the constructor's parameter record is expected to contain either an authors or an authorIdRefs slot. The JavaScript expression authors||authorIdRefs, using the disjunction operator ||, evaluates to a map authors, if there is a slot with name authors, or to an array authorIdRefs, otherwise. We handle the resulting ambiguity in the property setter by checking the type of the argument as shown in the following code fragment:

set authors( a) {
  this._authors = {};
  if (Array.isArray(a)) {  // array of IdRefs
    for (const idRef of a) {
      this.addAuthor( idRef);
    }
  } else {  // map of IdRefs to object references
    for (const idRef of Object.keys( a)) {
      this.addAuthor( a[idRef]);
    }
  }
}

In JS, a collection-valued reference property can be implemented in two ways:

  1. having an array list (a JS array) of object references as its value,

  2. having a map as its value, such that the values of the object's standard ID attribute are the keys, which are mapped to internal JS object references.

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. However, for implementing ordered or non-unique association ends corresponding to ordered-collection-valued or multi-set-valued (or bag-valued) reference properties, we use JS arrays.