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/m
, 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 coded as a JavaScript
constructor function with a single slots
parameter, which is
a record object with fields isbn
, title
and
year
, representing the constructor parameters to be assigned
to the ISBN, the title and the year attributes of the class Book
.
Notice that, for getting a simple name, we have put the class name
Book
in the global scope, which is okay
for a small app with only a few classes. In general, however, we should
avoid using the global scope (either with the help of namespace objects or
by using ES6 modules).
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:
A class-level property Book.instances
representing the collection of all Book
instances
managed by the application in the form of an entity table.
A class-level method Book.retrieveAll
for loading
all managed Book instances from the persistent data store.
A class-level method Book.saveAll
for saving all
managed Book instances to the persistent data store.
A class-level method Book.add
for creating a new
Book instance.
A class-level method Book.update
for updating an
existing Book instance.
A class-level method Book.destroy
for deleting a
Book instance.
A class-level method Book.createTestData
for
creating a few example book records to be used as test data.
A class-level method Book.clearData
for clearing
the book datastore.
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:
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.1.
The Book.add
procedure takes care of creating a new
Book
instance and adding it to the
Book.instances
collection:
Book.add = function (slots) {
const book = new Book( slots);
// add book to the collection of Book.instances
Book.instances[slots.isbn] = book;
console.log(`Book ${slots.isbn} created!`);
};
For persistent data storage, we use the Local Storage API supported by modern web browsers. Retrieving the book records from Local Storage involves three steps:
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"];
Converting the book table string into a corresponding entity
table books
with book rows as elements, with the help
of the built-in function JSON.parse
:
books = JSON.parse( booksString);
This conversion is called de-serialization.
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
convertRec2Obj
defined as a "static" (class-level)
method in the Book
class:
Book.convertRec2Obj = function (bookRow) {
const book = new Book( bookRow);
return book;
};
Here is the full code of the procedure:
Book.retrieveAll = function () {
var booksString="";
try {
if (localStorage["books"]) {
booksString = localStorage["books"];
}
} catch (e) {
alert("Error when reading from Local Storage\n" + e);
}
if (booksString) {
const books = JSON.parse( booksString);
const keys = Object.keys( books);
console.log(`${keys.length} books loaded.`);
for (const key of keys) {
Book.instances[key] = Book.convertRec2Obj( 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.
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) {
const book = Book.instances[slots.isbn],
year = parseInt( slots.year); // convert string to integer
if (book.title !== slots.title) book.title = slots.title;
if (book.year !== year) book.year = year;
console.log(`Book ${slots.isbn} modified!`);
};
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!`);
}
};
Saving all book objects from the Book.instances
collection in main memory to Local Storage in secondary memory involves
two steps:
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.
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 error = false;
try {
const booksString = JSON.stringify( Book.instances);
localStorage["books"] = booksString;
} catch (e) {
alert("Error when writing to Local Storage\n" + e);
error = true;
}
if (!error) {
const nmrOfBooks = Object.keys( Book.instances).length;
console.log(`${nmrOfBooks} books saved.`);
}
};
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();
};