Documentation

1 Getting Started
2 Design Overview
3 Page Controller Boot Process
4 Your First App
5 Page Controller
6 Modules
7 Components
8 jqcEmpty
9 jqcLabel
10 jqcButton
11 jqcLayer
12 jqcPanelDeck
13 jqcAccordion
14 jqcProgressBar
15 Data Binding & Remote Data Synchronization
16 jqcDataService
17 jqcDataModel
18 jqcViewModel
19 jqcList
20 jqcTable
21 jqcForm
22 jqcResponsiveLayoutManager
23 jqcTreeList
24 Demos

Data Binding & Remote Data Synchronization


This document matches version 1.6.0 .

Many web applications need to work with data in one way or another. They may need to load data from a remote data service, display the data in a table, enable the user to edit the data in a form, and send the edited data back to the remote data service.

Working with data is one of the more comprehensive aspects of jqComponents. You will need to spend some time learning it. But don't worry, this text contains lots of diagrams explaining the common data flows and how they are handled.

The text starts with a short overview of the core data components in jqComponents, followed by an explanation of how data binding works in the GUI components, and then continues to explain full data flows with remote data synchronization.

Core Data Components

The core data concepts in jqComponents are:

In jqComponents these concepts are represented by these components:

The jqcDataService, jqcDataModel and jqcForm you will have to insert into your app explicitly. The jqcViewModel objects are created internally in the GUI components that are data bound, so you will typically not create a jqcViewModel yourself (unless you implement your own data bound components).

Each of these concepts and components will be explained in more detail in the following sections.

Data Binding

Data binding in jqComponents is not a universal feature like it is in AngularJS, Knockout, Handlebars etc. Instead data binding is isolated to the GUI components that display data. In the latest version these components are:

A data bound component is typically capable of displaying one or more data objects. These data objects are just standard JavaScript objects.

A data bound component uses a view model to contain the data objects to display. A view model is an instance of jqcViewModel. The GUI components create the jqcViewModel internally, so you won't have to do that.

A view model contains a list of view model objects. One view model object for each data object to display. Each view model object contains a reference to the data object it represents as well as meta data about the visual representation of that data object.

Here is a diagram illustrating the data binding of a GUI component with the view model, view model objects and data objects:

The data binding design of data bound GUI components in jqComponents.

For instance, the jqcTable shows one row in a table per data object. For each data object a view model object is created inside the view model of the jqcTable. Each view model object corresponds to one row in the table. Each view model object contains a reference to the data object displayed in the corresponding row, a reference to the HTML element which displays the row, and some meta data about whether the row is selected or not.

Each column in the table is mapped to a field (property) of the data objects kept in the view model objects. Exactly how a GUI component maps fields from the data objects to what is rendered is different from component to component, so it is left out of this text.

Keeping an array of view model objects internally instead of the data object array directly makes it possible for the GUI component to filter and sort the view model object array without affecting the original data object array. The separate view model array also makes it possible for the GUI components to store extra meta data for each data object (like if it is selected, expanded etc.). Thus, if multiple GUI components show the same data objects, they can sort, filter and store meta data independently without affecting each other.

Here is a code example that shows how to interact with a data bound GUI component:

<ul id="theList" jqc-type="jqcList" ></ul>

<span jqc-module="bootModule"></span>



<script>
    function bootModule() {
        var module = {};

        module.postConfigure = function() {
            var list = module.jqc.pageController.components.theList;

            list.labelField("title");


            var data = [
                { id : "1", title : "Friday the 13th" }
                ,{ id : "2", title : "Nightmare on Elmstreet" }
            ];

            list.viewModel.setData(data);
        }


        return module;
    }
</script>    

The data array contains the raw data objects to display. The call to list.viewModel.setData() sets the data on the view model of the jqcList component. Setting data on the view model causes the view model to create view model objects for each data object internally, and finally causes the GUI component to re-render.

View Model

The jqcViewModel is covered in more detail in the text about the jqcViewModel, but I will cover the basics here.

View model objects (jqcViewModel) contains these functions:

setData(dataArray)

addData(dataArray)

updateData(dataArray)

removeData(dataArray)

idField(idFieldName)

The setData(dataArray) function sets the data to be displayed on the view model. All data kept internally will be deleted.

The addData(dataArray) function adds the data objects in the data array passed as parameters to the data objects already kept in the view model.

The updateData(dataArray) function replaces the data objects kept internally with the data objects in the data array parameter. The data objects are matched up based on their unique id, so each data object needs a unique id for this to work, and the view model object needs to know the name of the property on the data objects that contain this id. You set that via the idField() function.

The removeData(dataArray) funtion removes the data objects kept internally which have the same ids as the data objects in the data object array passed as parameter to the function. Again, this requires that you have set the name of the data object property containing the unique id for the data object via the idField() function.

The idField(idFieldName) function sets the name of the field (property) that contains the unique id of the data objects. For updateData() and removeData() to work properly the view model needs to know the name of the property on the data objects which contain their unique id. If no id is set, the name of the id field defaults to id.

Data Model

The jqcDataModel is covered in more detail in the text about the jqcDataModel, but I will cover the basics here.

Sometimes you have multiple GUI components that need to show the same data objects. You can call setData(), addData(), updateData() and removeData() directly on the view model of the GUI components. This will make all the GUI components able to show the same data objects.

You can also use a data model (jqcDataModel) as an intermediary data object repository. The jqcDataModel contains the same five functions as the jqcViewModel and they work in the same way. Furthermore, the jqcDataModel contains an addListener() function which can add a listener object to the data model. Whenever the data model changes, all listeners are notified.

The listener object should contain the same four functions as the view model and data model has:

Each of these four functions will be called when the corresponding function is called on the jqcDataModel.

Since the jqcViewModel already has these four functions, a view model can be registered as a listener on a data model. Here is a diagram illustrating the object wiring when using an intermediary data model with multiple view models listening for updates:

Multiple view models listening for changes on the same data model.

Here is the code example from above, modified to use a data model:

<span id="theDataModel" jqc-type="jqcDataModel" ></span>

<ul id="theList" jqc-type="jqcList" ></ul>

<span jqc-module="bootModule"></span>

<script>
    function bootModule() {
        var module = {};

        module.postConfigure = function() {
            var list      = module.jqc.pageController.components.theList;
            var dataModel = module.jqc.pageController.components.theDataModel;

            //dataModel.idField("id"); //not necessary, defaults to "id"
            dataModel.addListener(list.viewModel);

            list.labelField("title");
            //list.viewModel.idField("id"); //not necessary, defaults to "id"

            var data = [
                { id : "1", title : "Friday the 13th" }
                ,{ id : "2", title : "Nightmare on Elmstreet" }
            ];

            dataModel.setData(data);
        }


        return module;
    }
</script>

The jqcDataModel is optional to use. If you feel it complicates your app design, leave it out.

Editing Data

You may need data displayed in data bound GUI to be editable. For a data object to be editable, the HTML element showing the data object must be selectable or clickable. Rows in a jqcTable are both selectable and clickable, so the jqcTable is ideal to display data the user needs to be able to edit.

Once you have obtained the data object to edit, you typically pass it to a form so the properties of the data object can be edited in the form fields. If you use a jqcForm then this can be quite easy.

Forms

The jqcForm is covered in more detail in the text about the jqcForm, but I will cover the basics here.

Forms in jqComponents are rather simple. Some frameworks try to make forms and form validation too intelligent, which works fine for the simple cases, but often ends up getting in your way for the more advanced cases. Therefore the built-in form functionality in jqComponents is kept to the minimum.

The core functions of a jqcForm component are:

setFieldValues(dataObject);

getFieldValues(dataObject);

validate();

When a user selects a data object for editing, your app will call the setFieldValues(dataObject). The dataObject parameter is the data object to edit. The form will match the property names of the data object to the ids or names of the form fields to determine what property values to put into which form fields.

When the user clicks "save" your app will call validate(). This function returns a validation state object which tells if the form is valid. The form validates the form fields using a set of form validators which you give to the form at configuration time.

If the form is not valid your app will notify the user.

If the form is valid your app will call getFieldValues(dataObject). The dataObject parameter is an object into which the form field values are copied. This should not be the original data object, but a copy of the original data object. We do not want to pollute our original data model until the remote data service has confirmed that it is safe to do. So, copy the original data object, copy the field values into that data object, and send it to the remote service for update.

Communicating With The Server

The data displayed in a web app is most often loaded from the server. Similarly, the web app will need to communicate with the server when the user wants to add, update or delete data objects. Communication with the server can be done via the jqcDataService component (but you can also just use jQuery AJAX directly).

Data Service

The jqcDataService is covered in more detail in the text about the jqcDataService, but I will cover the basics here.

The jqcDataService represents a remote data service. A data service exchanges data with a real, remote data service running on your server. Typically, the web app will send 0 or 1 data object to the server, depending on what kind of request it is sending (create, read, update, delete). The remote data service will typically respond with a return code plus 0, 1 or more data objects. This is illustrated in this diagram:

Common data exchange pattern in web apps

To support this common data exchange pattern the jqcDataService has the function

sendRequest(dataObject)

This function can take a data object, or null if no data object is to be sent to the remote service. A data object is just a JavaScript object. There is nothing special about it. Typically the data object is extracted from a form object as a result of the user either adding or editing a data object. However, the data object can also come from a data model or view model, if the user selects a data object for removal.

The remote data service should respond with a return code telling if the request succeeded or failed, and 0, 1 or more data objects in an array. An array can contain 0, 1 or any other positive number of data objects so an array is a good general purpose return type.

Exactly how you implement the response from a remote data service is up to you. The jqcDataService component does not require a specific response format. But, to work well with the jqcDataModel and jqcViewModel it should return an array of read, created, updated or deleted objects, as the jqcDataModel and jqcViewModel components works with arrays of objects. Send the array back back via HTTP / JSON, then it easier to work with.

Typically you will create one jqcDataService for each service you want to call on the server. This often means one for reading, creating, updating and deleting data. A jqcDataService calls a done() listener when a response comes back from the server. Thus, each jqcDataService will have its own listener function, and each listener function will call the corresponding seData(), addData(), updateData() or removeData() on a jqcViewModel of a GUI component, or on a jqcDataModel.

Common Data Flows

Working with data in web apps often follows the pattern illustrated in this diagram:

Normal data flow in web apps.

The application starts by loading some data from a remote service. This data is loaded into a data model in the browser.

Since the data has to be displayed somehow (e.g. in a table), a view model is created from the data model. The view model allows the GUI components to reorder the data objects, add extra information to them (e.g. if a data object is selected, expanded etc.), filter the data objects etc. without polluting the original data model. Each GUI component displaying data creates its own view model. This is done to avoid that one GUI component's filtering, sorting etc. of its view model affects the view models of other GUI components.

At some point the user selects a data object for editing. The data object is then displayed in a form. If the user clicks "cancel" in the form, the data entered in the form fields is normally ignored. It is not copied back into the data object.

If the user clicks "save" in the form, the values entered in the form fields are extracted and validated. If they are valid, they are sent to the server along with any data from the edited data object which was not editable in the form. Note that the data from the form is not copied into the original data object yet. Rather, a new object is created which consists of all un-edited data from the original data object plus any edited data from the form. This mixed object is then sent to the remote data service for update.

If the remote service responds that the data object could not be updated, then the web application notifies the user, and the user can then retry the editing. Since the web app has not at this point copied any data from the form into the original data object, the data objects in the web app are still unpolluted, and the editing can be retried pretty easily. Even if the user does not want to retry editing the data object, the data object is still in sync with the data on the server, so the web app still displays up-to-date data.

If the remote data service responds that the data object was updated successfully, the remote data service sends back the data object in full. The web app can then replace the edited data object in the data model with the returned data object. All view models listening to the data model are then notified of the change so they can update themselves and their GUI components accordingly.

You have already seen how the view model and data model works, so the following sections will focus on the jqcDataService and jqcForm components.

Data Flow Examples

To help you better understand what is going on data-flow-wise in your application, let us look at the four most common types of data flows - the so-called CRUD flows:

  1. Create
  2. Read
  3. Update
  4. Delete

Each of these flows are covered below.

Create

The create flow is illustrated here:

Create data flow

First, the user clicks an "add" button (or similar), and the form is displayed so the user can enter the details of the data object to create.

Second, the user clicks the "save" button, and the form is validated. If invalid, the user is told to correct the data.

Third, if the data is valid it is passed to the matching jqcDataService and sent to the remote data service.

Fourth, the remote data service returns a return code and the newly created data object, if the creation succeeded. If creation failed, the user is notified.

Fifth, the data model which the new data object belongs to, is notified about the new data object. The data model then updates itself internally.

Sixth, the data model notifies the view models listening for changes in the data model about the new data object. The view models then update themselves internally.

Seventh, the view models notifies their GUI components to they can re-render themselves.

Read

Here is an illustration of the read data flow:

Read data flow

First, the relevant jqcDataService is asked to read the data. This could happen when the web app starts up, in the postRender() function of a module.

Second, the corresponding data model is notified of the read data objects. The data model then updates itself internally.

Third, the data model notifies the view models listening for changes in the data model about the read objects. The view models then update themselves internally.

Fourth, the view models notify their GUI components so they can re-render themselves.

Update

Here is an illustration of the update data flow:

Update data flow

First, the user selects the data object to edit. This is typically done via a GUI component (view). The data object is then obtained from the data model (or from the view model object which has a reference to the data object), and the properties of the data object is then inserted into the form fields.

Second, the user edits the data object information in the form and finishes with a click on "save" or "cancel". If the user clicks cancel the form is closed and nothing else happens.

Third, if the user clicks "save" the form fields are validated. If the form fields are invalid the user is notified so the field values can be changed.

Fourth, if the form fields are valid, they are extracted from the form fields. A copy of the original data object is made, and the form field values are copied into this data object copy too. The data object copy is then passed to the corresponding jqcDataService which sends the data object copy to the remote data service.

Fifth, the remote data service responds with a return code and the updated object. If the update fails, the user is notified and nothing more happens.

Sixth, if the update is successful, the corresponding data model is notified of the updated data. The data model then updates itself internally.

Seventh, the data model notifies the view models listening for changes in the data model about the updated object. The view models then updates themselves internally.

Eighth, the view models notify their GUI components about the updated object so they can re-render themselves.

Delete

This diagram illustrates the delete data flow:

Delete data flow

First, the user selects a data object for deletion. This typically happens via a GUI component (view). The data object is then obtained from the data model (or from the corresponding view model object which has a reference to the data object). The data object is then passed to the corresponding jqcDataService.

Second, the jqcDataService sends the data object to be removed to the remote data service.

Third, the remote data service responds with a return code and the deleted data object. Actually, only the id of the deleted data object is needed to complete the data flow, so the data object returned from the remote service does not have to contain more than the id of the deleted data object. If the delete request fails, the user is notified and nothing more happens.

Fourth, the jqcDataService notifies the corresponding data model about the deleted data object. The data model then updates itself internally.

Fifth, the data model notifies the view models listening for changes in the data model about the deleted object. The view models then update themselves internally.

Sixth, the view models notify their GUI components about the deleted object so they can re-render themselves.

More Details Elsewhere

Each of the core components (jqcDataService, jqcDataModel, jqcViewModel and jqcForm are explained in more detail in their own texts. The purpose of this text is primarily to explain how they are intended to work together.