The example app's user interface (UI) for creating a new book record looks as in Figure 7.2 below.
Notice that the UI contains four choice widgets:
a single selection list for
the attribute originalLanguage,
a multiple selection list
for the attribute otherAvailableLanguages,
a radio button group for
the attribute category, and
a checkbox group for the
attribute publicationForms.
We use HTML selection lists for rendering the enumeration
attributes originalLanguage and
otherAvailableLanguages in the HTML forms in
createBook.html and
upateBook.html. Since the attribute
otherAvailableLanguages is multi-valued, we need a
multiple selection list for it, as
shown in the following HTML code:
<form id="Book"> <div><label>ISBN: <input name="isbn" type="text" /></label></div> <div><label>Title: <input name="title" type="text" /></label></div> <div><label>Original language: <select name="originalLanguage"></select> </label></div> <div class="multi-sel"><label>Also available in: <select name="otherAvailableLanguages" multiple="multiple" rows="4"></select> </label></div> ... <div><button type="submit" name="commit">Save </button></div> </form>
While we define the select container elements for
these selection lists in the HTML code of
createBook.html and
upateBook.html, we fill in their
option child elements dynamically in
v/createBook.mjs and
v/updateBook.mjs with the help of the utility
method fillSelectWithOptions.
In the case of a single select element, the user's
single-valued selection can be retrieved from the value
attribute of the select element, while in the case of a
multiple select element, the user's multi-valued selection
can be retrieved from the selectedOptions attribute of the
select element.
Notice that the div element containing the multiple
selection list for otherAvailableLanguages has the
class value "multi-sel", which is used for defining
specific CSS rules that adjust the element's size.
Since the enumeration attributes category and
publicationForms do not have more than seven possible
values, we can use a radio button
group and a checkbox group
for rendering them in an HTML-form-based UI. These choice widgets are
formed with the help of the container element fieldset and
its child element legend as shown in the following HTML
fragment:
<form id="Book"> ... <fieldset data-bind="category"> <legend>Category</legend></fieldset> <fieldset data-bind="publicationForms"> <legend>Publication forms</legend></fieldset> <div><button type="submit" name="commit">Save </button></div> </form>
Notice that we use a custom attribute data-bind for
indicating to which attribute of the underlying model class the choice
widget is bound.
In the same way as the option child elements of a
selection list, also the labeled input child
elements of a choice widget are created dynamically with the help of the
utility method createChoiceWidget in
v/createBook.js and
v/updateBook.js.
const formEl = document.forms["Book"],
origLangSelEl = formEl["originalLanguage"],
otherAvailLangSelEl = formEl["otherAvailableLanguages"],
categoryFieldsetEl = formEl.querySelector(
"fieldset[data-bind='category']"),
pubFormsFieldsetEl = formEl.querySelector(
"fieldset[data-bind='publicationForms']"),
saveButton = formEl["commit"];
// load all book records
Book.retrieveAll();
// set up the originalLanguage selection list
fillSelectWithOptions( origLangSelEl, LanguageEL.labels);
// set up the otherAvailableLanguages selection list
fillSelectWithOptions( otherAvailLangSelEl, LanguageEL.labels);
// set up the category radio button group
createChoiceWidget( categoryFieldsetEl, "category", [],
"radio", BookCategoryEL.labels, true);
// set up the publicationForms checkbox group
createChoiceWidget( pubFormsFieldsetEl, "publicationForms", [],
"checkbox", PublicationFormEL.labels);Notice that like a selection list implemented with the HTML
select element that provides the user's selection in the
value or selectedOptions attribute, our choice
widgets also need a DOM attribute that holds the user's single- or
multi-valued choice. We dynamically add a custom attribute
data-value to the choice widget's fieldset
element for this purpose in createChoiceWidget.
Since choice widgets do not allow arbitrary user input, we do not have to check constraints such as range constraints or pattern constraints on user input, but only mandatory value constraints. This allows simplifying responsive validation in the UI.
In our example app, the enumeration attributes
originalLanguage, category and
publicationForms are mandatory, while
otherAvailableLanguages is optional.
In the case of a mandatory
single-valued enumeration attribute like
originalLanguage rendered as a single selection list, we
can enforce a choice, and thus the mandatory value constraint, by not
offering an empty or void option among the option
sub-elements of the select element. If the attribute is
rendered as a radio button group, we can enforce a choice, and thus the
mandatory value constraint, in the create use case by initially setting the
checked attribute of the first radio button to
true and not allowing the user to directly uncheck a
button. In this way, if the user doesn't check any button, the first one
is the default choice.
In the case of an optional
single-valued enumeration attribute rendered as a
single-selection list, we need to include an empty or void option (e.g.,
in the form of a string like "---"). If the attribute is rendered as a
radio button group, we do not check any button initially and we need to
allow the user to directly uncheck a button with a mouse click in a
click event listener.
In the case of a mandatory
multi-valued enumeration attribute like
publicationForms rendered as a multiple-selection list or
checkbox group, we need to check if the user has chosen at least one
option. Whenever the user selects or unselects an option in a
select element, a change event is raised by
the browser, so we can implement the responsive mandatory value
constraint validation as an event listener for change
events on the select element, by testing if the list of
selectedOptions is empty. If the attribute is rendered as a
checkbox group, we need an event listener for click events
added on the fieldset element and testing if the widget's
value set is non-empty, as shown in the following example code fragment:
pubFormsFieldsetEl.addEventListener("click", function () {
const val = pubFormsFieldsetEl.getAttribute("data-value");
formEl.publicationForms[0].setCustomValidity(
(!val || Array.isArray(val) && val.length === 0) ?
"At least one publication form must be selected!":"" );
});Notice that the HTML5 constraint validation API does not allow to
indicate a constraint violation on a fieldset element (as
the container element of a choice widget). As a workaround, we use the
first checkbox element of the publicationForms choice
widget, which can be accessed with
formEl.publicationForms[0], for invoking the
setCustomValidity method that indicates a constraint
violation if its argument is a non-empty (message) string.
You can run the enumeration app from our server or download the code as a ZIP archive file.