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 8, 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
chapter, 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.
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
Volume 2,
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.
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".
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.
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
updateLearningUnit.html
twice), such that you
get two browser tabs rendering the same page.
Select the same learning unit on both tabs, such that you see its data in the Update view.
Change one data item of this learning unit 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 didn't keep it independent of storage
management code, since we have included 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 incomplete in our minimal example app.
We show in Volume 2 how to achieve a more complete separation of concerns by defining abstract storage management methods in a special storage manager class, which is complemented by libraries of concrete storage management methods for specific storage technologies, called storage adapters.