6. Write the View Code

The user interface (UI) consists of a start page index.html that allows the user choosing one of the data management operations by navigating to the corresponding UI page such as retrieveAndListAllBooks.html or createBook.html in the app folder. The start page index.html has been discussed in Section 4.2.

We render the data management menu items in the form of buttons. For simplicity, we invoke the Book.clearData() and Book.createTestData() methods directly from the buttons' onclick event handler attribute. Notice, however, that it is generally preferable to register such event handling functions with addEventListener(...), as we do in all other cases.

6.1. The data management UI pages

Each data management UI page loads the same basic CSS and JavaScript files like the start page index.html discussed above. In addition, it loads a use-case-specific view code file src/v/useCase.js and then sets the setupUserInterface procedure of the use-case as an event handler for the page load event, which takes care of initializing the use case when the UI page has been loaded.

6.2. Initialize the app

For initializing the app, its namespace and MVC sub-namespaces have to be defined. For our example app, the main namespace is defined to be pl, standing for "Public Library", with the three sub-namespaces m, v and c being initially empty objects:

var pl = { m:{}, v:{}, c:{} };

We put this code in the file initialize.js in the c folder.

6.3. Set up the user interface

For setting up the user interfaces of the data management use cases, we have to distinguish the case of "Retrieve/List All" from the other ones (Create, Update, Delete). While the latter ones require using an HTML form and attaching event handlers to form controls, in the case of "Retrieve/List All" we only have to render a table displaying all books, as in the case of the Minimal App discussed in Chapter 3.

For the Create, Update and Delete use cases, we need to add event listeners for:

  1. responsive validation on form field input events,

  2. handling the event when the user clicks (or pushes) the save (or delete) button,

  3. making sure the main memory data is saved when a beforeunload event occurs, that is, when the browser window/tab is closed.

For the use case Create, we obtain the following code (in v/createBook.js)::

pl.v.createBook = {
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        saveButton = formEl.commit;
    // load all book records
    Book.retrieveAll();
    // add event listeners for responsive validation
    formEl.isbn.addEventListener("input", function () { 
        formEl.isbn.setCustomValidity( 
            Book.checkIsbnAsId( formEl.isbn.value).message);
    });
    formEl.title.addEventListener("input", function () { 
        formEl.title.setCustomValidity( 
            Book.checkTitle( formEl.title.value).message);
    });
    formEl.year.addEventListener("input", function () { 
        formEl.year.setCustomValidity( 
            Book.checkYear( formEl.year.value).message);
    });
    formEl.edition.addEventListener("input", function () { 
        formEl.edition.setCustomValidity( 
            Book.checkEdition( formEl.edition.value).message);
    });
    ...
  }
};

Notice that for each input field we add a listener for input events, such that on any user input a validation check is performed because input events are created by user input actions such as typing. We use the predefined function setCustomValidity from the HTML5 form validation API for having our property check functions invoked on the current value of the form field and returning an error message in the case of a constraint violation. So, whenever the string represented by the expression Book.checkIsbn( formEl.isbn.value).message is empty, everything is fine. Otherwise, if it represents an error message, the browser indicates the constraint violation to the user by rendering a red outline for the form field concerned (due to our CSS rule for the :invalid pseudo class).

In addition to the event handlers for responsive constraint validation, we need two more event handlers:

pl.v.createBook = {
  setupUserInterface: function () {
    ...
    // Set an event handler for the submit/save button
    saveButton.addEventListener("click", 
        pl.v.createBook.handleSaveButtonClickEvent);
    // neutralize the submit event
    formEl.addEventListener( 'submit', function (e) { 
        e.preventDefault();
        formEl.reset();
    });
    // when the browser window/tab is closed
    window.addEventListener("beforeunload", Book.saveAll);
  },
  handleSaveButtonClickEvent: function () {...}
};

While the validation on user input enhances the usability of the UI by providing immediate feedback to the user, validation on form data submission is even more important for catching invalid data. Therefore, the event handler handleSaveButtonClickEvent() performs the property checks again with the help of setCustomValidity, as shown in the following program listing:

handleSaveButtonClickEvent: function () {
  var formEl = document.forms['Book'];
  var slots = {isbn: formEl.isbn.value, 
               title: formEl.title.value,
               year: formEl.year.value};
  // set error messages in case of constraint violations
  formEl.isbn.setCustomValidity( 
      Book.checkIsbnAsId( slots.isbn).message);
  formEl.title.setCustomValidity( 
      Book.checkTitle( slots.title).message);
  formEl.year.setCustomValidity( 
      Book.checkYear( slots.year).message);
  if (formEl.edition.value) {
    slots.edition = formEl.edition.value;
    formEl.edition.setCustomValidity( 
        Book.checkEdition( slots.edition).message);
  }
  // save the input data only if all input fields are valid
  if (formEl.checkValidity()) Book.add( slots);
}

By invoking checkValidity() on the form element, we make sure that the form data is only saved (by Book.add), if there is no constraint violation. After this handleSaveButtonClickEvent handler has been executed on an invalid form, the browser takes control and tests if the predefined property validity has an error flag for any form field. In our approach, since we use setCustomValidity, the validity.customError would be true. If this is the case, the custom constraint violation message will be displayed (in a bubble) and the submit event will be suppressed.

In the UI of the use case Update, which is handled in v/updateBook.js, we do not have an input, but rather an output field for the standard identifier attribute isbn, since it is not supposed to be modifiable. Consequently, we don't need to validate any user input for it. However, we need to set up a selection list (in the form of an HTML select element) allowing the user to select a learning unit in the first step, before its data can be modified. This requires to add a change event listener on the select element such that the fields of the UI can be filled with the data of the selected object.

pl.v.updateBook = {
  setupUserInterface: function () {
    var formEl = document.forms['Book'],
        submitButton = formEl.commit,
        selectBookEl = formEl.selectBook;
    // set up the book selection list
    util.fillSelectWithOptions( Book.instances, 
    selectBookEl, "isbn", "title");
    // when a book is selected, fill the form with its data
    selectBookEl.addEventListener("change", function () {
      var book=null, bookKey = selectBookEl.value;
      if (bookKey) {  // set form fields and reset CustomValidity
        book = Book.instances[bookKey];
        ["isbn","title","year","edition"].forEach( function (p) {
          formEl[p].value = book[p] !== undefined ? book[p] : "";
          formEl[p].setCustomValidity("");  // no error
        });
      } else formEl.reset();
    });
    // add event listeners for responsive validation
    ...
    // Set an event handler for the submit/save button
    ...
    // neutralize the submit event
    ...
    // Set a handler for the event when the browser window/tab is closed
    ...
  },
  handleSaveButtonClickEvent: function () {...}
};

There is no need to set up responsive validation for the standard identifier attribute isbn, but for all other form fields, as shown above for the Create use case.

The logic of the setupUserInterface method for the Delete use case is similar. We only need to take care that the object to be deleted can be selected by providing a selection list, like in the Update use case. No validation is needed for the Delete use case.