summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/mvc
diff options
context:
space:
mode:
Diffstat (limited to 'js/dojo/dojox/mvc')
-rw-r--r--js/dojo/dojox/mvc/Bind.js62
-rw-r--r--js/dojo/dojox/mvc/Generate.js155
-rw-r--r--js/dojo/dojox/mvc/Group.js17
-rw-r--r--js/dojo/dojox/mvc/Output.js92
-rw-r--r--js/dojo/dojox/mvc/README82
-rw-r--r--js/dojo/dojox/mvc/Repeat.js109
-rw-r--r--js/dojo/dojox/mvc/StatefulModel.js463
-rw-r--r--js/dojo/dojox/mvc/_Container.js108
-rw-r--r--js/dojo/dojox/mvc/_DataBindingMixin.js390
-rw-r--r--js/dojo/dojox/mvc/_base.js65
-rw-r--r--js/dojo/dojox/mvc/_patches.js50
11 files changed, 1593 insertions, 0 deletions
diff --git a/js/dojo/dojox/mvc/Bind.js b/js/dojo/dojox/mvc/Bind.js
new file mode 100644
index 0000000..edb95d2
--- /dev/null
+++ b/js/dojo/dojox/mvc/Bind.js
@@ -0,0 +1,62 @@
+//>>built
+define("dojox/mvc/Bind", [
+ "dojo/_base/lang",
+ "dojo/_base/array"
+], function(lang, array){
+ var mvc = lang.getObject("dojox.mvc", true);
+ /*=====
+ mvc = dojox.mvc;
+ =====*/
+
+ return lang.mixin(mvc, {
+ bind: function(/*dojo.Stateful*/ source, /*String*/ sourceProp,
+ /*dojo.Stateful*/ target, /*String*/ targetProp,
+ /*Function?*/ func, /*Boolean?*/ bindOnlyIfUnequal){
+ // summary:
+ // Bind the specified property of the target to the specified
+ // property of the source with the supplied transformation.
+ // source:
+ // The source dojo.Stateful object for the bind.
+ // sourceProp:
+ // The name of the source's property whose change triggers the bind.
+ // target:
+ // The target dojo.Stateful object for the bind whose
+ // property will be updated with the result of the function.
+ // targetProp:
+ // The name of the target's property to be updated with the
+ // result of the function.
+ // func:
+ // The optional calculation to be performed to obtain the target
+ // property value.
+ // bindOnlyIfUnequal:
+ // Whether the bind notification should happen only if the old and
+ // new values are unequal (optional, defaults to false).
+ var convertedValue;
+ return source.watch(sourceProp, function(prop, oldValue, newValue){
+ convertedValue = lang.isFunction(func) ? func(newValue) : newValue;
+ if(!bindOnlyIfUnequal || convertedValue != target.get(targetProp)){
+ target.set(targetProp, convertedValue);
+ }
+ });
+ },
+
+ bindInputs: function(/*dojo.Stateful[]*/ sourceBindArray, /*Function*/ func){
+ // summary:
+ // Bind the values at the sources specified in the first argument
+ // array such that a composing function in the second argument is
+ // called when any of the values changes.
+ // sourceBindArray:
+ // The array of dojo.Stateful objects to watch values changes on.
+ // func:
+ // The composing function that is called when any of the source
+ // values changes.
+ // tags:
+ // protected
+ var watchHandles = [];
+ array.forEach(sourceBindArray, function(h){
+ watchHandles.push(h.watch("value", func));
+ });
+ return watchHandles;
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/Generate.js b/js/dojo/dojox/mvc/Generate.js
new file mode 100644
index 0000000..0374075
--- /dev/null
+++ b/js/dojo/dojox/mvc/Generate.js
@@ -0,0 +1,155 @@
+//>>built
+define("dojox/mvc/Generate", [
+ "dojo/_base/lang",
+ "dojo/_base/declare",
+ "./_Container",
+ "./Group",
+ "dijit/form/TextBox"
+], function(lang, declare, Container){
+ /*=====
+ Container = dojox.mvc._Container;
+ declare = dojo.declare;
+ =====*/
+
+ return declare("dojox.mvc.Generate", [Container], {
+ // summary:
+ // A container that generates a view based on the data model its bound to.
+ //
+ // description:
+ // A generate introspects its data binding and creates a view contained in
+ // it that allows displaying the bound data. Child dijits or custom view
+ // components inside it inherit their parent data binding context from it.
+
+ // _counter: [private] Integer
+ // A count maintained internally to always generate predictable widget
+ // IDs in the view generated by this container.
+ _counter : 0,
+
+ // defaultWidgetMapping: Object
+ // The mapping of types to a widget class. Set widgetMapping to override this.
+ //
+ _defaultWidgetMapping: {"String" : "dijit.form.TextBox"},
+
+ // defaultClassMapping: Object
+ // The mapping of class to use. Set classMapping to override this.
+ //
+ _defaultClassMapping: {"Label" : "generate-label-cell", "String" : "generate-dijit-cell", "Heading" : "generate-heading", "Row" : "row"},
+
+
+ // defaultIdNameMapping: Object
+ // The mapping of id and name to use. Set idNameMapping to override this. A count will be added to the id and name
+ //
+ _defaultIdNameMapping: {"String" : "textbox_t"},
+
+ ////////////////////// PRIVATE METHODS ////////////////////////
+
+ _updateBinding: function(){
+ // summary:
+ // Regenerate if the binding changes.
+ this.inherited(arguments);
+ this._buildContained();
+ },
+
+ _buildContained: function(){
+ // summary:
+ // Destroy any existing generated view, recreate it from scratch
+ // parse the new contents.
+ // tags:
+ // private
+ this._destroyBody();
+
+ this._counter = 0;
+ this.srcNodeRef.innerHTML = this._generateBody(this.get("binding"));
+
+ this._createBody();
+ },
+
+ _generateBody: function(binding, hideHeading){
+ // summary:
+ // Generate the markup for the view associated with this generate
+ // container.
+ // binding:
+ // The associated data binding to generate a view for.
+ // hideHeading:
+ // Whether the property name should be displayed as a heading.
+ // tags:
+ // private
+ var body = "";
+ for(var prop in binding){
+ if(binding[prop] && lang.isFunction(binding[prop].toPlainObject)){
+ if(binding[prop].get(0)){
+ body += this._generateRepeat(binding[prop], prop);
+ }else if(binding[prop].value){
+ // TODO: Data types based widgets
+ body += this._generateTextBox(prop);
+ }else{
+ body += this._generateGroup(binding[prop], prop, hideHeading);
+ }
+ }
+ }
+ return body;
+ },
+
+ _generateRepeat: function(binding, repeatHeading){
+ // summary:
+ // Generate a repeating model-bound view.
+ // binding:
+ // The bound node (a collection/array node) to generate a
+ // repeating UI/view for.
+ // repeatHeading:
+ // The heading to be used for this portion.
+ // tags:
+ // private
+ var headingClass = (this.classMapping && this.classMapping["Heading"]) ? this.classMapping["Heading"] : this._defaultClassMapping["Heading"];
+ var repeat = '<div data-dojo-type="dojox.mvc.Group" data-dojo-props="ref: \'' + repeatHeading + '\'" + id="' + this.id + '_r' + this._counter++ + '">' +
+ '<div class="' + headingClass + '\">' + repeatHeading + '</div>';
+ repeat += this._generateBody(binding, true);
+ repeat += '</div>';
+ return repeat;
+ },
+
+ _generateGroup: function(binding, groupHeading, hideHeading){
+ // summary:
+ // Generate a hierarchical model-bound view.
+ // binding:
+ // The bound (intermediate) node to generate a hierarchical
+ // view portion for.
+ // groupHeading:
+ // The heading to be used for this portion.
+ // hideHeading:
+ // Whether the heading should be hidden for this portion.
+ // tags:
+ // private
+ var group = '<div data-dojo-type="dojox.mvc.Group" data-dojo-props="ref: \'' + groupHeading + '\'" + id="' + this.id + '_g' + this._counter++ + '">';
+ if(!hideHeading){
+ var headingClass = (this.classMapping && this.classMapping["Heading"]) ? this.classMapping["Heading"] : this._defaultClassMapping["Heading"];
+ group += '<div class="' + headingClass + '\">' + groupHeading + '</div>';
+ }
+ group += this._generateBody(binding);
+ group += '</div>';
+ return group;
+ },
+
+ _generateTextBox: function(prop){
+ // summary:
+ // Produce a widget for a simple value.
+ // prop:
+ // The data model property name.
+ // tags:
+ // private
+ // TODO: Data type based widget generation / enhanced meta-data
+ var idname = this.idNameMapping ? this.idNameMapping["String"] : this._defaultIdNameMapping["String"];
+ idname = idname + this._counter++;
+ var widClass = this.widgetMapping ? this.widgetMapping["String"] : this._defaultWidgetMapping["String"];
+ var labelClass = (this.classMapping && this.classMapping["Label"]) ? this.classMapping["Label"] : this._defaultClassMapping["Label"];
+ var stringClass = (this.classMapping && this.classMapping["String"]) ? this.classMapping["String"] : this._defaultClassMapping["String"];
+ var rowClass = (this.classMapping && this.classMapping["Row"]) ? this.classMapping["Row"] : this._defaultClassMapping["Row"];
+
+ return '<div class="' + rowClass + '\">' +
+ '<label class="' + labelClass + '\">' + prop + ':</label>' +
+ '<input class="' + stringClass + '\" data-dojo-type="' + widClass + '\" data-dojo-props="name: \'' + idname + "', ref: '" + prop + '\'" id="' +
+ idname + '\"></input>' +
+ '</div>';
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/Group.js b/js/dojo/dojox/mvc/Group.js
new file mode 100644
index 0000000..b67313c
--- /dev/null
+++ b/js/dojo/dojox/mvc/Group.js
@@ -0,0 +1,17 @@
+//>>built
+define("dojox/mvc/Group", ["dojo/_base/declare", "dijit/_WidgetBase"], function(declare, WidgetBase){
+ /*=====
+ WidgetBase = dijit._WidgetBase;
+ declare = dojo.declare;
+ =====*/
+
+ return declare("dojox.mvc.Group", [WidgetBase], {
+ // summary:
+ // A simple model-bound container widget with single-node binding to a data model.
+ //
+ // description:
+ // A group is usually bound to an intermediate dojo.Stateful node in the data model.
+ // Child dijits or custom view components inside a group inherit their parent
+ // data binding context from it.
+ });
+});
diff --git a/js/dojo/dojox/mvc/Output.js b/js/dojo/dojox/mvc/Output.js
new file mode 100644
index 0000000..f019665
--- /dev/null
+++ b/js/dojo/dojox/mvc/Output.js
@@ -0,0 +1,92 @@
+//>>built
+define("dojox/mvc/Output", [
+ "dojo/_base/declare",
+ "dojo/_base/lang",
+ "dojo/dom",
+ "dijit/_WidgetBase"
+], function(declare, lang, dom, _WidgetBase){
+ /*=====
+ declare = dojo.declare;
+ dom = dojo.dom;
+ _WidgetBase = dijit._WidgetBase;
+ =====*/
+
+ return declare("dojox.mvc.Output", [_WidgetBase], {
+ // summary:
+ // A simple widget that displays templated output, parts of which may
+ // be data-bound.
+ //
+ // description:
+ // Simple output example:
+ //
+ // | <span dojoType="dojox.mvc.Output" ref="model.balance">
+ // | Your balance is: ${this.value}
+ // | </span>
+ //
+ // The output widget being data-bound, if the balance changes in the
+ // dojox.mvc.StatefulModel, the content within the <span> will be
+ // updated accordingly.
+
+ // templateString: [private] String
+ // The template or data-bound output content.
+ templateString : "",
+
+ postscript: function(params, srcNodeRef){
+ // summary:
+ // Override and save template from body.
+ this.srcNodeRef = dom.byId(srcNodeRef);
+ if(this.srcNodeRef){
+ this.templateString = this.srcNodeRef.innerHTML;
+ this.srcNodeRef.innerHTML = "";
+ }
+ this.inherited(arguments);
+ },
+
+ set: function(name, value){
+ // summary:
+ // Override and refresh output on value change.
+ this.inherited(arguments);
+ if(name === "value"){
+ this._output();
+ }
+ },
+
+ ////////////////////// PRIVATE METHODS ////////////////////////
+
+ _updateBinding: function(name, old, current){
+ // summary:
+ // Rebuild output UI if data binding changes.
+ // tags:
+ // private
+ this.inherited(arguments);
+ this._output();
+ },
+
+ _output: function(){
+ // summary:
+ // Produce the data-bound output.
+ // tags:
+ // private
+ var outputNode = this.srcNodeRef || this.domNode;
+ outputNode.innerHTML = this.templateString ? this._exprRepl(this.templateString) : this.value;
+ },
+
+ _exprRepl: function(tmpl){
+ // summary:
+ // Does substitution of ${foo+bar} type expressions in template string.
+ // tags:
+ // private
+ var pThis = this, transform = function(value, key){
+ if(!value){return "";}
+ var exp = value.substr(2);
+ exp = exp.substr(0, exp.length - 1);
+ with(pThis){return eval(exp) || "";}
+ };
+ transform = lang.hitch(this, transform);
+ return tmpl.replace(/\$\{.*?\}/g,
+ function(match, key, format){
+ return transform(match, key).toString();
+ });
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/README b/js/dojo/dojox/mvc/README
new file mode 100644
index 0000000..f4b34a4
--- /dev/null
+++ b/js/dojo/dojox/mvc/README
@@ -0,0 +1,82 @@
+-------------------------------------------------------------------------------
+Project Name: dojox.mvc
+-------------------------------------------------------------------------------
+Version 0.1
+Release date: May 16th, 2011
+-------------------------------------------------------------------------------
+Project state: experimental (code and API subject to change in future releases)
+-------------------------------------------------------------------------------
+Credits:
+ Rahul Akolkar (original author)
+ Ed Chatelain
+ Charlie Wiecha
+
+-------------------------------------------------------------------------------
+Project description:
+
+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 project focuses on
+separation of MVC concerns on the client, thereby on easing development
+of data-rich applications and accelerating the authoring of applications to
+Create, Read, Update, and Delete data using a set of Dojo-based patterns.
+
+This project is useful across form factors. For example, it may be used with
+dijit as well as dojox.mobile.
+
+We begin by introducing a first-class client-side data model based on
+dojo.Stateful and extending Dojo Form widgets with support for the
+Model-View-Control (MVC) pattern key to separating data from presentation in
+user interface design. This basic MVC pattern allows for the flexible reuse of
+each of the Model, View, and Control artifacts by application authors in
+varying configurations.
+
+We also add support for a set of commonly needed MVC widgets and containers
+such as:
+- Output: a data-bound output widget
+- Group: an aggregation of widgets with the same parent data binding context
+- Repeat: a model-bound repeater widget that binds to a data collection
+- Generate: an example of UI generation from a supplied data model
+
+For more, see descriptive class documentation at the top of the following
+files:
+dojox/mvc/StatefulModel.js
+dojox/mvc/_DataBindingMixin.js
+
+For an introductory page on the included samples, see:
+dojox/mvc/tests/mvc_index.html
+
+For mobile demos, see:
+dojox/mvc/tests/mobile/demo/demo.html
+
+-------------------------------------------------------------------------------
+Dependencies:
+
+ Dojo Core (base, dojo.Stateful)
+ Dijit (dijit._WidgetBase, dijit.form.*)
+
+-------------------------------------------------------------------------------
+Documentation:
+
+Documentation resides at:
+ http://dojotoolkit.org/reference-guide/dojox/mvc.html
+
+-------------------------------------------------------------------------------
+Installation instructions:
+
+Grab the following from the Dojo SVN Repository:
+ http://svn.dojotoolkit.org/src/dojox/trunk/mvc.js
+ http://svn.dojotoolkit.org/src/dojox/trunk/mvc/*
+
+Install into the following directory structure:
+/dojox/mvc.js
+/dojox/mvc/*
+
+...which should be at the same level as your Dojo checkout.
+
+then dojo.require("dojox.mvc") in your application to load basic support for
+data bindings. Other components (such as MVC containers i.e. Group, Repeat)
+should be required as per application need.
+
+-------------------------------------------------------------------------------
diff --git a/js/dojo/dojox/mvc/Repeat.js b/js/dojo/dojox/mvc/Repeat.js
new file mode 100644
index 0000000..761bb2d
--- /dev/null
+++ b/js/dojo/dojox/mvc/Repeat.js
@@ -0,0 +1,109 @@
+//>>built
+define("dojox/mvc/Repeat", [
+ "dojo/_base/declare",
+ "dojo/dom",
+ "./_Container"
+], function(declare, dom, _Container){
+ /*=====
+ declare = dojo.declare;
+ dom = dojo.dom;
+ _Container = dojox.mvc._Container;
+ =====*/
+
+ return declare("dojox.mvc.Repeat", [_Container], {
+ // summary:
+ // A model-bound container which binds to a collection within a data model
+ // and produces a repeating user-interface from a template for each
+ // iteration within the collection.
+ //
+ // description:
+ // A repeat is bound to an intermediate dojo.Stateful node corresponding
+ // to an array in the data model. Child dijits or custom view components
+ // inside it inherit their parent data binding context from it.
+
+ // index: Integer
+ // An index used to track the current iteration when the repeating UI is
+ // produced. This may be used to parameterize the content in the repeat
+ // template for the current iteration.
+ //
+ // For example, consider a collection of search or query results where
+ // each item contains a "Name" property used to prime the "Results" data
+ // model. Then, the following CRUD-style UI displays all the names in
+ // the search results in text boxes where they may be updated or such.
+ //
+ // | <div dojoType="dojox.mvc.Repeat" ref="Results">
+ // | <div class="row" dojoType="dojox.mvc.Group" ref="${this.index}">
+ // | <label for="nameInput${this.index}">Name:</label>
+ // | <input dojoType="dijit.form.TextBox" id="nameInput${this.index}" ref="'Name'"></input>
+ // | </div>
+ // | </div>
+ index : 0,
+
+ // summary:
+ // Override and save template from body.
+ postscript: function(params, srcNodeRef){
+ this.srcNodeRef = dom.byId(srcNodeRef);
+ if(this.srcNodeRef){
+ if(this.templateString == ""){ // only overwrite templateString if it has not been set
+ this.templateString = this.srcNodeRef.innerHTML;
+ }
+ this.srcNodeRef.innerHTML = "";
+ }
+ this.inherited(arguments);
+ },
+
+ ////////////////////// PRIVATE METHODS ////////////////////////
+
+ _updateBinding: function(name, old, current){
+ // summary:
+ // Rebuild repeating UI if data binding changes.
+ // tags:
+ // private
+ this.inherited(arguments);
+ this._buildContained();
+ },
+
+ _buildContained: function(){
+ // summary:
+ // Destroy any existing contained view, recreate the repeating UI
+ // markup and parse the new contents.
+ // tags:
+ // private
+
+ // TODO: Potential optimization: only create new widgets for insert, only destroy for delete.
+ this._destroyBody();
+ this._updateAddRemoveWatch();
+
+ var insert = "";
+ for(this.index = 0; this.get("binding").get(this.index); this.index++){
+ insert += this._exprRepl(this.templateString);
+ }
+ var repeatNode = this.srcNodeRef || this.domNode;
+ repeatNode.innerHTML = insert;
+
+ // srcNodeRef is used in _createBody, so in the programmatic create case where repeatNode was set
+ // from this.domNode we need to set srcNodeRef from repeatNode
+ this.srcNodeRef = repeatNode;
+
+ this._createBody();
+ },
+
+ _updateAddRemoveWatch: function(){
+ // summary:
+ // Updates the watch handle when binding changes.
+ // tags:
+ // private
+ if(this._addRemoveWatch){
+ this._addRemoveWatch.unwatch();
+ }
+ var pThis = this;
+ this._addRemoveWatch = this.get("binding").watch(function(name,old,current){
+ if(/^[0-9]+$/.test(name.toString())){
+ if(!old || !current){
+ pThis._buildContained();
+ } // else not an insert or delete, will get updated in above
+ }
+ });
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/StatefulModel.js b/js/dojo/dojox/mvc/StatefulModel.js
new file mode 100644
index 0000000..0f4b41f
--- /dev/null
+++ b/js/dojo/dojox/mvc/StatefulModel.js
@@ -0,0 +1,463 @@
+//>>built
+define("dojox/mvc/StatefulModel", [
+ "dojo/_base/lang",
+ "dojo/_base/array",
+ "dojo/_base/declare",
+ "dojo/Stateful"
+], function(lang, array, declare, Stateful){
+ /*=====
+ declare = dojo.declare;
+ Stateful = dojo.Stateful;
+ =====*/
+
+ var StatefulModel = declare("dojox.mvc.StatefulModel", [Stateful], {
+ // summary:
+ // The first-class native JavaScript data model based on dojo.Stateful
+ // that wraps any data structure(s) that may be relevant for a view,
+ // a view portion, a dijit or any custom view layer component.
+ //
+ // description:
+ // A data model is effectively instantiated with a plain JavaScript
+ // object which specifies the initial data structure for the model.
+ //
+ // | var struct = {
+ // | order : "abc123",
+ // | shipto : {
+ // | address : "123 Example St, New York, NY",
+ // | phone : "212-000-0000"
+ // | },
+ // | items : [
+ // | { part : "x12345", num : 1 },
+ // | { part : "n09876", num : 3 }
+ // | ]
+ // | };
+ // |
+ // | var model = dojox.mvc.newStatefulModel({ data : struct });
+ //
+ // The simple example above shows an inline plain JavaScript object
+ // illustrating the data structure to prime the model with, however
+ // the underlying data may be made available by other means, such as
+ // from the results of a dojo.store or dojo.data query.
+ //
+ // To deal with stores providing immediate values or Promises, a
+ // factory method for model instantiation is provided. This method
+ // will either return an immediate model or a model Promise depending
+ // on the nature of the store.
+ //
+ // | var model = dojox.mvc.newStatefulModel({ store: someStore });
+ //
+ // The created data model has the following properties:
+ //
+ // - It enables dijits or custom components in the view to "bind" to
+ // data within the model. A bind creates a bi-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
+ // dijits 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 containing the new value.
+ //
+ // - The data model internally creates a tree of dojo.Stateful
+ // objects that matches the input, which is effectively a plain
+ // JavaScript object i.e. "pure data". This tree allows dijits or
+ // other view components to bind to any node within the data model.
+ // Typically, dijits with simple values bind to leaf nodes of the
+ // datamodel, whereas containers bind to internal nodes of the
+ // datamodel. For example, a datamodel created using the object below
+ // will generate the dojo.Stateful tree as shown:
+ //
+ // | var model = dojox.mvc.newStatefulModel({ data : {
+ // | prop1 : "foo",
+ // | prop2 : {
+ // | leaf1 : "bar",
+ // | leaf2 : "baz"
+ // | }
+ // | }});
+ // |
+ // | // The created dojo.Stateful tree is illustrated below (all nodes are dojo.Stateful objects)
+ // | //
+ // | // o (root node)
+ // | // / \
+ // | // (prop1 node) o o (prop2 node)
+ // | // / \
+ // | // (leaf1 node) o o (leaf2 node)
+ // | //
+ // | // The root node is accessed using the expression "model" (the var name above). The prop1
+ // | // node is accessed using the expression "model.prop1", the leaf2 node is accessed using
+ // | // the expression "model.prop2.leaf2" and so on.
+ //
+ // - Each of the dojo.Stateful nodes in the model may store data as well
+ // as associated "meta-data", which includes things such as whether
+ // the data is required or readOnly etc. This meta-data differs from
+ // that maintained by, for example, an individual dijit in that this
+ // is maintained by the datamodel and may therefore be affected by
+ // datamodel-level constraints that span multiple dijits or even
+ // additional criteria such as server-side computations.
+ //
+ // - 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.
+ //
+ // There need not be a one-to-one association between a datamodel and
+ // a view or portion thereof. For example, multiple datamodels may
+ // back the dijits in a view. Indeed, this may be useful where the
+ // binding data comes from a number of data sources or queries, for
+ // example. Just as well, dijits from multiple portions of the view
+ // may be bound to a single datamodel.
+ //
+ // Finally, requiring this class also enables all dijits to become data
+ // binding aware. The data binding is commonly specified declaratively
+ // via the "ref" property in the "data-dojo-props" attribute value.
+ //
+ // To illustrate, the following is the "Hello World" of such data-bound
+ // widget examples:
+ //
+ // | <script>
+ // | dojo.require("dojox.mvc");
+ // | dojo.require("dojo.parser");
+ // | var model;
+ // | dojo.addOnLoad(function(){
+ // | model = dojox.mvc.newStatefulModel({ data : {
+ // | hello : "Hello World"
+ // | }});
+ // | dojo.parser.parse();
+ // | }
+ // | </script>
+ // |
+ // | <input id="helloInput" dojoType="dijit.form.TextBox"
+ // | ref="model.hello">
+ //
+ // or
+ //
+ // | <script>
+ // | var model;
+ // | require(["dojox/mvc", "dojo/parser"], function(dxmvc, parser){
+ // | model = dojox.mvc.newStatefulModel({ data : {
+ // | hello : "Hello World"
+ // | }});
+ // | parser.parse();
+ // | });
+ // | </script>
+ // |
+ // | <input id="helloInput" data-dojo-type="dijit.form.TextBox"
+ // | data-dojo-props="ref: 'model.hello'">
+ //
+ // Such data binding awareness for dijits is added by extending the
+ // dijit._WidgetBase class to include data binding capabilities
+ // provided by dojox.mvc._DataBindingMixin, and this class declares a
+ // dependency on dojox.mvc._DataBindingMixin.
+ //
+ // The presence of a data model and the data-binding capabilities
+ // outlined above support the flexible development of a number of MVC
+ // patterns on the client. As an example, CRUD operations can be
+ // supported with minimal application code.
+
+ // data: Object
+ // The plain JavaScript object / data structure used to initialize
+ // this model. At any point in time, it holds the lasted saved model
+ // state.
+ // Either data or store property must be provided.
+ data: null,
+
+ // store: dojo.store.DataStore
+ // The data store from where to retrieve initial data for this model.
+ // An optional query may also be provided along with this store.
+ // Either data or store property must be provided.
+ store: null,
+
+ // valid: boolean
+ // Whether this model deems the associated data to be valid.
+ valid: true,
+
+ // value: Object
+ // The associated value (if this is a leaf node). The value of
+ // intermediate nodes in the model is not defined.
+ value: "",
+
+ //////////////////////// PUBLIC METHODS / API ////////////////////////
+
+ reset: function(){
+ // summary:
+ // Resets this data model values to its original state.
+ // Structural changes to the data model (such as adds or removes)
+ // are not restored.
+ if(lang.isObject(this.data) && !(this.data instanceof Date) && !(this.data instanceof RegExp)){
+ for(var x in this){
+ if(this[x] && lang.isFunction(this[x].reset)){
+ this[x].reset();
+ }
+ }
+ }else{
+ this.set("value", this.data);
+ }
+ },
+
+ commit: function(/*"dojo.store.DataStore?"*/ store){
+ // summary:
+ // Commits this data model:
+ // - Saves the current state such that a subsequent reset will not
+ // undo any prior changes.
+ // - Persists client-side changes to the data store, if a store
+ // has been supplied as a parameter or at instantiation.
+ // store:
+ // dojo.store.DataStore
+ // Optional dojo.store.DataStore to use for this commit, if none
+ // provided but one was provided at instantiation time, that store
+ // will be used instead.
+ this._commit();
+ var ds = store || this.store;
+ if(ds){
+ this._saveToStore(ds);
+ }
+ },
+
+ toPlainObject: function(){
+ // summary:
+ // Produces a plain JavaScript object representation of the data
+ // currently within this data model.
+ // returns:
+ // Object
+ // The plain JavaScript object representation of the data in this
+ // model.
+ var ret = {};
+ var nested = false;
+ for(var p in this){
+ if(this[p] && lang.isFunction(this[p].toPlainObject)){
+ if(!nested && typeof this.get("length") === "number"){
+ ret = [];
+ }
+ nested = true;
+ ret[p] = this[p].toPlainObject();
+ }
+ }
+ if(!nested){
+ if(this.get("length") === 0){
+ ret = [];
+ }else{
+ ret = this.value;
+ }
+ }
+ return ret;
+ },
+
+ add: function(/*String*/ name, /*dojo.Stateful*/ stateful){
+ // summary:
+ // Adds a dojo.Stateful tree represented by the given
+ // dojox.mvc.StatefulModel at the given property name.
+ // name:
+ // The property name to use whose value will become the given
+ // dijit.Stateful tree.
+ // stateful:
+ // The dojox.mvc.StatefulModel to insert.
+ // description:
+ // In case of arrays, the property names are indices passed
+ // as Strings. An addition of such a dojo.Stateful node
+ // results in right-shifting any trailing sibling nodes.
+ var n, n1, elem, elem1, save = new StatefulModel({ data : "" });
+ if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
+ n = name;
+ if(!this.get(n)){
+ if(this.get("length") == 0 && n == 0){ // handle the empty array case
+ this.set(n, stateful);
+ } else {
+ n1 = n-1;
+ if(!this.get(n1)){
+ throw new Error("Out of bounds insert attempted, must be contiguous.");
+ }
+ this.set(n, stateful);
+ }
+ }else{
+ n1 = n-0+1;
+ elem = stateful;
+ elem1 = this.get(n1);
+ if(!elem1){
+ this.set(n1, elem);
+ }else{
+ do{
+ this._copyStatefulProperties(elem1, save);
+ this._copyStatefulProperties(elem, elem1);
+ this._copyStatefulProperties(save, elem);
+ this.set(n1, elem1); // for watchers
+ elem1 = this.get(++n1);
+ }while(elem1);
+ this.set(n1, elem);
+ }
+ }
+ this.set("length", this.get("length") + 1);
+ }else{
+ this.set(name, stateful);
+ }
+ },
+
+ remove: function(/*String*/ name){
+ // summary:
+ // Removes the dojo.Stateful tree at the given property name.
+ // name:
+ // The property name from where the tree will be removed.
+ // description:
+ // In case of arrays, the property names are indices passed
+ // as Strings. A removal of such a dojo.Stateful node
+ // results in left-shifting any trailing sibling nodes.
+ var n, elem, elem1;
+ if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
+ n = name;
+ elem = this.get(n);
+ if(!elem){
+ throw new Error("Out of bounds delete attempted - no such index: " + n);
+ }else{
+ this._removals = this._removals || [];
+ this._removals.push(elem.toPlainObject());
+ n1 = n-0+1;
+ elem1 = this.get(n1);
+ if(!elem1){
+ this.set(n, undefined);
+ delete this[n];
+ }else{
+ while(elem1){
+ this._copyStatefulProperties(elem1, elem);
+ elem = this.get(n1++);
+ elem1 = this.get(n1);
+ }
+ this.set(n1-1, undefined);
+ delete this[n1-1];
+ }
+ this.set("length", this.get("length") - 1);
+ }
+ }else{
+ elem = this.get(name);
+ if(!elem){
+ throw new Error("Illegal delete attempted - no such property: " + name);
+ }else{
+ this._removals = this._removals || [];
+ this._removals.push(elem.toPlainObject());
+ this.set(name, undefined);
+ delete this[name];
+ }
+ }
+ },
+
+ valueOf: function(){
+ // summary:
+ // Returns the value representation of the data currently within this data model.
+ // returns:
+ // Object
+ // The object representation of the data in this model.
+ return this.toPlainObject();
+ },
+
+ toString: function(){
+ // summary:
+ // Returns the string representation of the data currently within this data model.
+ // returns:
+ // String
+ // The object representation of the data in this model.
+ return this.value === "" && this.data ? this.data.toString() : this.value.toString();
+ },
+
+ //////////////////////// PRIVATE INITIALIZATION METHOD ////////////////////////
+
+ constructor: function(/*Object*/ args){
+ // summary:
+ // Instantiates a new data model that view components may bind to.
+ // This is a private constructor, use the factory method
+ // instead: dojox.mvc.newStatefulModel(args)
+ // args:
+ // The mixin properties.
+ // description:
+ // Creates a tree of dojo.Stateful objects matching the initial
+ // data structure passed as input. The mixin property "data" is
+ // used to provide a plain JavaScript object directly representing
+ // the data structure.
+ // tags:
+ // private
+ var data = (args && "data" in args) ? args.data : this.data;
+ this._createModel(data);
+ },
+
+ //////////////////////// PRIVATE METHODS ////////////////////////
+
+ _createModel: function(/*Object*/ obj){
+ // summary:
+ // Create this data model from provided input data.
+ // obj:
+ // The input for the model, as a plain JavaScript object.
+ // tags:
+ // private
+ if(lang.isObject(obj) && !(obj instanceof Date) && !(obj instanceof RegExp) && obj !== null){
+ for(var x in obj){
+ var newProp = new StatefulModel({ data : obj[x] });
+ this.set(x, newProp);
+ }
+ if(lang.isArray(obj)){
+ this.set("length", obj.length);
+ }
+ }else{
+ this.set("value", obj);
+ }
+ },
+
+ _commit: function(){
+ // summary:
+ // Commits this data model, saves the current state into data to become the saved state,
+ // so a reset will not undo any prior changes.
+ // tags:
+ // private
+ for(var x in this){
+ if(this[x] && lang.isFunction(this[x]._commit)){
+ this[x]._commit();
+ }
+ }
+ this.data = this.toPlainObject();
+ },
+
+ _saveToStore: function(/*"dojo.store.DataStore"*/ store){
+ // summary:
+ // Commit the current values to the data store:
+ // - remove() any deleted entries
+ // - put() any new or updated entries
+ // store:
+ // dojo.store.DataStore to use for this commit.
+ // tags:
+ // private
+ if(this._removals){
+ array.forEach(this._removals, function(d){
+ store.remove(store.getIdentity(d));
+ }, this);
+ delete this._removals;
+ }
+ var dataToCommit = this.toPlainObject();
+ if(lang.isArray(dataToCommit)){
+ array.forEach(dataToCommit, function(d){
+ store.put(d);
+ }, this);
+ }else{
+ store.put(dataToCommit);
+ }
+ },
+
+ _copyStatefulProperties: function(/*dojo.Stateful*/ src, /*dojo.Stateful*/ dest){
+ // summary:
+ // Copy only the dojo.Stateful properties from src to dest (uses
+ // duck typing).
+ // src:
+ // The source object for the copy.
+ // dest:
+ // The target object of the copy.
+ // tags:
+ // private
+ for(var x in src){
+ var o = src.get(x);
+ if(o && lang.isObject(o) && lang.isFunction(o.get)){
+ dest.set(x, o);
+ }
+ }
+ }
+ });
+
+ return StatefulModel;
+});
diff --git a/js/dojo/dojox/mvc/_Container.js b/js/dojo/dojox/mvc/_Container.js
new file mode 100644
index 0000000..330a92f
--- /dev/null
+++ b/js/dojo/dojox/mvc/_Container.js
@@ -0,0 +1,108 @@
+//>>built
+define("dojox/mvc/_Container", [
+ "dojo/_base/declare",
+ "dojo/_base/lang",
+ "dijit/_WidgetBase",
+ "dojo/regexp"
+], function(declare, lang, _WidgetBase, regexp){
+ /*=====
+ declare = dojo.declare;
+ _WidgetBase = dijit._WidgetBase;
+ =====*/
+
+ return declare("dojox.mvc._Container", [_WidgetBase], {
+
+ // stopParser: [private] Boolean
+ // Flag to parser to not try and parse widgets declared inside the container.
+ stopParser: true,
+
+ // exprchar: Character
+ // Character to use for a substitution expression, for a substitution string like ${this.index}
+ exprchar: '$',
+
+ // templateString: [private] String
+ // The template or content for this container. It is usually obtained from the
+ // body of the container and may be modified or repeated over a collection/array.
+ // In this simple implementation, attach points, attach events and WAI
+ // attributes are not supported in the template.
+ templateString : "",
+
+ // _containedWidgets: [protected] dijit._Widget[]
+ // The array of contained widgets at any given point in time within this container.
+ _containedWidgets : [],
+
+ ////////////////////// PROTECTED METHODS ////////////////////////
+
+ _parser : null,
+
+ _createBody: function(){
+ // summary:
+ // Parse the body of this MVC container widget.
+ // description:
+ // The bodies of MVC containers may be model-bound views generated dynamically.
+ // Parse the body, start an contained widgets and attach template nodes for
+ // contained widgets as necessary.
+ // tags:
+ // protected
+ if(!this._parser){
+ try{
+ // returns dojo/parser if loaded, otherwise throws
+ this._parser = require("dojo/parser");
+ }catch(e){
+ // if here, dojo/parser not loaded
+ try{
+ // returns dojox/mobile/parser if loaded, otherwise throws
+ this._parser = require("dojox/mobile/parser");
+ }catch(e){
+ // if here, both dojox/mobile/parser and dojo/parser are not loaded
+ console.error("Add explicit require(['dojo/parser']) or explicit require(['dojox/mobile/parser']), one of the parsers is required!");
+ }
+ }
+ }
+ if(this._parser){
+ this._containedWidgets = this._parser.parse(this.srcNodeRef,{
+ template: true,
+ inherited: {dir: this.dir, lang: this.lang},
+ propsThis: this,
+ scope: "dojo"
+ });
+ }
+ },
+
+ _destroyBody: function(){
+ // summary:
+ // Destroy the body of this MVC container widget. Also destroys any
+ // contained widgets.
+ // tags:
+ // protected
+ if(this._containedWidgets && this._containedWidgets.length > 0){
+ for(var n = this._containedWidgets.length - 1; n > -1; n--){
+ var w = this._containedWidgets[n];
+ if(w && !w._destroyed && w.destroy){
+ w.destroy();
+ }
+ }
+ }
+ },
+
+ ////////////////////// PRIVATE METHODS ////////////////////////
+
+ _exprRepl: function(tmpl){
+ // summary:
+ // Does substitution of ${foo+bar} type expressions in template string.
+ // tags:
+ // private
+ var pThis = this, transform = function(value, key){
+ if(!value){return "";}
+ var exp = value.substr(2);
+ exp = exp.substr(0, exp.length - 1);
+ with(pThis){return eval(exp);}
+ };
+ transform = lang.hitch(this, transform);
+ return tmpl.replace(new RegExp(regexp.escapeString(this.exprchar)+"(\{.*?\})","g"),
+ function(match, key, format){
+ return transform(match, key).toString();
+ });
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/_DataBindingMixin.js b/js/dojo/dojox/mvc/_DataBindingMixin.js
new file mode 100644
index 0000000..2aa357a
--- /dev/null
+++ b/js/dojo/dojox/mvc/_DataBindingMixin.js
@@ -0,0 +1,390 @@
+//>>built
+define("dojox/mvc/_DataBindingMixin", [
+ "dojo/_base/lang",
+ "dojo/_base/array",
+ "dojo/_base/declare",
+ "dojo/Stateful",
+ "dijit/registry"
+], function(lang, array, declare, Stateful, registry){
+ /*=====
+ registry = dijit.registry;
+ =====*/
+
+ return declare("dojox.mvc._DataBindingMixin", null, {
+ // summary:
+ // Provides the ability for dijits or custom view components to become
+ // data binding aware.
+ //
+ // description:
+ // Data binding awareness enables dijits or other view layer
+ // components to bind to locations within a client-side data model,
+ // which is commonly an instance of the dojox.mvc.StatefulModel class. A
+ // bind is a bi-directional update mechanism which is capable of
+ // synchronizing value changes between the bound dijit or other view
+ // component and the specified location within the data model, as well
+ // as changes to other properties such as "valid", "required",
+ // "readOnly" etc.
+ //
+ // The data binding is commonly specified declaratively via the "ref"
+ // property in the "data-dojo-props" attribute value.
+ //
+ // Consider the following simple example:
+ //
+ // | <script>
+ // | var model;
+ // | require(["dijit/StatefulModel", "dojo/parser"], function(StatefulModel, parser){
+ // | model = new StatefulModel({ data : {
+ // | hello : "Hello World"
+ // | }});
+ // | parser.parse();
+ // | });
+ // | </script>
+ // |
+ // | <input id="hello1" data-dojo-type="dijit.form.TextBox"
+ // | data-dojo-props="ref: model.hello"></input>
+ // |
+ // | <input id="hello2" data-dojo-type="dijit.form.TextBox"
+ // | data-dojo-props="ref: model.hello"></input>
+ //
+ // In the above example, both dijit.form.TextBox instances (with IDs
+ // "hello1" and "hello2" respectively) are bound to the same reference
+ // location in the data model i.e. "hello" via the "ref" expression
+ // "model.hello". Both will have an initial value of "Hello World".
+ // Thereafter, a change in the value of either of the two textboxes
+ // will cause an update of the value in the data model at location
+ // "hello" which will in turn cause a matching update of the value in
+ // the other textbox.
+
+ // ref: String||dojox.mvc.StatefulModel
+ // The value of the data binding expression passed declaratively by
+ // the developer. This usually references a location within an
+ // existing datamodel and may be a relative reference based on the
+ // parent / container data binding (dot-separated string).
+ ref: null,
+
+/*=====
+ // binding: [readOnly] dojox.mvc.StatefulModel
+ // The read only value of the resolved data binding for this widget.
+ // This may be a result of resolving various relative refs along
+ // the parent axis.
+ binding: null,
+=====*/
+
+ //////////////////////// PUBLIC METHODS ////////////////////////
+
+ isValid: function(){
+ // summary:
+ // Returns the validity of the data binding.
+ // returns:
+ // Boolean
+ // The validity associated with the data binding.
+ // description:
+ // This function is meant to provide an API bridge to the dijit API.
+ // Validity of data-bound dijits is a function of multiple concerns:
+ // - The validity of the value as ascertained by the data binding
+ // and constraints specified in the data model (usually semantic).
+ // - The validity of the value as ascertained by the widget itself
+ // based on widget constraints (usually syntactic).
+ // In order for dijits to function correctly in data-bound
+ // environments, it is imperative that their isValid() functions
+ // assess the model validity of the data binding via the
+ // this.inherited(arguments) hierarchy and declare any values
+ // failing the test as invalid.
+ return this.get("binding") ? this.get("binding").get("valid") : true;
+ },
+
+ //////////////////////// LIFECYCLE METHODS ////////////////////////
+
+ _dbstartup: function(){
+ // summary:
+ // Tie data binding initialization into the widget lifecycle, at
+ // widget startup.
+ // tags:
+ // private
+ if(this._databound){
+ return;
+ }
+ this._unwatchArray(this._viewWatchHandles);
+ // add 2 new view watches, active only after widget has started up
+ this._viewWatchHandles = [
+ // 1. data binding refs
+ this.watch("ref", function(name, old, current){
+ if(this._databound){
+ this._setupBinding();
+ }
+ }),
+ // 2. widget values
+ this.watch("value", function(name, old, current){
+ if(this._databound){
+ var binding = this.get("binding");
+ if(binding){
+ // dont set value if the valueOf current and old match.
+ if(!((current && old) && (old.valueOf() === current.valueOf()))){
+ binding.set("value", current);
+ }
+ }
+ }
+ })
+ ];
+ this._beingBound = true;
+ this._setupBinding();
+ delete this._beingBound;
+ this._databound = true;
+ },
+
+ //////////////////////// PRIVATE METHODS ////////////////////////
+
+ _setupBinding: function(parentBinding){
+ // summary:
+ // Calculate and set the dojo.Stateful data binding for the
+ // associated dijit or custom view component.
+ // parentBinding:
+ // The binding of this widget/view component's data-bound parent,
+ // if available.
+ // description:
+ // The declarative data binding reference may be specified in two
+ // ways via markup:
+ // - For older style documents (non validating), controls may use
+ // the "ref" attribute to specify the data binding reference
+ // (String).
+ // - For validating documents using the new Dojo parser, controls
+ // may specify the data binding reference (String) as the "ref"
+ // property specified in the data-dojo-props attribute.
+ // Once the ref value is obtained using either of the above means,
+ // the binding is set up for this control and its required, readOnly
+ // etc. properties are refreshed.
+ // The data binding may be specified as a direct reference to the
+ // dojo.Stateful model node or as a string relative to its DOM
+ // parent or another widget.
+ // There are three ways in which the data binding node reference is
+ // calculated when specified as a string:
+ // - If an explicit parent widget is specified, the binding is
+ // calculated relative to the parent widget's data binding.
+ // - For any dijits that specify a data binding reference,
+ // we walk up their DOM hierarchy to obtain the first container
+ // dijit that has a data binding set up and use the reference String
+ // as a property name relative to the parent's data binding context.
+ // - If no such parent is found i.e. for the outermost container
+ // dijits that specify a data binding reference, the binding is
+ // calculated by treating the reference String as an expression and
+ // evaluating it to obtain the dojo.Stateful node in the datamodel.
+ // This method throws an Error in these two conditions:
+ // - The ref is an expression i.e. outermost bound dijit, but the
+ // expression evaluation fails.
+ // - The calculated binding turns out to not be an instance of a
+ // dojo.Stateful node.
+ // tags:
+ // private
+ if(!this.ref){
+ return; // nothing to do here
+ }
+ var ref = this.ref, pw, pb, binding;
+ // Now compute the model node to bind to
+ if(ref && lang.isFunction(ref.toPlainObject)){ // programmatic instantiation or direct ref
+ binding = ref;
+ }else if(/^\s*expr\s*:\s*/.test(ref)){ // declarative: refs as dot-separated expressions
+ ref = ref.replace(/^\s*expr\s*:\s*/, "");
+ binding = lang.getObject(ref);
+ }else if(/^\s*rel\s*:\s*/.test(ref)){ // declarative: refs relative to parent binding, dot-separated
+ ref = ref.replace(/^\s*rel\s*:\s*/, "");
+ parentBinding = parentBinding || this._getParentBindingFromDOM();
+ if(parentBinding){
+ binding = lang.getObject("" + ref, false, parentBinding);
+ }
+ }else if(/^\s*widget\s*:\s*/.test(ref)){ // declarative: refs relative to another dijits binding, dot-separated
+ ref = ref.replace(/^\s*widget\s*:\s*/, "");
+ var tokens = ref.split(".");
+ if(tokens.length == 1){
+ binding = registry.byId(ref).get("binding");
+ }else{
+ pb = registry.byId(tokens.shift()).get("binding");
+ binding = lang.getObject(tokens.join("."), false, pb);
+ }
+ }else{ // defaults: outermost refs are expressions, nested are relative to parents
+ parentBinding = parentBinding || this._getParentBindingFromDOM();
+ if(parentBinding){
+ binding = lang.getObject("" + ref, false, parentBinding);
+ }else{
+ try{
+ if(lang.getObject(ref) instanceof Stateful){
+ binding = lang.getObject(ref);
+ }
+ }catch(err){
+ if(ref.indexOf("${") == -1){ // Ignore templated refs such as in repeat body
+ throw new Error("dojox.mvc._DataBindingMixin: '" + this.domNode +
+ "' widget with illegal ref expression: '" + ref + "'");
+ }
+ }
+ }
+ }
+ if(binding){
+ if(lang.isFunction(binding.toPlainObject)){
+ this.binding = binding;
+ this._updateBinding("binding", null, binding);
+ }else{
+ throw new Error("dojox.mvc._DataBindingMixin: '" + this.domNode +
+ "' widget with illegal ref not evaluating to a dojo.Stateful node: '" + ref + "'");
+ }
+ }
+ },
+
+ _isEqual: function(one, other){
+ // test for equality
+ return one === other ||
+ // test for NaN === NaN
+ isNaN(one) && typeof one === 'number' &&
+ isNaN(other) && typeof other === 'number';
+ },
+
+ _updateBinding: function(name, old, current){
+ // summary:
+ // Set the data binding to the supplied value, which must be a
+ // dojo.Stateful node of a data model.
+ // name:
+ // The name of the binding property (always "binding").
+ // old:
+ // The old dojo.Stateful binding node of the data model.
+ // current:
+ // The new dojo.Stateful binding node of the data model.
+ // description:
+ // Applies the specified data binding to the attached widget.
+ // Loses any prior watch registrations on the previously active
+ // bind, registers the new one, updates data binds of any contained
+ // widgets and also refreshes all associated properties (valid,
+ // required etc.)
+ // tags:
+ // private
+
+ // remove all existing watches (if there are any, there will be 5)
+ this._unwatchArray(this._modelWatchHandles);
+ // add 5 new model watches
+ var binding = this.get("binding");
+ if(binding && lang.isFunction(binding.watch)){
+ var pThis = this;
+ this._modelWatchHandles = [
+ // 1. value - no default
+ binding.watch("value", function (name, old, current){
+ if(pThis._isEqual(old, current)){return;}
+ if(pThis._isEqual(pThis.get('value'), current)){return;}
+ pThis.set("value", current);
+ }),
+ // 2. valid - default "true"
+ binding.watch("valid", function (name, old, current){
+ pThis._updateProperty(name, old, current, true);
+ if(current !== pThis.get(name)){
+ if(pThis.validate && lang.isFunction(pThis.validate)){
+ pThis.validate();
+ }
+ }
+ }),
+ // 3. required - default "false"
+ binding.watch("required", function (name, old, current){
+ pThis._updateProperty(name, old, current, false, name, current);
+ }),
+ // 4. readOnly - default "false"
+ binding.watch("readOnly", function (name, old, current){
+ pThis._updateProperty(name, old, current, false, name, current);
+ }),
+ // 5. relevant - default "true"
+ binding.watch("relevant", function (name, old, current){
+ pThis._updateProperty(name, old, current, false, "disabled", !current);
+ })
+ ];
+ var val = binding.get("value");
+ if(val != null){
+ this.set("value", val);
+ }
+ }
+ this._updateChildBindings();
+ },
+
+ _updateProperty: function(name, old, current, defaultValue, setPropName, setPropValue){
+ // summary:
+ // Update a binding property of the bound widget.
+ // name:
+ // The binding property name.
+ // old:
+ // The old value of the binding property.
+ // current:
+ // The new or current value of the binding property.
+ // defaultValue:
+ // The optional value to be applied as the current value of the
+ // binding property if the current value is null.
+ // setPropName:
+ // The optional name of a stateful property to set on the bound
+ // widget.
+ // setPropValue:
+ // The value, if an optional name is provided, for the stateful
+ // property of the bound widget.
+ // tags:
+ // private
+ if(old === current){
+ return;
+ }
+ if(current === null && defaultValue !== undefined){
+ current = defaultValue;
+ }
+ if(current !== this.get("binding").get(name)){
+ this.get("binding").set(name, current);
+ }
+ if(setPropName){
+ this.set(setPropName, setPropValue);
+ }
+ },
+ _updateChildBindings: function(parentBind){
+ // summary:
+ // Update this widget's value based on the current binding and
+ // set up the bindings of all contained widgets so as to refresh
+ // any relative binding references.
+ // findWidgets does not return children of widgets so need to also
+ // update children of widgets which are not bound but may hold widgets which are.
+ // parentBind:
+ // The binding on the parent of a widget whose children may have bindings
+ // which need to be updated.
+ // tags:
+ // private
+ var binding = this.get("binding") || parentBind;
+ if(binding && !this._beingBound){
+ array.forEach(registry.findWidgets(this.domNode), function(widget){
+ if(widget.ref && widget._setupBinding){
+ widget._setupBinding(binding);
+ }else{
+ widget._updateChildBindings(binding);
+ }
+ });
+ }
+ },
+
+ _getParentBindingFromDOM: function(){
+ // summary:
+ // Get the parent binding by traversing the DOM ancestors to find
+ // the first enclosing data-bound widget.
+ // returns:
+ // The parent binding, if one exists along the DOM parent axis.
+ // tags:
+ // private
+ var pn = this.domNode.parentNode, pw, pb;
+ while(pn){
+ pw = registry.getEnclosingWidget(pn);
+ if(pw){
+ pb = pw.get("binding");
+ if(pb && lang.isFunction(pb.toPlainObject)){
+ break;
+ }
+ }
+ pn = pw ? pw.domNode.parentNode : null;
+ }
+ return pb;
+ },
+
+ _unwatchArray: function(watchHandles){
+ // summary:
+ // Given an array of watch handles, unwatch all.
+ // watchHandles:
+ // The array of watch handles.
+ // tags:
+ // private
+ array.forEach(watchHandles, function(h){ h.unwatch(); });
+ }
+ });
+});
diff --git a/js/dojo/dojox/mvc/_base.js b/js/dojo/dojox/mvc/_base.js
new file mode 100644
index 0000000..cbdad32
--- /dev/null
+++ b/js/dojo/dojox/mvc/_base.js
@@ -0,0 +1,65 @@
+//>>built
+define("dojox/mvc/_base", [
+ "dojo/_base/kernel",
+ "dojo/_base/lang",
+ "./StatefulModel",
+ "./Bind",
+ "./_DataBindingMixin",
+ "./_patches"
+], function(kernel, lang, StatefulModel){
+ // module:
+ // dojox/mvc/_base
+ // summary:
+ // Pulls in essential MVC dependencies such as basic support for
+ // data binds, a data model and data binding mixin for dijits.
+ kernel.experimental("dojox.mvc");
+
+ var mvc = lang.getObject("dojox.mvc", true);
+ /*=====
+ mvc = dojox.mvc;
+ =====*/
+
+ // Factory method for dojox.mvc.StatefulModel instances
+ mvc.newStatefulModel = function(/*Object*/args){
+ // summary:
+ // Factory method that instantiates a new data model that view
+ // components may bind to.
+ // args:
+ // The mixin properties.
+ // description:
+ // Factory method that returns a client-side data model, which is a
+ // tree of dojo.Stateful objects matching the initial data structure
+ // passed as input:
+ // - The mixin property "data" is used to provide a plain JavaScript
+ // object directly representing the data structure.
+ // - The mixin property "store", along with an optional mixin property
+ // "query", is used to provide a data store to query to obtain the
+ // initial data.
+ // This function returns an immediate dojox.mvc.StatefulModel instance or
+ // a Promise for such an instance as follows:
+ // - if args.data: returns immediate
+ // - if args.store:
+ // - if store returns immediate: this function returns immediate
+ // - if store returns a Promise: this function returns a model
+ // Promise
+ if(args.data){
+ return new StatefulModel({ data : args.data });
+ }else if(args.store && lang.isFunction(args.store.query)){
+ var model;
+ var result = args.store.query(args.query);
+ if(result.then){
+ return (result.then(function(data){
+ model = new StatefulModel({ data : data });
+ model.store = args.store;
+ return model;
+ }));
+ }else{
+ model = new StatefulModel({ data : result });
+ model.store = args.store;
+ return model;
+ }
+ }
+ };
+
+ return mvc;
+});
diff --git a/js/dojo/dojox/mvc/_patches.js b/js/dojo/dojox/mvc/_patches.js
new file mode 100644
index 0000000..326c734
--- /dev/null
+++ b/js/dojo/dojox/mvc/_patches.js
@@ -0,0 +1,50 @@
+//>>built
+define("dojox/mvc/_patches", [
+ "dojo/_base/lang",
+ "dojo/_base/array",
+ "dijit/_WidgetBase",
+ "./_DataBindingMixin",
+ "dijit/form/ValidationTextBox",
+ "dijit/form/NumberTextBox"
+], function(lang, array, wb, dbm, vtb, ntb){
+ /*=====
+ vtb = dijit.form.ValidationTextBox;
+ ntb = dijit.form.NumberTextBox;
+ dbm = dojox.mvc._DataBindingMixin;
+ wb = dijit._WidgetBase;
+ =====*/
+
+ //Apply the data binding mixin to all dijits, see mixin class description for details
+ lang.extend(wb, new dbm());
+
+ // monkey patch dijit._WidgetBase.startup to get data binds set up
+ var oldWidgetBaseStartup = wb.prototype.startup;
+ wb.prototype.startup = function(){
+ this._dbstartup();
+ oldWidgetBaseStartup.apply(this);
+ };
+
+ // monkey patch dijit._WidgetBase.destroy to remove watches setup in _DataBindingMixin
+ var oldWidgetBaseDestroy = wb.prototype.destroy;
+ wb.prototype.destroy = function(/*Boolean*/ preserveDom){
+ if(this._modelWatchHandles){
+ array.forEach(this._modelWatchHandles, function(h){ h.unwatch(); });
+ }
+ if(this._viewWatchHandles){
+ array.forEach(this._viewWatchHandles, function(h){ h.unwatch(); });
+ }
+ oldWidgetBaseDestroy.apply(this, [preserveDom]);
+ };
+
+ // monkey patch dijit.form.ValidationTextBox.isValid to check this.inherited for isValid
+ var oldValidationTextBoxIsValid = vtb.prototype.isValid;
+ vtb.prototype.isValid = function(/*Boolean*/ isFocused){
+ return (this.inherited("isValid", arguments) !== false && oldValidationTextBoxIsValid.apply(this, [isFocused]));
+ };
+
+ // monkey patch dijit.form.NumberTextBox.isValid to check this.inherited for isValid
+ var oldNumberTextBoxIsValid = ntb.prototype.isValid;
+ ntb.prototype.isValid = function(/*Boolean*/ isFocused){
+ return (this.inherited("isValid", arguments) !== false && oldNumberTextBoxIsValid.apply(this, [isFocused]));
+ };
+});