9. Points of Attention

9.1. Catching invalid data

The app discussed in this chapter is limited to support the minimum functionality of a data management app only. It does not take care of preventing users from entering invalid data into the app's database. In Chapter 5, we show how to express integrity constraints in a model class, and how to perform data validation both in the model/storage code of the app and in the HTML5-based user interface.

9.2. Database size and memory management

Notice that in this we have made the assumption that all application data can be loaded into main memory (like all book data is loaded into the map Book.instances). This approach only works in the case of local data storage of smaller databases, say, with not more than 2 MB of data, roughly corresponding to 10 tables with an average population of 1000 rows, each having an average size of 200 Bytes. When larger databases are to be managed, or when data is stored remotely, it's no longer possible to load the entire population of all tables into main memory, but we have to use a technique where only parts of the table contents are loaded.

9.3. Boilerplate code

Another issue with the do-it-yourself code of this example app is the boilerplate code needed per model class for the data storage management methods add, retrieve, update, and destroy. While it is good to write this code a few times for learning app development, you don't want to write it again and again later when you work on real projects. In we present an approach how to put these methods in a generic form in a meta-class, such that they can be reused in all model classes of an app.

9.4. Serializing and de-serializing attribute values

Serializing an attribute value means to convert it to a suitable string value. For standard datatypes, such as numbers, a standard serialization is provided by the predefined conversion function String. When a string value, like "13" or "yes", represents the value of a non-string-valued attribute, it has to be de-serialized, that is, converted to the range type of the attribute, before it is assigned to the attribute. This is the situation, for instance, when a user has entered a value in a form input field for an integer-valued attribute. The value of the form field is of type string, so it has to be converted (de-serialized) to an integer using the predefined conversion function parseInt.

For instance, in our example app, we have the integer-valued attribute year. When the user has entered a value for this attribute in a corresponding form field, in the Create or Update user interface, the form field holds a string value, which has to be converted to an integer in an assignment like the following:

this.year = parseInt( formEl.year.value);

One important question is: where should we take care of de-serialization: in the "view" (before the value is passed to the "model" layer), or in the "model"? Since attribute range types are a business concern, and the business logic of an app is supposed to be encapsulated in the "model", de-serialization should be performed in the "model" layer, and not in the "view".

9.5. Implicit versus explicit form field labels

The explicit labeling of form fields requires to add an id value to the input element and a for-reference to its label element as in the following example:

<div>
  <label for="isbn">ISBN:</label>
  <input id="isbn" name="isbn" />
</div>

This technique for associating a label with a form field is getting quite inconvenient when we have many form fields on a page because we have to make up a great many of unique id values and have to make sure that they don't conflict with any of the id values of other elements on the same page. It's therefore preferable to use an approach, called implicit labeling, where these id references are not needed. In this approach, the input element is a child element of its label element, as in

<div>
  <label>ISBN: <input name="isbn" /></label>
</div>

Having input elements as child elements of their label elements doesn't seem very logical. Rather, one would expect the label to be a child of an input element. But that's the way it is defined in HTML5.

A small disadvantage of using implicit labels may be the lack of support by certain CSS libraries. In the following parts of this tutorial, we will use our own CSS styling for implicitly labeled form fields.

9.6. Synchronizing views with the model

When an app is used by more than one user at the same time, we have to take care of somehow synchronizing the possibly concurrent read/write actions of users such that users always have current data in their "views" and are prevented from interfering with each other. This is a very difficult problem, which is attacked in different ways by different approaches. It has been mainly investigated for multi-user database management systems and large enterprise applications built on top of them.

The original MVC proposal included a data binding mechanism for automated one-way model-to-view synchronization (updating the model's views whenever a change in the model data occurs). We didn't take care of this in our minimal app because a front-end app with local storage doesn't really have multiple concurrent users. However, we can create a (rather artificial) situation that illustrates the issue:

  1. Open the Update UI page of the minimal app twice (for instance, by opening updateBook.html twice), such that you get two browser tabs rendering the same page.

  2. Select the same book on both tabs, such that you see its data in the Update view.

  3. Change one data item of this book on one of the tabs and save your change.

  4. When you now go to the other tab, you still see the old data value, while you may have expected that it would have been automatically updated.

A mechanism for automatically updating all views of a model object whenever a change in its property values occurs is provided by the observer pattern that treats any view as an observer of its model object. Applying the observer pattern requires that (1) model objects can have a multi-valued reference property like observers, which holds a set of references to view objects; (2) a notify method can be invoked on view objects by the model object whenever one of its property values is changed; and (3) the notify method defined for view objects takes care of refreshing the user interface.

Notice, however, that the general model-view synchronization problem is not really solved by automatically updating all (other users') views of a model object whenever a change in its data occurs. Because this would only help, if the users of these views didn't make themselves any change of the data item concerned, meanwhile. Otherwise, their changed data value would be overwritten by the automated refresh, and they may not even notice this, which is not acceptable in terms of usability.

9.7. Architectural separation of concerns

From an architectural point of view, it is important to keep the app's model classes independent of

  1. the user interface (UI) code because it should be possible to re-use the same model classes with different UI technologies;

  2. the storage management code because it should be possible to re-use the same model classes with different storage technologies.

In this chapter, we have kept the model class Book independent of the UI code, since it does not contain any references to UI elements, nor does it invoke any view method. However, for simplicity, we don't keep it independent of storage management code, since we include the method definitions for add, update, destroy, etc., which invoke the storage management methods of JavaScrpt's localStorage API. Therefore, the separation of concerns is only incomplete in our apps, and a possible improvement would be to define abstract storage management methods in a special storage manager class, which is complemented by libraries of concrete storage management methods (called storage adapters) for specific storage technologies such as JavaScrpt's IndexedDB API or remote data storage services.