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.
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
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.
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
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
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
For instance, in our example app, we have the integer-valued
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".
<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
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>
input elements as child elements of their
label elements doesn't seem very logical. Rather, one would
label to be a child of an
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.
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:
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.
Select the same book on both tabs, such that you see its data in the Update view.
Change one data item of this book on one of the tabs and save your change.
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.
From an architectural point of view, it is important to keep the app's model classes independent of
the user interface (UI) code because it should be possible to re-use the same model classes with different UI technologies;
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
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
IndexedDB API or remote data storage