5. Write the Model Code

How to Encode a JavaScript Data Model

5.1. Encode the enumerations

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

var  PublicationFormEL = new Enumeration(["hardcover","paperback","ePub","PDF"])
var  LanguageEL = new Enumeration({"en":"English", "de":"German",
                  "fr":"French", "es":"Spanish"})

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

5.2. Encode the model class as a constructor function

The class Book is encoded by means of a corresponding JavaScript constructor function such that all its (non-derived) properties are supplied with values from corresponding key-value slots of a slots parameter.

function Book( slots) {
  // assign default values
  this.isbn = "";   // string
  this.title = "";  // string
  this.originalLanguage = 0;  // number (from LanguageEL)
  this.otherAvailableLanguages = [];  // list of numbers (from LanguageEL)
  this.category = 0;  // number (from BookCategoryEL)
  this.publicationForms = [];  // list of numbers (from PublicationFormEL)
  // assign properties only if the constructor is invoked with an argument
  if (arguments.length > 0) {
    this.setIsbn( slots.isbn); 
    this.setTitle( slots.title); 
    this.setOriginalLanguage( slots.originalLanguage);
    this.setOtherAvailableLanguages( slots.otherAvailableLanguages);
    this.setCategory( slots.category);
    this.setPublicationForms( slots.publicationForms);
  }
};

5.3. Encode the enumeration attribute checks

Encode 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:

Book.checkOriginalLanguage = function (l) {
  if (l === undefined) {
    return new MandatoryValueConstraintViolation(
        "An original language must be provided!");
  } else if (!Number.isInteger( l) || l < 1 || l > LanguageEL.MAX) {
    return new RangeConstraintViolation("Invalid value for original language: "+ l);
  } else {
    return new NoConstraintViolation();
  }
};

For a multi-valued enmueration attribute, such as publicationForms, we break down the check code into two 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):

Book.checkPublicationForm = function (p) {
  if (!p && p !== 0) {
    return new MandatoryValueConstraintViolation(
        "No publication form provided!");
  } else if (!Number.isInteger( p) || p < 1 || 
        p > PublicationFormEL.MAX) {
    return new RangeConstraintViolation(
        "Invalid value for publication form: "+ p);
  } else {
    return new NoConstraintViolation();
  }
};
Book.checkPublicationForms = function (pubForms) {
  var i=0, constrVio=null;
  if (pubForms.length === 0) {
    return new MandatoryValueConstraintViolation(
        "No publication form provided!");
  } else {
    for (i=0; i < pubForms.length; i++) {
      constrVio = Book.checkPublicationForm( pubForms[i]);
      if (!(constrVio instanceof NoConstraintViolation)) {
        return constrVio;
      }
    }
    return new NoConstraintViolation();
  }
};

5.4. Encode the enumeration attribute setters

Both for single-valued and for multi-valued enumeration attributes an ordinary setter is defined. In the case of a multi-valued enumeration attribute, this setter assigns an entire set of values (in the form of a JS array) to the attribute after checking its validity.

5.5. Write a serialization function

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

Book.prototype.toString = function () {
  return "Book{ ISBN:"+ this.isbn +", title:"+ this.title + 
    ", originalLanguage:"+ this.originalLanguage +
    ", otherAvailableLanguages:"+ this.otherAvailableLanguages.toString() +
    ", category:"+ this.category +
	  ", publicationForms:"+ this.publicationForms.toString() +"}"; 
};

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

5.6. Data 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).

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

    Array.prototype.isEqualTo = function (a2) {
      return (this.length === a2.length) && this.every( function( el, i) {
        return el === a2[i]; });
    }; 

    This allows us to express these tests in the following way:

    if (!book.publicationForms.isEqualTo( slots.publicationForms)) {
      book.setPublicationForms( slots.publicationForms);
      updatedProperties.push("publicationForms");
    }

5.7. Creating 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:

Book.createTestData = function () {
  try {
    Book.instances["006251587X"] = new Book({isbn:"006251587X", 
        title:"Weaving the Web", originalLanguage:LanguageEL.EN, 
        otherAvailableLanguages:[LanguageEL.DE,LanguageEL.FR], 
        category:BookCategoryEL.NOVEL, 
        publicationForms:[PublicationFormEL.EPUB,PublicationFormEL.PDF]});
    ...
    Book.saveAll();
  } catch (e) {
    console.log( e.constructor.name + ": " + e.message);
  }
};