Skip to Content | Skip to Navigation


dojox/mvc

Authors:Akira Sudoh, Ed Chatelain, Rahul Akolkar
Developers:Akira Sudoh, Ed Chatelain, Rahul Akolkar
since:V1.7

Introduction

Enterprise Rich Internet Applications (RIAs) often focus more on rich data vs. the rich media aspects of RIAs more typical of consumer applications. For example, such RIAs depend on implementing the well-known CRUD operations on data stored in back-end systems. The dojox/mvc package focuses on View to Model data binding (eg. View Controller) concerns on the client, easing development of data-rich UI, which Create, Read, Update, and Delete data. dojox.mvc deals with data binding/controller concerns within a View, but does not deal with application level concerns that span multiple Views (such as navigation), see dojox/app for Application-level Controller concerns.

How it works

dojox/mvc has the following properties:

  • It enables widgets (desktop and mobile) to “bind” to data within the model. A bind creates a bi-directional or a single directional update mechanism between the bound view and the underlying data.
  • The data model is “live” data i.e. it maintains any updates driven by the view on the underlying data.
  • The data model issues updates to portions of the view if the data they bind to is updated in the model. For example, if two widgets are bound to the same part of a data model, updating the value of one in the view will cause the data model to issue an update to the other widget with the new value.
  • When the model is backed by a dojo.store or dojo.data query, the client-side updates can be persisted once the client is ready to “submit” the changes (which may include both value changes or structural changes - adds/deletes). The datamodel allows control over when the underlying data is persisted i.e. this can be more incremental or batched per application needs.

Features

Data binding APIs, as well as several other APIs, widgets, etc., are available in dojox/mvc.

Data binding API

There are two data binding APIs:

  • dojox/mvc/sync provides a simple way for data binding, by keeping two dojo/Stateful objects in sync.
  • dojox/mvc/at typically is used in data-dojo-props for declarative data binding, where a widget can synchronize its attribute with another dojo/Stateful. It can also be used in the first parameter of widget constructor (list of initial property values) for programmatic data binding.

Both APIs above support:

Both APIs above use dojo/Stateful as the endpoints of data binding, including widgets that inherit dojo/Stateful.

Components in-between data binding endpoints

There are also some helper classes (below), also based on dojo/Stateful, that typically work in-between such endpoints:

Widgets, etc.

A number of widgets and MVC containers, etc. are also available, including:

Advanced data model as well as its helper

dojox/mvc/WidgetList, dojox/mvc/ListController, etc. work with an array of data. For those components to react to changes in the array, such as adds/removals, etc., dojox/mvc/StatefulArray is available. dojox/mvc/StatefulArray is mostly interface-compatible to native Array.

For easy conversion of plain objects/arrays from/to dojo/Stateful and dojox/mvc/StatefulArray, the following APIs are available:

Deprecated APIs and their successors

dojox/mvc/StatefulModel has been deprecated. dojox/mvc/StatefulModel had several different features, such as:

Though some applications use all of these features, many applications do not. For example:

  • As 1.8 dojox/mvc supports binding any dojo/Stateful properties, dojox/mvc/StatefulModel‘s unique approach of converting non-object value to dojox/mvc/StatefulModel (with value attribute) is no longer needed. Regular dojo/Stateful can be used as data model, in many cases.
  • Some applications do not use array in data model at all.
  • Some applications use arrays in data model in a static manner (No notification is needed for removals/additions of elements for such kind of applications).
  • Some applications would implement getters/setters in its data models to work with more complex data (e.g. XML from REST call, whose request for data item tends to be served by XPath).

To be able to support these different needs dojox/mvc/StatefulModel is being separated into to classes that support each item:

dojox/mvc/_DataBindingMixin has been deprecated.

Examples

Basic example, input-output sync: Anything typed into the input fields will be updated in the model and reflected in the output field when you leave the input field.

var model;
require([
    "dojo/parser",
    "dojo/Stateful",
    "dojo/domReady!"
], function(parser, Stateful){
    // For this test we can use a simple dojo/Stateful as our model
    model = new Stateful({First: "John", Last: "Doe", Email: "jdoe@example.com"});
    parser.parse();
});
.row { width: 500px; display: inline-block; margin: 5px; }
.cell { width: 20%;  display:inline-block; }
.textcell { width: 30%;  display:inline-block; }
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div id="wrapper">
    <div id="header">
        <div id="navigation"></div>
        <div id="headerInsert">
          <h1>Input Ouput Sync</h1>
          <h2>Data Binding Example</h2>
        </div>
    </div>
    <div id="main">
        <div id="leftNav"></div>
        <div id="mainContent">
            <div class="row">
                <label class="cell" for="firstnameInput">First:</label>
                <input class="cell" id="firstnameInput" data-dojo-type="dijit/form/TextBox"
                       data-dojo-props="value: at(model, 'First')">
                <!-- Content in output below will always be in sync with value of textbox above -->
                (First name is:
                <span data-dojo-type="dojox/mvc/Output"
                      data-dojo-props="value: at(model, 'First')"></span>)
            </div>
            <div class="row">
                <label class="cell" for="lastnameInput">Last:</label>
                <input class="cell" id="lastnameInput" data-dojo-type="dijit/form/TextBox"
                       data-dojo-props="value: at(model, 'Last')">
                (Last name is:
                <span data-dojo-type="dojox/mvc/Output"
                      data-dojo-props="value: at(model, 'Last')"></span>)
            </div>
            <div class="row">
                <label class="cell" for="emailInput">Email:</label>
                <input class="cell" id="emailInput" data-dojo-type="dijit/form/TextBox"
                       data-dojo-props="value: at(model, 'Email')">
                (email is:
                <span data-dojo-type="dojox/mvc/Output"
                      data-dojo-props="value: at(model, 'Email')"></span>)
            </div>
        </div>
    </div>
</div>

Basic example two, input-output sync: Anything typed into the input fields will be updated in the model and reflected in the output field when you leave the input field. The “Reset” button will reset the model back to it’s original values. The other buttons show how to programmatically set things in the model to have the update reflected in the widget, and how to programmatically update the widget and have it update the model.

var model;
require([
    "dojo/parser",
    "dojo/Stateful",
    "dojo/domReady!"
], function(parser, Stateful){
    model = new Stateful({First: "John", Last: "Doe", Email: "jdoe@example.com"});
    parser.parse();
});
.row { width: 500px; display: inline-block; margin: 5px; }
.cell { width: 20%;  display:inline-block; }
.textcell { width: 30%;  display:inline-block; }
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div id="main">
    <span id="ctrl" data-dojo-type="dojox/mvc/EditModelRefController" data-dojo-props="sourceModel: model"></span>
    <div class="row">
        <label class="cell" for="firstId">First:</label>
        <input class="textcell" id="firstId" data-dojo-type="dijit/form/TextBox"
               data-dojo-props="value: at('widget:ctrl', 'First')"></input>
        <!-- Content in output below will always be in sync with value of textbox above -->
        <span data-dojo-type="dojox/mvc/Output"
              data-dojo-props="value: at('widget:ctrl', 'First')">
            (first name is: ${this.value})
        </span>
    </div>
    <div class="row">
        <label class="cell" for="lastnameInput">Last:</label>
        <input class="textcell" id="lastnameInput" data-dojo-type="dijit/form/TextBox"
               data-dojo-props="value: at('widget:ctrl', 'Last')"></input>
        <span data-dojo-type="dojox/mvc/Output"
              data-dojo-props="value: at('widget:ctrl', 'Last')">
            (last name is: ${this.value})
        </span>
    </div>
    <div class="row">
        <label class="cell" for="emailInput">Email:</label>
        <input class="textcell" id="emailInput" data-dojo-type="dijit/form/TextBox"
               data-dojo-props="value: at('widget:ctrl', 'Email')"></input>
        <span data-dojo-type="dojox/mvc/Output"
              data-dojo-props="value: at('widget:ctrl', 'Email')">
            (email is: ${this.value})
        </span>
    </div>
    <br/>
    Model:
    <button id="reset" type="button" data-dojo-type="dijit/form/Button"
            data-dojo-props="onClick: function(){ require('dijit/registry').byId('ctrl').reset(); }">Reset</button>
    <button id="fromModel" type="button" data-dojo-type="dijit/form/Button"
            data-dojo-props="onClick: function(){ require('dijit/registry').byId('ctrl').set('First', 'Updated in Model'); }">Update First from Model</button>
    <button id="fromWidget" type="button" data-dojo-type="dijit/form/Button"
            data-dojo-props="onClick: function(){ require('dijit/registry').byId('firstId').set('value', 'Updated Widget'); }">Update First from Widget</button>
</div>