2. Step 2 - Write the Model Code

In the second step, we write the code of our model class and save it in a specific model class file. In an MVC app, the model code is the most important part of the app. It's also the basis for writing the view and controller code. In fact, large parts of the view and controller code could be automatically generated from the model code. Many MVC frameworks provide this kind of code generation.

In the information design model shown in Figure 3.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:

function Book( slots) {
  this.isbn = slots.isbn;
  this.title = slots.title;
  this.year = slots.year;
};

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.

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:

  1. A class-level property Book.instances representing the collection of all Book instances managed by the application in the form of an entity table.

  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.

  4. A class-level method Book.add for creating a new Book instance.

  5. A class-level method Book.update for updating an existing Book instance.

  6. A class-level method Book.destroy for deleting a Book instance.

  7. A class-level method Book.createTestData for creating a few example book records to be used as test data.

  8. A class-level method Book.clearData for clearing the book datastore.

2.1. Representing the collection of all Book instances

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

Book.instances = {};

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 an entity table (a map of entity 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 an entity table in the form of a lookup table, as shown in Table 3.2.

Table 3.2. An entity table representing a collection of books

Key Value
006251587X { isbn:"006251587X," title:"Weaving the Web", year:2000 }
0465026567 { isbn:"0465026567," title:"Gödel, Escher, Bach", year:1999 }
0465030793 { isbn:"0465030793," title:"I Am A Strange Loop", year:2008 }

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

Table 3.3. A collection of book objects represented as a table

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

2.2. Creating a new Book instance

The Book.add procedure takes care of creating a new Book instance and adding it to the Book.instances collection :

Book.add = function (slots) {
  var book = new Book( slots);
  // add book to the Book.instances collectio
  Book.instances[slots.isbn] = book;
  console.log("Book " + slots.isbn + " created!");
};

2.3. Loading all Book instances

For persistent data storage, we use the Local Storage API supported by modern web browsers. Loading the book records from Local Storage involves three 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"];

    This retrieval is performed in line 5 of the program listing below.

  2. Converting the book table string into a corresponding entity table books with book rows as elements, with the help of the built-in procedure JSON.parse:

    books = JSON.parse( booksString);

    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 entity table Book.instances, with the help of the procedure convertRow2Obj defined as a "static" (class-level) method in the Book class:

    Book.convertRow2Obj = function (bookRow) {
      var book = new Book( bookRow);
      return book;
    };

Here is the full code of the procedure:

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.

2.4. Updating a Book instance

For updating an existing Book instance we first retrieve it from Book.instances, and then re-assign those attributes the value of which has changed:

Book.update = function (slots) {
  var book = Book.instances[slots.isbn];
  var year = parseInt( slots.year);
  if (book.title !== slots.title) { book.title = slots.title;}
  if (book.year !== year) { book.year = year;}
  console.log("Book " + slots.isbn + " modified!");
};

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.

2.5. Deleting a Book instance

A Book instance is deleted from the entity table Book.instances by first testing if the table has a row with the given key (line 2), and then applying the JavaScript built-in delete operator:, which deletes a slot from an object, or an entry from a map:

Book.destroy = function (isbn) {
  if (Book.instances[isbn]) {
    console.log("Book " + isbn + " deleted");
    delete Book.instances[isbn];
  } else {
    console.log("There is no book with ISBN " + 
        isbn + " in the database!");
  }
};

2.6. Saving all Book instances

Saving all book objects from the Book.instances collection in main memory to Local Storage in secondary memory involves two steps:

  1. Converting the entity table Book.instances into a string with the help of the predefined JavaScript procedure JSON.stringify:

    booksString = JSON.stringify( Book.instances);

    This conversion is called serialization.

  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.");
};

2.7. Creating test data

For being able to test our code, we may create some test data and save it in our Local Storage database. We can use the following procedure for this:

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();
};

2.8. Clearing all data

The following procedure clears all data from Local Storage:

Book.clearData = function () {
  if (confirm("Do you really want to delete all book data?")) {
    localStorage["books"] = "{}";
  }
};