diff options
Diffstat (limited to 'js/dojo/dojox/mvc')
| -rw-r--r-- | js/dojo/dojox/mvc/Bind.js | 62 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/Generate.js | 155 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/Group.js | 17 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/Output.js | 92 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/README | 82 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/Repeat.js | 109 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/StatefulModel.js | 463 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/_Container.js | 108 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/_DataBindingMixin.js | 390 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/_base.js | 65 | ||||
| -rw-r--r-- | js/dojo/dojox/mvc/_patches.js | 50 |
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])); + }; +}); |
