5. Code the View

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), and one data management UI code file for each object type (in our example, books.mjs and publishers.mjs). Each data management UI page 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 (by setting the CSS property display:none for all others).

Each UI code file for managing the data of an object type O has the following parts (code blocks):

  1. Import classes, datatypes and utility procedures.

  2. Load the required data from the database.

  3. Set up a few general, use-case-independent UI elements.

  4. Retrieve O: add an event listener for the menu item Retrieve all in the Manage UI for creating, and activating, the table view in the Retrieve UI.

  5. Create O: add event listeners

    1. for the menu item Create in the Manage UI for populating the Create UI's choice widgets,

    2. for responsive constraint validation per input field,

    3. for the Save button for creating a new O record.

  6. Update O: add event listeners

    1. for the menu item Update in the Manage UI for populating the Update UI's select element, which allows selecting the O record to be updated,

    2. for O selection events (more precisely, for change events of the select field) for filling out the Update UI's fields with the property values of the selected O,

    3. for responsive constraint validation per input field,

    4. for the Save button for updating an existing O record.

  7. Delete O: add event listeners

    1. for the menu item Delete in the Manage UI for populating the Update UI's select element, which allows selecting the O record to be updated,

    2. for O selection events (more precisely, for change events of the select field) for filling out the Delete UI's fields with the property values of the selected O,

    3. for the Delete button for deleting an existing O record.

For instance, in books.mjs, for managing book data, we have the following first three code blocks:

  1. Import classes, datatypes and utility procedures:

    import Author from "../m/Author.mjs";
    import Publisher from "../m/Publisher.mjs";
    import Book from "../m/Book.mjs";
    import { fillSelectWithOptions, createListFromMap, 
        createMultipleChoiceWidget } from "../../lib/util.mjs";
  2. Load data:

    Author.retrieveAll();
    Publisher.retrieveAll();
    Book.retrieveAll();
  3. Set up general, use-case-independent UI elements:

    // set up back-to-menu buttons for all CRUD UIs
    for (const btn of document.querySelectorAll("button.back-to-menu")) {
      btn.addEventListener("click", refreshManageDataUI);
    }
    // neutralize the submit event for all CRUD UIs
    for (const frm of document.querySelectorAll("section > form")) {
      frm.addEventListener("submit", function (e) {
        e.preventDefault();
        frm.reset();
      });
    }
    // save data when leaving the page
    window.addEventListener("beforeunload", Book.saveAll);

In books.html, there is the following menu for choosing a CRUD operation:

<section id="Book-M" class="UI-Page">
  <h1>Manage book data</h1>
  <ul class="menu">
	<li><button type="button" id="RetrieveAndListAll">Retrieve/list 
            all book records</button></li>
	<li><button type="button" id="Create">Create 
          a new book record</button></li>
	<li><button type="button" id="Update">Update 
            a book record</button></li>
	<li><button type="button" id="Delete">Delete 
            a book record</button></li>
  </ul>
  <div class="button"><a href="index.html">Back to Main menu</a></div>
</section>

For each of these CRUD buttons we add an event listener that takes care of setting up the corresponding UI. For instance, for "Retrieve/list all", we have the following code in books.mjs:

document.getElementById("RetrieveAndListAll")
    .addEventListener("click", function () {
  document.getElementById("Book-M").style.display = "none";
  document.getElementById("Book-R").style.display = "block";
  ...  // set up the UI for Retrieve/list all
});

5.1. Setting up the Retrieve/List All user interface

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

const tableBodyEl = document.
      querySelector("section#Book-R > table > tbody");
tableBodyEl.innerHTML = "";  // drop old content
for (const key of Object.keys( Book.instances)) {
  const book = Book.instances[key];
  const row = tableBodyEl.insertRow();
  row.insertCell().textContent = book.isbn;
  row.insertCell().textContent = book.title;
  row.insertCell().textContent = book.year;
  // if the book has a publisher, show its name
  row.insertCell().textContent =
    book.publisher ? book.publisher.name : "";
}

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.

5.2. Setting up the Create and Update user interfaces

For allowing to select associated objects in the Create and Update user interfaces, a selection list (i.e., a HTML 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 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 UI is set up by populating a selection list for selecting the publisher with the help of a utility method fillSelectWithOptions as shown in the following program listing:

const createFormEl = document.querySelector("section#Book-C > form");
const selectPublisherEl = createFormEl.selectPublisher;
document.getElementById("Create").addEventListener("click", function () {
  document.getElementById("Book-M").style.display = "none";
  document.getElementById("Book-C").style.display = "block";
  // set up a single selection list for selecting a publisher
  fillSelectWithOptions( selectPublisherEl, Publisher.instances, "name");
  createFormEl.reset();
});
// set up event handlers for responsive constraint validation
...
// handle Save button click events
createFormEl["commit"].addEventListener("click", function () {
  ...
});

When the user pushes the Save button, all form control values, including the value of the select field, are copied to a slots record, 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:

// handle Save button click events
createFormEl["commit"].addEventListener("click", function () {
  const slots = {
    isbn: createFormEl.isbn.value,
    title: createFormEl.title.value,
    year: createFormEl.year.value,
    publisher_id: createFormEl.selectPublisher.value
  };
  // check all input fields and show error messages
  createFormEl.isbn.setCustomValidity(
      Book.checkIsbnAsId( slots.isbn).message);
  // save the input data only if all form fields are valid
  if (createFormEl.checkValidity()) {
    Book.add( slots);
  }
});

The code for setting up the Update user interface is similar.