6. Write the View and Controller Code

The examle app's user interface for creating a new book record looks as in Figure 9.3 below.

Figure 9.3. The user interface for creating a new book record with ISBN, title and four enumeration attributes

The user interface for creating a new book record with ISBN, title and four enumeration attributes

6.1. Selection lists

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:

<body>
  <h1>Public Library: Create a new book record</h1>
  <form id="Book" class="pure-form pure-form-aligned">
    <div class="pure-control-group">
      <label for="isbn">ISBN</label>
      <input id="isbn" name="isbn" />
    </div>
    <div class="pure-control-group">
      <label for="title">Title</label>
      <input id="title" name="title" />
    </div>
    <div class="pure-control-group">
      <label for="ol">Original language</label>
      <select id="ol" name="originalLanguage"></select>
    </div>
    <div class="pure-control-group">
      <label for="oal">Other available languages</label>
      <select id="oal" name="otherAvailableLanguages" 
              multiple="multiple"></select>
    </div>
    ...
  </form>
</body>

While we define the select container elements for these selection lists in createBook.html and upateBook.html, we fill in their option child elements dynamically in the setupUserInterface methods in view/createBook.js and view/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.

6.2. Choice widgets

Since the enumeration attributes category and publicationForms have not 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. Such choice widgets are formed with the help of the container element fieldset as shown in the following HTML fragment:

<body>
  <h1>Public Library: Create a new book record</h1>
  <form id="Book" class="pure-form pure-form-aligned">
    ...
    <fieldset class="pure-controls" data-bind="category">
      <legend>Category</legend>
    </fieldset>
    <fieldset class="pure-controls" data-bind="publicationForms">
      <legend>Publication forms</legend>
    </fieldset>
    <div class="pure-controls">
      <p><button type="submit" name="commit">Save</button></p>
      <nav><a href="index.html">Back to main menu</a></nav>
    </div>
  </form>
</body>

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 util.createChoiceWidget in the setupUserInterface methods in view/createBook.js and view/updateBook.js. Like a selection list implemented with the HTML select element that povides 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.

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']"),
      submitButton = 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);
  ...
},    

6.3. Responsive validation for selection lists and choice widgets

Since selection lists and 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. In our example app the enumeration attributes originalLanguage, category and publicationForms are mandatory, while otherAvailableLanguages is optional.

For selection lists, whenever a new selection is made by the user, a change event is raised by the browser, so we declare the following mandatory value check as an event listener for change events on the select element:

setupUserInterface: function () {
  ...
  // add event listeners for responsive validation
  formEl.title.addEventListener("input", function () { 
      formEl.title.setCustomValidity( 
          Book.checkTitle( formEl.title.value).message);
  });
  // simplified validation: check only mandatory value
  origLangSelEl.addEventListener("change", function () {
    origLangSelEl.setCustomValidity( 
        (!origLangSelEl.value) ? "A value must be selected!":"" );
  });
  // simplified validation: check only mandatory value
  categoryFieldsetEl.addEventListener("click", function () {
    formEl.category[0].setCustomValidity( 
        (!categoryFieldsetEl.getAttribute("data-value")) ? 
            "A category must be selected!":"" );
  });
  // simplified validation: check only mandatory value
  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!":"" );
  });
  ...
},