The example app's user interface (UI) for creating a new book record looks as in Figure 11.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 the
setupUserInterface
methods in
v/createBook.js
and
v/updateBook.js
with the help of the utility method
util.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 label
ed input
child
elements of a choice widget are created dynamically with the help of the
utility method util.createChoiceWidget
in the
setupUserInterface
methods in
v/createBook.js
and
v/updateBook.js
.
setupUserInterface: function () { var 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; // set up the originalLanguage selection list util.fillSelectWithOptions( origLangSelEl, LanguageEL.labels); // set up the otherAvailableLanguages selection list util.fillSelectWithOptions( otherAvailLangSelEl, LanguageEL.labels); // set up the category radio button group util.createChoiceWidget( categoryFieldsetEl, "category", [], "radio", BookCategoryEL.labels); // set up the publicationForms checkbox group util.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 util.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:
setupUserInterface: function () {
...
// check mandatory value constraint for checkbox group
pubFormsFieldsetEl.addEventListener("click", function () {
var 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.