source: Dev/trunk/src/client/dojox/mvc/StatefulModel.js @ 483

Last change on this file since 483 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 16.9 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/lang",
4        "dojo/_base/array",
5        "dojo/_base/declare",
6        "dojo/Stateful",
7        "./getStateful",
8        "./getPlainValue",
9        "./StatefulArray"
10], function(kernel, lang, array, declare, Stateful, getStateful, getPlainValue, StatefulArray){
11
12        kernel.deprecated("dojox/mvc/StatefulModel", "Use dojox/mvc/getStateful, dojox/mvc/getPlainValue, dojox/mvc/StatefulArray or one of the dojox/mvc/*RefControllers instead");
13
14        var StatefulModel = declare("dojox.mvc.StatefulModel", [Stateful], {
15                // summary:
16                //              Deprecated.  Use dojox/mvc/getStateful, dojox/mvc/getPlainValue, dojox/mvc/StatefulArray or one of the dojox/mvc/*RefControllers instead.
17                //              The first-class native JavaScript data model based on dojo/Stateful
18                //              that wraps any data structure(s) that may be relevant for a view,
19                //              a view portion, a dijit or any custom view layer component.
20                //
21                // description:
22                //              A data model is effectively instantiated with a plain JavaScript
23                //              object which specifies the initial data structure for the model.
24                //
25                //              |       var struct = {
26                //              |               order   : "abc123",
27                //              |               shipto  : {
28                //              |                       address : "123 Example St, New York, NY",
29                //              |                       phone   : "212-000-0000"
30                //              |               },
31                //              |               items : [
32                //              |                       { part : "x12345", num : 1 },
33                //              |                       { part : "n09876", num : 3 }
34                //              |               ]
35                //              |       };
36                //              |
37                //              |       var model = dojox/mvc.newStatefulModel({ data : struct });
38                //
39                //              The simple example above shows an inline plain JavaScript object
40                //              illustrating the data structure to prime the model with, however
41                //              the underlying data may be made available by other means, such as
42                //              from the results of a dojo/store or dojo/data query.
43                //
44                //              To deal with stores providing immediate values or Promises, a
45                //              factory method for model instantiation is provided. This method
46                //              will either return an immediate model or a model Promise depending
47                //              on the nature of the store.
48                //
49                //              |       var model = mvc.newStatefulModel({ store: someStore });
50                //
51                //              The created data model has the following properties:
52                //
53                //              - It enables dijits or custom components in the view to "bind" to
54                //                data within the model. A bind creates a bi-directional update
55                //                mechanism between the bound view and the underlying data:
56                //
57                //              a) The data model is "live" data i.e. it maintains any updates
58                //              driven by the view on the underlying data.
59                //
60                //              b) The data model issues updates to portions of the view if the
61                //              data they bind to is updated in the model. For example, if two
62                //              dijits are bound to the same part of a data model, updating the
63                //              value of one in the view will cause the data model to issue an
64                //              update to the other containing the new value.
65                //
66                //              - The data model internally creates a tree of dojo/Stateful
67                //                objects that matches the input, which is effectively a plain
68                //                JavaScript object i.e. "pure data". This tree allows dijits or
69                //                other view components to bind to any node within the data model.
70                //                Typically, dijits with simple values bind to leaf nodes of the
71                //                datamodel, whereas containers bind to internal nodes of the
72                //                datamodel. For example, a datamodel created using the object below
73                //                will generate the dojo/Stateful tree as shown:
74                //
75                //              |       var model = dojox/mvc/newStatefulModel({ data : {
76                //              |               prop1   : "foo",
77                //              |               prop2   : {
78                //              |                       leaf1   : "bar",
79                //              |                       leaf2   : "baz"
80                //              |               }
81                //              |       }});
82                //              |
83                //              |       // The created dojo/Stateful tree is illustrated below (all nodes are dojo/Stateful objects)
84                //              |       //
85                //              |       //                          o  (root node)
86                //              |       //                         / \
87                //              |       //       (prop1 node) o   o (prop2 node)
88                //              |       //                           / \
89                //              |       //         (leaf1 node) o   o (leaf2 node)
90                //              |       //
91                //              |       // The root node is accessed using the expression "model" (the var name above). The prop1
92                //              |       // node is accessed using the expression "model.prop1", the leaf2 node is accessed using
93                //              |       // the expression "model.prop2.leaf2" and so on.
94                //
95                //              - Each of the dojo/Stateful nodes in the model may store data as well
96                //                as associated "meta-data", which includes things such as whether
97                //                the data is required or readOnly etc. This meta-data differs from
98                //                that maintained by, for example, an individual dijit in that this
99                //                is maintained by the datamodel and may therefore be affected by
100                //                datamodel-level constraints that span multiple dijits or even
101                //                additional criteria such as server-side computations.
102                //
103                //              - When the model is backed by a dojo/store or dojo/data query, the
104                //                client-side updates can be persisted once the client is ready to
105                //                "submit" the changes (which may include both value changes or
106                //                structural changes - adds/deletes). The datamodel allows control
107                //                over when the underlying data is persisted i.e. this can be more
108                //                incremental or batched per application needs.
109                //
110                //              There need not be a one-to-one association between a datamodel and
111                //              a view or portion thereof. For example, multiple datamodels may
112                //              back the dijits in a view. Indeed, this may be useful where the
113                //              binding data comes from a number of data sources or queries, for
114                //              example. Just as well, dijits from multiple portions of the view
115                //              may be bound to a single datamodel.
116                //
117                //              Finally, requiring this class also enables all dijits to become data
118                //              binding aware. The data binding is commonly specified declaratively
119                //              via the "ref" property in the "data-dojo-props" attribute value.
120                //
121                //              To illustrate, the following is the "Hello World" of such data-bound
122                //              widget examples:
123                //
124                //
125                //              |       <script>
126                //              |               var model;
127                //              |               require(["dojox/mvc", "dojo/parser"], function(mvc, parser){
128                //              |                       model = mvc.newStatefulModel({ data : {
129                //              |                               hello : "Hello World"
130                //              |                       }});
131                //              |                       parser.parse();
132                //              |               });
133                //              |       </script>
134                //              |
135                //              |       <input id="helloInput" data-dojo-type="dijit/form/TextBox"
136                //              |               data-dojo-props="ref: 'model.hello'">
137                //
138                //              Such data binding awareness for dijits is added by extending the
139                //              dijit/_WidgetBase class to include data binding capabilities
140                //              provided by dojox/mvc/_DataBindingMixin, and this class declares a
141                //              dependency on dojox/mvc/_DataBindingMixin.
142                //
143                //              The presence of a data model and the data-binding capabilities
144                //              outlined above support the flexible development of a number of MVC
145                //              patterns on the client. As an example, CRUD operations can be
146                //              supported with minimal application code.
147                //
148                // tags:
149                //              deprecated
150       
151                // data: Object
152                //              The plain JavaScript object / data structure used to initialize
153                //              this model. At any point in time, it holds the lasted saved model
154                //              state.
155                //              Either data or store property must be provided.
156                data: null,
157
158                // store: dojo/store/DataStore
159                //              The data store from where to retrieve initial data for this model.
160                //              An optional query may also be provided along with this store.
161                //              Either data or store property must be provided.
162                store: null,
163       
164                // valid: boolean
165                //              Whether this model deems the associated data to be valid.
166                valid: true,
167
168                // value: Object
169                //              The associated value (if this is a leaf node). The value of
170                //              intermediate nodes in the model is not defined.
171                value: "",
172
173                //////////////////////// PUBLIC METHODS / API ////////////////////////
174
175                reset: function(){
176                        // summary:
177                        //              Resets this data model values to its original state.
178                        //              Structural changes to the data model (such as adds or removes)
179                        //              are not restored.
180                        if(lang.isObject(this.data) && !(this.data instanceof Date) && !(this.data instanceof RegExp)){
181                                for(var x in this){
182                                        if(this[x] && lang.isFunction(this[x].reset)){
183                                                this[x].reset();
184                                        }
185                                }
186                        }else{
187                                this.set("value", this.data);
188                        }
189                },
190
191                commit: function(/*"dojo/store/DataStore?"*/ store){
192                        // summary:
193                        //              Commits this data model:
194                        //
195                        //              - Saves the current state such that a subsequent reset will not
196                        //                undo any prior changes.
197                        //              - Persists client-side changes to the data store, if a store
198                        //                has been supplied as a parameter or at instantiation.
199                        // store:
200                        //              dojo/store/DataStore
201                        //              Optional dojo/store/DataStore to use for this commit, if none
202                        //              provided but one was provided at instantiation time, that store
203                        //              will be used instead.
204                        this._commit();
205                        var ds = store || this.store;
206                        if(ds){
207                                this._saveToStore(ds);
208                        }
209                },
210
211                toPlainObject: function(){
212                        // summary:
213                        //              Produces a plain JavaScript object representation of the data
214                        //              currently within this data model.
215                        // returns:
216                        //              Object
217                        //              The plain JavaScript object representation of the data in this
218                        //              model.
219                        return getPlainValue(this, StatefulModel.getPlainValueOptions);
220                },
221
222                splice: function(/*Number*/ idx, /*Number*/ n){
223                        // summary:
224                        //              Removes and then adds some elements to this array.
225                        //              Updates the removed/added elements, as well as the length, as stateful.
226                        // idx: Number
227                        //              The index where removal/addition should be done.
228                        // n: Number
229                        //              How many elements to be removed at idx.
230                        // varargs: Anything[]
231                        //              The elements to be added to idx.
232                        // returns: dojox/mvc/StatefulArray
233                        //              The removed elements.
234
235                        var a = (new StatefulArray([])).splice.apply(this, lang._toArray(arguments));
236                        for(var i = 0; i < a.length; i++){
237                                (this._removals = this._removals || []).push(a[i].toPlainObject());
238                        }
239                        return a;
240                },
241
242                add: function(/*String*/ name, /*dojo/Stateful*/ stateful){
243                        // summary:
244                        //              Adds a dojo/Stateful tree represented by the given
245                        //              dojox/mvc/StatefulModel at the given property name.
246                        // name:
247                        //              The property name to use whose value will become the given
248                        //              dijit/Stateful tree.
249                        // stateful:
250                        //              The dojox/mvc/StatefulModel to insert.
251                        // description:
252                        //              In case of arrays, the property names are indices passed
253                        //              as Strings. An addition of such a dojo/Stateful node
254                        //              results in right-shifting any trailing sibling nodes.
255
256                        if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
257                                if(this.get("length") < (name - 0)){
258                                        throw new Error("Out of bounds insert attempted, must be contiguous.");
259                                }
260                                this.splice(name - 0, 0, stateful);
261                        }else{
262                                this.set(name, stateful);
263                        }
264                },
265
266                remove: function(/*String*/ name){
267                        // summary:
268                        //              Removes the dojo/Stateful tree at the given property name.
269                        // name:
270                        //              The property name from where the tree will be removed.
271                        // description:
272                        //              In case of arrays, the property names are indices passed
273                        //              as Strings. A removal of such a dojo/Stateful node
274                        //              results in left-shifting any trailing sibling nodes.
275                        if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
276                                if(!this.get(name)){
277                                        throw new Error("Out of bounds delete attempted - no such index: " + n);
278                                }else{
279                                        this.splice(name - 0, 1);
280                                }
281                        }else{
282                                var elem = this.get(name);
283                                if(!elem){
284                                        throw new Error("Illegal delete attempted - no such property: " + name);
285                                }else{
286                                        this._removals = this._removals || [];
287                                        this._removals.push(elem.toPlainObject());
288                                        this.set(name, undefined);
289                                        delete this[name];
290                                }
291                        }
292                },
293
294                valueOf: function(){
295                        // summary:
296                        //              Returns the value representation of the data currently within this data model.
297                        // returns:
298                        //              Object
299                        //              The object representation of the data in this model.
300                        return this.toPlainObject();
301                },
302
303                toString: function(){
304                        // summary:
305                        //              Returns the string representation of the data currently within this data model.
306                        // returns:
307                        //              String
308                        //              The object representation of the data in this model.
309                        return this.value === "" && this.data ? this.data.toString() : this.value.toString();
310                },
311
312                //////////////////////// PRIVATE INITIALIZATION METHOD ////////////////////////
313
314                constructor: function(/*Object*/ args){
315                        // summary:
316                        //              Instantiates a new data model that view components may bind to.
317                        //              This is a private constructor, use the factory method
318                        //              instead: dojox/mvc/newStatefulModel(args)
319                        // args:
320                        //              The mixin properties.
321                        // description:
322                        //              Creates a tree of dojo/Stateful objects matching the initial
323                        //              data structure passed as input. The mixin property "data" is
324                        //              used to provide a plain JavaScript object directly representing
325                        //              the data structure.
326                        // tags:
327                        //              private
328                        var data = (args && "data" in args) ? args.data : this.data;
329                        this._createModel(data);
330                },
331
332                //////////////////////// PRIVATE METHODS ////////////////////////
333
334                _createModel: function(/*Object*/ data){
335                        // summary:
336                        //              Create this data model from provided input data.
337                        //      obj:
338                        //              The input for the model, as a plain JavaScript object.
339                        // tags:
340                        //              private
341
342                        if(data != null){
343                                data = getStateful(data, StatefulModel.getStatefulOptions);
344                                if(lang.isArray(data)){
345                                        // Some consumers of dojox/mvc/StatefulModel inherits it via dojo/declare(), where we cannot use array inheritance technique
346                                        // (dojo/declare() does not support return value in constructor)
347                                        this.length = 0;
348                                        [].splice.apply(this, data);
349                                }else if(lang.isObject(data)){
350                                        for(var s in data){
351                                                if(data.hasOwnProperty(s)){
352                                                        this[s] = data[s];
353                                                }
354                                        }
355                                }else{
356                                        this.set("value", data);
357                                }
358                        }
359                },
360
361                _commit: function(){
362                        // summary:
363                        //              Commits this data model, saves the current state into data to become the saved state,
364                        //              so a reset will not undo any prior changes. 
365                        // tags:
366                        //              private
367                        for(var x in this){
368                                if(this[x] && lang.isFunction(this[x]._commit)){
369                                        this[x]._commit();
370                                }
371                        }
372                        this.data = this.toPlainObject();
373                },
374
375                _saveToStore: function(/*"dojo/store/DataStore"*/ store){
376                        // summary:
377                        //              Commit the current values to the data store:
378                        //
379                        //              - remove() any deleted entries
380                        //              - put() any new or updated entries
381                        // store:
382                        //              dojo/store/DataStore to use for this commit.
383                        // tags:
384                        //              private
385                        if(this._removals){
386                                array.forEach(this._removals, function(d){
387                                        store.remove(store.getIdentity(d));
388                                }, this);
389                                delete this._removals;
390                        }
391                        var dataToCommit = this.toPlainObject();
392                        if(lang.isArray(dataToCommit)){
393                                array.forEach(dataToCommit, function(d){
394                                        store.put(d);
395                                }, this);
396                        }else{
397                                store.put(dataToCommit);
398                        }
399                }
400        });
401
402        lang.mixin(StatefulModel, {
403                getStatefulOptions: {
404                        // summary:
405                        //              An object that defines how model object should be created from plain object hierarchy.
406
407                        getType: function(/*Anything*/ v){
408                                // summary:
409                                //              Returns the type of the given value.
410                                // v: Anything
411                                //              The value.
412
413                                return lang.isArray(v) ? "array" : v != null && {}.toString.call(v) == "[object Object]" ? "object" : "value"; // String
414                        },
415
416                        getStatefulArray: function(/*Anything[]*/ a){
417                                // summary:
418                                //              Create a stateful array from a plain array.
419                                // a: Anything[]
420                                //              The plain array.
421
422                                var _self = this, statefularray = lang.mixin(new StatefulArray(array.map(a, function(item){ return getStateful(item, _self); })));
423                                for(var s in StatefulModel.prototype){
424                                        if(s != "set"){ statefularray[s] = StatefulModel.prototype[s]; }
425                                }
426                                statefularray.data = a;
427                                return statefularray;
428                        },
429
430                        getStatefulObject: function(/*Object*/ o){
431                                // summary:
432                                //              Create a stateful object from a plain object.
433                                // o: Object
434                                //              The plain object.
435
436                                var object = new StatefulModel();
437                                object.data = o;
438                                for(var s in o){
439                                        object.set(s, getStateful(o[s], this));
440                                }
441                                return object; // dojox/mvc/StatefulModel
442                        },
443
444                        getStatefulValue: function(/*Anything*/ v){
445                                // summary:
446                                //              Create a stateful value from a plain value.
447                                // v: Anything
448                                //              The plain value.
449
450                                var value = new StatefulModel();
451                                value.data = v;
452                                value.set("value", v);
453                                return value;
454                        }
455                },
456
457                getPlainValueOptions: {
458                        // summary:
459                        //              An object that defines how plain value should be created from model object.
460
461                        getType: function(/*Anything*/ v){
462                                // summary:
463                                //              Returns the type of the given value.
464                                // v: Anything
465                                //              The value.
466
467                                if(lang.isArray(v)){ return "array"; }
468                                if(lang.isObject(v)){ // Primitive values may have their own properties
469                                        for(var s in v){
470                                                if(v.hasOwnProperty(s) && s != "value" && (v[s] || {}).get && (v[s] || {}).watch){
471                                                        return "object";
472                                                }
473                                        }
474                                }
475                                return "value";
476                        },
477
478                        getPlainArray: function(/*dojox/mvc/StatefulArray*/ a){
479                                return array.map(a, function(item){ return getPlainValue(item, this); }, this);
480                        },
481
482                        getPlainObject: function(/*dojox/mvc/StatefulModel*/ o){
483                                var plain = {};
484                                for(var s in o){
485                                        if(s == "_watchCallbacks" || (s in StatefulModel.prototype)){ continue; }
486                                        plain[s] = getPlainValue(o[s], this);
487                                }
488                                return plain;
489                        },
490
491                        getPlainValue: function(/*Anything*/ v){
492                                return (v || {}).set && (v || {}).watch ? getPlainValue(v.value, this) : v;
493                        }
494                }
495        });
496
497        return StatefulModel;
498});
Note: See TracBrowser for help on using the repository browser.