6. Code the View and Controller

The user interface (UI) consists of a start page index.html that allows navigating to data management UI pages, one for each object type (in our example, books.html and publishers.html). Each of these data management UI pages contains 5 sections: a Manage section, like Manage books, with a menu for choosing a CRUD use case, and a section for each CRUD use case, like Retrieve/list all books, Create book, Update book and Delete book, such that only one of them is displayed at any time (for instance, by setting the CSS property display:none for all others).

6.1. Initialize the app

For initializing a data management use case, the required data (for instance, all publisher and book records) have to be loaded from persistent storage. This is performed in a controller procedure such as pl.c.books.manage.initialize in c/books.js with the following code:

pl.c.books.manage = {
  initialize: function () {
    Publisher.retrieveAll();
    Book.retrieveAll();
    pl.v.books.manage.setUpUserInterface();
  }
};

The initialize method for managing book data first loads the publishers table and the books table since the book data management UI needs to show their data. Then the book data management menu is rendered by calling the setUpUserInterface procedure.

6.2. Showing associated objects in Retrieve/List All

In our example, we have only one reference property, Book::publisher, which is functional and optional. For showing information about the optional publisher of a book in the Retrieve/list all use case, the corresponding cell in the HTML table is filled with the name of the publisher, if there is any:

pl.v.books.retrieveAndListAll = {
  setupUserInterface: function () {
    const tableBodyEl = document.querySelector(
	                  "section#Book-R>table>tbody");
    tableBodyEl.innerHTML = "";  // drop old contents
    for (let key of Object.keys( Book.instances)) {
      const book = Book.instances[key];
      const row = tableBodyEl.insertRow(-1);
      row.insertCell(-1).textContent = book.isbn;
      row.insertCell(-1).textContent = book.title;
      row.insertCell(-1).textContent = book.year;
      // if the book has a publisher, show its name
      row.insertCell(-1).textContent = 
	      book.publisher ? book.publisher.name : "";
    }
    document.getElementById("Book-M").style.display = "none";
    document.getElementById("Book-R").style.display = "block";
  }
};

For a multi-valued reference property, the table cell would have to be filled with a list of all associated objects referenced by the property.

6.3. Selecting associated objects in the Create and Update use cases

For allowing to select objects to be associated with the currently edited object from in the Create and Update use cases, an HTML selection list (i.e., a select element) is populated with option elements formed from the instances of the associated object type with the help of a utility method fillSelectWithOptions. The HTML select element is defined in the books.html view file:

<section id="Book-C" class="UI-Page">
 <h1>Public Library: Create a new book record</h1>
 <form>
  ... 
  <div class="select-one">
   <label>Publisher: <select name="selectPublisher"></select></label>
  </div>
  ...
 </form>
</section>

The Create user interface is set up by the following procedure:

pl.v.books.create = {
  setupUserInterface: function () {
    const formEl = document.querySelector("section#Book-C > form"),
        selectPublisherEl = formEl.selectPublisher,
        saveButton = formEl.commit;
    // add event listeners for responsive validation 
    formEl.isbn.addEventListener("input", function () {
      formEl.isbn.setCustomValidity( 
          Book.checkIsbnAsId( formEl.isbn.value).message);
    });
    // set up a single selection list for selecting a publisher
    util.fillSelectWithOptions( selectPublisherEl,
	      Publisher.instances, "name");
    // define event handler for submitButton click events    
    saveButton.addEventListener("click", this.handleSaveButtonClickEvent);
    // define event handler for neutralizing the submit event
    formEl.addEventListener("submit", function (e) { 
      e.preventDefault();
      formEl.reset();
    });
    // replace the manage form with the create form
    document.getElementById("Book-M").style.display = "none";
    document.getElementById("Book-C").style.display = "block";
    formEl.reset();
  },
  handleSaveButtonClickEvent: function () {
    ...
  }
};

When the user clicks (or touches) the save button, all form control values, including the value of the select control, are copied to a slots list, which is used as the argument for invoking the add method after all form fields have been checked for validity, as shown in the following program listing:

handleSaveButtonClickEvent: function () {
    const formEl = document.querySelector("section#Book-C > form");
    const slots = {
        isbn: formEl.isbn.value, 
        title: formEl.title.value,
        year: formEl.year.value,
        publisher_id: formEl.selectPublisher.value
    };
    // validate all form controls and show error messages 
    formEl.isbn.setCustomValidity( 
	    Book.checkIsbnAsId( slots.isbn).message);
    /* ... (do the same with title and year) */
    // save the input data only if all form fields are valid
    if (formEl.checkValidity()) {
      Book.add( slots);
    }
}

The setupUserInterface code for the update book use case is similar.