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.
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.
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.
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:
responsive validation on form field input
events,
handling the event when the user clicks (or pushes) the save (or delete) button,
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.