Click here to Skip to main content
Click here to Skip to main content

JavaScript Frontend Web App Tutorial Part 1: Building a Minimal App in Seven Steps

, 6 Oct 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This tutorial shows how to build a JavaScript frontend web application with minimal effort, not using any (third-party) framework or library.

Introduction

This article is an excerpt from JavaScript Frontend Web Apps Tutorial Part 1: Building a Minimal App in Seven Steps, which is part of the open access book Engineering Frontend Web Apps with Plain JavaScript. It shows how to build such an app with minimal effort, not using any (third-party) framework or library. A frontend web app can be provided by any web server, but it is executed on the user's computer device (smartphone, tablet or notebook), and not on the remote web server. Typically, but not necessarily, a frontend web app is a single-user application, which is not shared with other users.

The minimal version of a JavaScript frontend data management app discussed in this tutorial only includes a minimum of the overall functionality required for a complete app. It takes care of only one object type (Book) and supports the four standard data management operations (Create/Read/Update/Delete), but it needs to be enhanced by styling the user interface with CSS rules, and by adding further important parts of the app's overall functionality:

  • Part 2: Handling constraint validation

  • Part 3: Managing unidirectional associations

  • Part 4: Managing bidirectional associations

  • Part 5: Handling subtype (inheritance) relationships between object types

These additional parts will be published soon.

Background

This section provides a brief discussion of HTML and some elements of JavaScript, assuming that the reader is already familiar with basic programming concepts and has some experience with programming, for instance, in PHP, Java or C#.

HTML

We adopt the symbolic equation

HTML = HTML5 = XHTML5

stating that when we just say "HTML", or "HTML5", we actually mean XHTML5, because we prefer the clear syntax of XML documents over the liberal and confusing HTML4-style syntax that is also allowed by HTML5.

JavaScript Objects

JavaScript objects are different from classical OO/UML objects. In particular, they need not instantiate a class. And they can have their own (instance-level) methods in the form of method slots, so they do not only have (ordinary) property slots, but also method slots. In addition, they may also have key-value slots. So, they may have three different kinds of slots, while classical objects (called "instance specifications" in UML) only have property slots.

JavaScript objects can be used in many different ways for different purposes. Here are five different use cases for, or possible meanings of, JavaScript objects:

  1. A record is a set of property slots like, for instance,

    var myRecord = { firstName:"Tom", lastName:"Smith", age:26} 
  2. A map (or 'hash map' or 'associative array') is a set of key-value slots. It supports look-ups of values based on keys like, for instance,

    var numeral2number = { "one":1, "two":2, "three":3}

    which associates the numeric value 1 with the key "one", 2 with "two", etc. A key need not be a valid JavaScript identifier, but can be any kind of string (e.g. it may contain blank spaces).

  3. An untyped object does not instantiate a class. It may have property slots and method slots like, for instance,

    var person1 = {  
      lastName: "Smith",  
      firstName: "Tom",
      getInitials: function () {
        return this.firstName.charAt(0) + this.lastName.charAt(0); 
      }  
    };
  4. A namespace may be defined in the form of an untyped object referenced by a global object variable, the name of which represents a namespace prefix. For instance, the following object variable provides the main namespace of an application based on the Model-View-Controller (MVC) architecture paradigm where we have three subnamespaces corresponding to the three parts of an MVC application:

    var myApp = { model:{}, view:{}, ctrl:{} };
  5. A typed object o that instantiates a class defined by a JavaScript constructor function C is created with the expression:

    var o = new C(...)

    The type/class of such a typed object can be retrieved with the introspective expression:

    o.constructor.name  // returns "C" 

Maps

A map is processed with the help of a special loop where we loop over all keys of the map using the pre-defined function Object.keys(a), which returns an array of all keys of a map a. For instance,

for (var i=0; i < Object.keys( numeral2number).length; i++) {
  key = Object.keys( numeral2number)[i];
  alert('The numeral '+ key +' denotes the number '+ numeral2number[key]);
}

For adding a new element to a map, we simply create a new key-value entry as in:

numeral2number["thirty two"] = 32; 

For deleting an element from a map, we can use the pre-defined JavaScript delete operator as in:

delete numeral2number["thirty two"];

Defining and Instantiating a Class

A class can be defined in two steps. First, define the constructor function that defines the properties of the class and assigns them the values of the constructor's parameters:

function Person( first, last) {
  this.firstName = first; 
  this.lastName = last; 
}

Next, define the instance-level methods of the class as function slots of the prototype object property of the constructor function:

Person.prototype.getInitials = function () {
  return this.firstName.charAt(0) + this.lastName.charAt(0); 
}

Finally, class-level ("static") methods can be defined as function slots of the constructor function, as in:

Person.checkName = function (n) {
  ... 
}

An instance of a class is created by applying the new operator to the constructor function:

var pers1 = new Person("Tom","Smith");

The method getInitials is invoked on the Person object pers1 by using the 'dot notation':

alert("The initials of the person are: " + pers1.getInitials()); 

Coding the App

The purpose of our example app is to manage information about books. That is, we deal with a single object type: Book, as depicted in the following figure.

What do we need for such an information management application? There are four standard use cases, which have to be supported by the application:

  1. Create: Enter the data of a book that is to be added to the collection of managed books.

  2. Read: Show a list of all books in the collection of managed books.

  3. Update the data of a book.

  4. Delete a book record.

For entering data with the help of the keyboard and the screen of our computer, we can use HTML forms, which provide the user interface technology for web applications.

For maintaining a collection of data objects, we need a storage technology that allows to keep data objects in persistent records on a secondary storage device, such as a harddisk or a solid state disk. Modern web browsers provide two such technologies: the simpler one is called Local Storage, and the more powerful one is called IndexDB. For our minimal example app, we use Local Storage.

Step 1 - Set up the Folder Structure

In the first step, we set up our folder structure for the application. We pick a name for our app, such as "Public Library", and a corresponding (possibly abbreviated) name for the application folder, such as "publicLibrary". Then we create this folder on our computer's disk and a subfolder "src" for our JavaScript source code files. In this folder, we create the subfolders "model", "view" and "ctrl", following the Model-View-Controller paradigm for software application architectures. And finally we create an index.html file for the app's start page, as discussed below. Thus, we end up with the following folder structure:

publicLibrary
  src
    ctrl
    model
    view
  index.html

The start page of the app loads the Book.js model class file and provides a menu for choosing one of the CRUD data management operations performed by a corresponding page such as, for instance, createBook.html, or for creating test data with the help of the procedure Book.createTestData(), or clearing all data with Book.clearData():

The minimal app's start page index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal JS Frontend App Example</title>
  <script src="src/model/Book.js"></script>
</head>
<body>
  <h1>Public Library</h1>
  <h2>An Example of a Minimal JavaScript Frontend App</h2>
  <p>This app supports the following operations:</p>
  <menu>
    <li><a href="listBooks.html"><button type="button">List all books</button></a></li>
    <li><a href="createBook.html"><button type="button">Add a new book</button></a></li>
    <li><a href="updateBook.html"><button type="button">Update a book</button></a></li>
    <li><a href="deleteBook.html"><button type="button">Delete a book</button></a></li>
    <li><button type="button" onclick="Book.clearData()">Clear database</button></li>
    <li><button type="button" onclick="Book.createTestData()">Create test data</button></li>
  </menu>
</body>
</html> 

Step 2 - Write the Model Code

In the second step, we write the code of our model class in a specific JavaScript file. In the information design model shown in 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 a map.

  2. A class-level method Book.loadAllInstances for loading all managed Book instances from the persistent data store.

  3. A class-level method Book.saveAllInstances for saving all managed Book instances to the persistent data store.

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

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

  6. A class-level method Book.deleteRow 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.

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, since we want to represent it in the form of a map (a set of key-value slots, also called 'hashmap') where an ISBN is a key for accessing the corresponding book object (as the value associated with the key). We can visualize the structure of such a map in the form of a lookup table, as shown in Table 1.

Table 1: A map 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 this map are simple objects corresponding to table rows. Consequently, we could represent them also in a simple table, as shown in Table 2.

Table 2: 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. Loading all Book instances

For persistent data storage, we use Local Storage, which is a HTML5 JavaScript 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 "bookTable" from Local Storage with the help of the assignment:

    bookTableString = localStorage["bookTable"];

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

  2. Converting the book table string into a corresponding map bookTable with book rows as elements, with the help of the built-in function JSON.parse:

    bookTable = JSON.parse( bookTableString); 

    This conversion, performed in line 11 of the program listing below, is called deserialization.

  3. Converting each row of bookTable (representing an untyped record object) into a corresponding object of type Book stored as an element of the map 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.loadAllInstances = function () {
  var key="", keys=[], bookTableString="", bookTable={};  
  try {
    if (localStorage["bookTable"]) {
      bookTableString = localStorage["bookTable"];
    }
  } catch (e) {
    alert("Error when reading from Local Storage\n" + e);
  }
  if (bookTableString) {
    bookTable = JSON.parse( bookTableString);
    keys = Object.keys( bookTable);
    console.log( keys.length +" books loaded.");
    for (var i=0; i < keys.length; i++) {
      key = keys[i];
      Book.instances[key] = Book.convertRow2Obj( bookTable[key]);
    }
  }
};

Notice that since an input operation like localStorage["bookTable"] may fail, we perform it in a try-catch block, where we can follow up with an error message whenever the input operation fails.

3. 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 map Book.instances into a string with the help of the predefined JavaScript function JSON.stringify:

    bookTableString = JSON.stringify( Book.instances);

    This conversion is called serialization.

  2. Writing the resulting string as the value of the key "bookTable" to Local Storage:

    localStorage["bookTable"] = bookTableString;

These two steps are performed in line 5 and in line 6 of the following program listing:

Book.saveAllInstances = function () {
  var bookTableString="", error=false,
      nmrOfBooks = Object.keys( Book.instances).length;  
  try {
    bookTableString = JSON.stringify( Book.instances);
    localStorage["bookTable"] = bookTableString;
  } catch (e) {
    alert("Error when writing to Local Storage\n" + e);
    error = true;
  }
  if (!error) console.log( nmrOfBooks + " books saved.");
};

4. Creating a new Book instance

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

Book.createRow = function (slots) {
  var book = new Book( slots);
  Book.instances[slots.isbn] = book;
  console.log("Book " + slots.isbn + " created!");
};

5. Updating an existing 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.updateRow = 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 and parseFloat.

6. Deleting an existing Book instance

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

Book.deleteRow = 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!");
  }
}; 

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&ouml;del, Escher, Bach", year:1999});
  Book.instances["0465030793"] = new Book({isbn:"0465030793", title:"I Am A Strange Loop", year:2008});
  Book.saveAllInstances();
};

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["bookTable"] = "{}";
  }
};

Step 3 - Initialize the Application

We initialize the application by defining its namespace and MVC subnamespaces. Namespaces are an important concept in software engineering and many programming languages, including Java and PHP, provide specific support for namespaces, which help grouping related pieces of code and avoiding name conflicts. Since there is no specific support for namespaces in JavaScript, we use special objects for this purpose (we may call them "namespace objects"). First, we define a root namespace (object) for our app, and then we define three subnamespaces, one for each of the three parts of the application code: model, view and controller. In the case of our example app, we may use the following code for this:

var pl = { model:{}, view:{}, ctrl:{} }; 

Here, the main namespace is defined to be pl, standing for "Public Library", with the three subnamespaces model, view and ctrl being initially empty objects. We put this code in a separate file initialize.js in the ctrl folder, because such a namespace definition belongs to the controller part of the application code.

Step 4 - Implement the List Objects Use Case

This use case corresponds to the "Read" from the four basic data management use cases Create-Read-Update-Delete (CRUD).

The user interface for this use case is provided by the following HTML page containing an HTML table for displaying the book objects. For our example app, this page would be called listBooks.html (in the main folder publicLibrary) and would contain the following HTML code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal JS Frontend App Example</title>
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/Book.js"></script>
  <script src="src/view/listBooks.js"></script>
  <script>
   window.addEventListener( "load", pl.view.listBooks.setupUserInterface);
  </script>
</head>
<body>
  <h1>Public Library: List all books</h1>
  <table id="books">
    <thead><tr><th>ISBN</th><th>Title</th><th>Year</th></tr></thead>
    <tbody></tbody>
  </table>
  <nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>

Notice that this HTML file loads three JavaScript files: the controller file src/ctrl/initialize.js, the model file src/model/Book.js and the view file src/view/listBooks.js. The first two files contain the code for initializing the app and for the model class Book as explained above, and the third one, which represents the UI code of the "list books" operation, is developed now. In fact, for this operation, we just need a procedure for setting up the data management context and the UI, called setupUserInterface:

 pl.view.listBooks = {
  setupUserInterface: function () {
    var tableBodyEl = document.querySelector("table#books>tbody");
    var keys=[], key="", row={};
    // load all book objects
    Book.loadAllInstances();
    keys = Object.keys( Book.instances);
    // for each book, create a table row with a cell for each attribute
    for (var i=0; i < keys.length; i++) {
      key = keys[i];
      row = tableBodyEl.insertRow();
      row.insertCell(-1).textContent = Book.instances[key].isbn;      
      row.insertCell(-1).textContent = Book.instances[key].title;  
      row.insertCell(-1).textContent = Book.instances[key].year;
    }
  }
};

The simple logic of this procedure consists of two steps:

  1. Read the collection of all objects from the persistent data store (in line 6).
  2. Display each object as a row in a HTML table on the screen (in the loop starting in line 9).

More specifically, the procedure setupUserInterface first creates the book objects from the corresponding rows retrieved from Local Storage by invoking Book.loadAllInstances() and then creates the view table in a loop over all key-value slots of the map Book.instances where each value represents a book object. In each step of this loop, a new row is created in the table body element with the help of the JavaScript DOM operation insertRow(), and then three cells are created in this row with the help of the DOM operation insertCell(): the first one for the isbn property value of the book object, and the second and third ones for its title and year property values. Both insertRow and insertCell have to be invoked with the argument -1 for making sure that new elements are appended to the list of rows and cells.

Step 5 - Implement the Create Object Use Case

For a data management operation with user input, such as the "create object" operation, an HTML page with an HTML form is required as a user interface. The form has a form field for each attribute of the Book class. For our example app, this page would be called createBook.html (in the app folder publicLibrary) and would contain the following HTML code:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal JS Frontend App Example</title>    
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/Book.js"></script>
  <script src="src/view/createBook.js"></script>
  <script>
   window.addEventListener("load", pl.view.createBook.setupUserInterface);
  </script>
</head>
<body>
  <h1>Public Library: Create a new book record</h1>
  <form id="Book">
    <p><label>ISBN: <input name="isbn" /></label></p>
    <p><label>Title: <input name="title" /></label></p>
    <p><label>Year: <input name="year" /></label></p>
    <p><button type="button" name="commit">Save</button></p>
  </form>
  <nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>

The view code file src/view/createBook.js contains two procedures:

  1. setupUserInterface takes care of retrieving the collection of all objects from the persistent data store and setting up an event handler (handleSaveButtonClickEvent) on the save button for handling click button events by saving the user input data;

  2. handleSaveButtonClickEvent reads the user input data from the form fields and then saves this data by calling the Book.saveRow procedure.

pl.view.createBook = {
  setupUserInterface: function () {
    var saveButton = document.forms['Book'].commit;
    // load all book objects
    Book.loadAllInstances();
    // Set an event handler for the save/submit button
    saveButton.addEventListener("click", 
        pl.view.createBook.handleSaveButtonClickEvent);
    window.addEventListener("beforeunload", function () {
        Book.saveAllInstances(); 
    });
  },
  handleSaveButtonClickEvent: function () {
    var formEl = document.forms['Book'];
    var slots = { isbn: formEl.isbn.value, 
        title: formEl.title.value, 
        year: formEl.year.value};
    Book.createRow( slots);
    formEl.reset();
  }
};

Step 6 - Implement the Upate Object Use Case

Again, we have a user interface page (updateBook.html) and a view code file (src/view/updateBook.js). The HTML form for the UI of the "update object" operation has a selection field for choosing the book to be updated, and a form field for each attribute of the Book class. However, the form field for the standard identifier attribute (ISBN) is read-only because we do not allow changing the standard identifier of an existing object.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal JS Frontend App Example</title>
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/Book.js"></script>
  <script src="src/view/updateBook.js"></script>
  <script>
   window.addEventListener("load", pl.view.updateBook.setupUserInterface);
  </script>
</head>
<body>
  <h1>Public Library: Update a book record</h1>
  <form id="Book">
    <p>
      <label>Select book: 
        <select name="selectBook"><option value=""> --- </option></select>
      </label>
    </p>
    <p><label>ISBN: <input name="isbn" readonly="readonly" /></label></p>
    <p><label>Title: <input name="title" /></label></p>
    <p><label>Year: <input name="year" /></label></p>
    <p><button type="button" name="commit">Save Changes</button></p>
  </form>
  <nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>

The setupUserInterface procedure now has to set up a selection field by retrieveing the collection of all book objects from the persistent data store for populating the select element's option list:

pl.view.updateBook = {
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        saveButton = formEl.commit,
        selectBookEl = formEl.selectBook;
    var key="", keys=[], book=null, optionEl=null;
    // load all book objects
    Book.loadAllInstances();
    // populate the selection list with books
    keys = Object.keys( Book.instances);
    for (var i=0; i < keys.length; i++) {
      key = keys[i];
      book = Book.instances[key];
      optionEl = document.createElement("option");
      optionEl.text = book.title;
      optionEl.value = book.isbn;
      selectBookEl.add( optionEl, null);
    }
    // when a book is selected, populate the form with the book data
    selectBookEl.addEventListener("change", function () {
        var book=null, key = selectBookEl.value;
        if (key) {
          book = Book.instances[key];
          formEl.isbn.value = book.isbn;
          formEl.title.value = book.title;
          formEl.year.value = book.year;
        } else {
          formEl.isbn.value = "";
          formEl.title.value = "";
          formEl.year.value = "";
        }
    });
    saveButton.addEventListener("click", 
        pl.view.updateBook.handleUpdateButtonClickEvent);
    window.addEventListener("beforeunload", function () {
        Book.saveAllInstances(); 
    });
  },
  // save updated data
  handleUpdateButtonClickEvent: function () {
    var formEl = document.forms['Book'];
    var slots = { isbn: formEl.isbn.value, 
        title: formEl.title.value, 
        year: formEl.year.value
    };
    Book.updateRow( slots);
    formEl.reset();
  }
}; 

Step 7 - Implement the Delete Object Use Case

For the "delete object" use case, the UI form just has a selection field for choosing the book to be deleted:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Minimal JS Frontend App Example</title>
  <script src="src/ctrl/initialize.js"></script>
  <script src="src/model/Book.js"></script>
  <script src="src/view/deleteBook.js"></script>
  <script>
   window.addEventListener("load", pl.view.deleteBook.setupUserInterface);
  </script>
</head>
<body>
  <h1>Public Library: Delete a book record</h1>
  <form id="Book">
    <p>
      <label>Select book: 
        <select name="selectBook"><option value=""> --- </option></select>
      </label>
    </p>
    <p><button type="button" name="commit">Delete</button></p>
  </form>
  <nav><a href="index.html">Back to main menu</a></nav>
</body>
</html>

The view code in src/view/deleteBook.js consists of the following two procedures:

pl.view.deleteBook = {
  setupUserInterface: function () {
    var deleteButton = document.forms['Book'].commit;
    var selectEl = document.forms['Book'].selectBook;
    var key="", keys=[], book=null, optionEl=null;
    // load all book objects
    Book.loadAllInstances();
    keys = Object.keys( Book.instances);
    // populate the selection list with books
    for (var i=0; i < keys.length; i++) {
      key = keys[i];
      book = Book.instances[key];
      optionEl = document.createElement("option");
      optionEl.text = book.title;
      optionEl.value = book.isbn;
      selectEl.add( optionEl, null);
    }
    deleteButton.addEventListener("click", 
        pl.view.deleteBook.handleDeleteButtonClickEvent);
    window.addEventListener("beforeunload", function () {
        Book.saveAllInstances(); 
    });
  },
  handleDeleteButtonClickEvent: function () {
    var selectEl = document.forms['Book'].selectBook;
    var isbn = selectEl.value;
    if (isbn) {
      Book.deleteRow( isbn);
      selectEl.remove( selectEl.selectedIndex);
    }
  }
};

Run the App and Get the Code

You can run the minimal app from our server, and find more resources about web engineering, including open access books, on web-engineering.info.

Points of Variation

Instead of using the Local Storage API, the IndexDB API could be used for the persistent storage of the application data..

Points of Attention

The code of this app should be extended by

  • adding some CSS styling for the user interface pages and
  • adding constraint validation.

We show how to do this in the follow-up tutorial JavaScript Frontend Web Apps Tutorial Part 2: Adding Constraint Validation.

Another issue with the do-it-yourself code of this example app is the boilerplate code needed per class for the data storage management methods createRow, updateRow, etc. While it is good to write this code a few times for learning app development, you don't want to write it again and again later when you work on real projects. In Part 6 and Part 7 of our tutorial series, we will present an approach how to put these methods in a generic form in a meta-class ObjectType, such that they can be reused for all classes of an app.

History

  • 2 April 2014: First version created
  • 3 April 2014: Corrected typos, added link, added new subsection "HTML" and new section "Points of Variation"
  • 11 April 2014: Improved table formating, updated code, resolved mismatches between article and code
  • 12 May 2014: Updated hyperlinks
  • 6 October 2014: Updated hyperlinks, upload a diagram image, added final section "Points of Attention"

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gerd Wagner
Instructor / Trainer
Germany Germany
Researcher, developer, instructor and cat lover.
Follow on   Google+   LinkedIn

Comments and Discussions

 
GeneralNice article Pinmemberwicklowwanderer13-Oct-14 7:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 6 Oct 2014
Article Copyright 2014 by Gerd Wagner
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid