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):
Import classes, datatypes and utility procedures.
Load the required data from the database.
Set up a few general, use-case-independent UI elements.
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.
Create O: add event listeners
for the menu item Create in the Manage UI for populating the Create UI's choice widgets,
for responsive constraint validation per input field,
for the Save button for creating a new O record.
Update O: add event listeners
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,
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,
for responsive constraint validation per input field,
for the Save button for updating an existing O record.
Delete O: add event listeners
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,
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,
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:
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";
Load data:
Author.retrieveAll(); Publisher.retrieveAll(); Book.retrieveAll();
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
});
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.
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.