source: Dev/trunk/src/client/dojox/mvc/WidgetList.js

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

Added Dojo 1.9.3 release.

File size: 12.1 KB
Line 
1define([
2        "require",
3        "dojo/_base/array",
4        "dojo/_base/lang",
5        "dojo/_base/declare",
6        "dijit/_Container",
7        "dijit/_WidgetBase",
8        "./Templated"
9], function(require, array, lang, declare, _Container, _WidgetBase, Templated){
10        var childTypeAttr = "data-mvc-child-type",
11         childMixinsAttr = "data-mvc-child-mixins",
12         childParamsAttr = "data-mvc-child-props",
13         childBindingsAttr = "data-mvc-child-bindings",
14         undef;
15
16        function evalParams(params){
17                return eval("({" + params + "})");
18        }
19
20        function unwatchElements(/*dojox/mvc/WidgetList*/ w){
21                for(var h = null; h = (w._handles || []).pop();){
22                        h.unwatch();
23                }
24        }
25
26        function flatten(/*String[][]*/ a){
27                var flattened = [];
28                array.forEach(a, function(item){
29                        [].push.apply(flattened, item);
30                });
31                return flattened;
32        }
33
34        function loadModules(/*dojo/Stateful[]*/ items, /*Function*/ callback){
35                // summary:
36                //              Load modules associated with an array of data.
37                // items: dojo/Stateful[]
38                //              The array of data.
39                // callback: Function
40                //              Then callback called when the modules have been loaded.
41
42                if(this.childClz){
43                        callback(this.childClz);
44                }else if(this.childType){
45                        var typesForItems = !lang.isFunction(this.childType) && !lang.isFunction(this.childMixins) ? [[this.childType].concat(this.childMixins && this.childMixins.split(",") || [])] :
46                         array.map(items, function(item){
47                                var type = lang.isFunction(this.childType) ? this.childType.call(item, this) : this.childType,
48                                 mixins = lang.isFunction(this.childMixins) ? this.childMixins.call(item, this) : this.childMixins;
49                                return type ? [type].concat(lang.isArray(mixins) ? mixins : mixins ? mixins.split(",") : []) : ["dojox/mvc/Templated"];
50                        }, this);
51                        require(array.filter(array.map(flatten(typesForItems), function(type){ return lang.getObject(type) ? undef : type; }), function(type){ return type !== undef; }), function(){
52                                callback.apply(this, array.map(typesForItems, function(types){
53                                        var clzList = array.map(types, function(type){ return lang.getObject(type) || require(type); });
54                                        return clzList.length > 1 ? declare(clzList, {}) : clzList[0];
55                                }));
56                        });
57                }else{
58                        callback(Templated);
59                }
60        }
61
62        var WidgetList = declare("dojox.mvc.WidgetList", [_WidgetBase, _Container], {
63                // summary:
64                //              A widget that creates child widgets repeatedly based on the children attribute (the repeated data) and childType/childMixins/childParams attributes (determines how to create each child widget).
65                // example:
66                //              Create multiple instances of dijit/TextBox based on the data in array.
67                //              The text box refers to First property in the array item.
68                // |            <div data-dojo-type="dojox/mvc/WidgetList"
69                // |             data-dojo-props="children: array"
70                // |             data-mvc-child-type="dijit/form/TextBox"
71                // |             data-mvc-child-props="value: at(this.target, 'First')"></div>
72                // example:
73                //              Create multiple instances of widgets-in-template based on the HTML written in `<script type="dojox/mvc/InlineTemplate">`.
74                //              The label refers to Serial property in the array item, and the text box refers to First property in the array item.
75                // |            <div data-dojo-type="dojox/mvc/WidgetList"
76                // |             data-dojo-mixins="dojox/mvc/_InlineTemplateMixin"
77                // |             data-dojo-props="children: array">
78                // |                    <script type="dojox/mvc/InlineTemplate">
79                // |                            <div>
80                // |                                    <span data-dojo-type="dijit/_WidgetBase"
81                // |                                     data-dojo-props="_setValueAttr: {node: 'domNode', type: 'innerText'}, value: at('rel:', 'Serial')"></span>:
82                // |                                    <span data-dojo-type="dijit/form/TextBox"
83                // |                                     data-dojo-props="value: at('rel:', 'First')"></span>
84                // |                            </div>
85                // |                    </script>
86                // |            </div>
87                // example:
88                //              Programmatically create multiple instances of widgets-in-template based on the HTML stored in childTemplate.
89                //              (childTemplate may come from dojo/text)
90                //              Also programmatically establish data binding at child widget's startup phase.
91                //              The label refers to Serial property in the array item, and the text box refers to First property in the array item.
92                // |            var childTemplate = '<div>'
93                // |             + '<span data-dojo-type="dijit/_WidgetBase"'
94                // |             + ' data-dojo-attach-point="labelNode"'
95                // |             + ' data-dojo-props="_setValueAttr: {node: \'domNode\', type: \'innerText\'}"></span>'
96                // |             + '<span data-dojo-type="dijit/form/TextBox"'
97                // |             + ' data-dojo-attach-point="inputNode"></span>'
98                // |             + '</div>';
99                // |            (new WidgetList({
100                // |                    children: array,
101                // |                    childParams: {
102                // |                            startup: function(){
103                // |                                    this.labelNode.set("value", at("rel:", "Serial"));
104                // |                                    this.inputNode.set("value", at("rel:", "First"));
105                // |                                    this.inherited("startup", arguments);
106                // |                            }
107                // |                    },
108                // |                    templateString: childTemplate
109                // |            }, dom.byId("programmaticRepeat"))).startup();
110                // example:
111                //              Using the same childTemplate above, establish data binding for child widgets based on the declaration in childBindings.
112                //              (childBindings may come from dojo/text, by eval()'ing the text)
113                // |            var childBindings = {
114                // |                    labelNode: {value: at("rel:", "Serial")},
115                // |                    inputNode: {value: at("rel:", "First")}
116                // |            };
117                // |            (new WidgetList({
118                // |                    children: array,
119                // |                    templateString: childTemplate,
120                // |                    childBindings: childBindings
121                // |            }, dom.byId("programmaticRepeatWithSeparateBindingDeclaration"))).startup();
122
123                // childClz: Function
124                //              The class of the child widget. Takes precedence over childType/childMixins.
125                childClz: null,
126
127                // childType: String|Function
128                //              The module ID of child widget, or a function that takes child data as the argument and returns the module ID of child widget. childClz takes precedence over this/childMixins.
129                //              Can be specified via data-mvc-child-type attribute of widget declaration.
130                childType: "",
131
132                // childMixins: String|String[]|Function
133                //              The list of module IDs (separated by comma), or a functinon that takes child data as the argument and returns it, of the classes that will be mixed into child widget. childClz takes precedence over childType/this.
134                //              Can be specified via data-mvc-child-mixins attribute of widget declaration.
135                childMixins: "",
136
137                // childParams: Object|Function
138                //              The mixin properties for child widget.
139                //              Can be specified via data-mvc-child-props attribute of widget declaration.
140                //              "this" in data-mvc-child-props will have the following properties:
141                //
142                //              - parent - This widget's instance.
143                //              - target - The data item in children.
144                childParams: null,
145
146                // childBindings: Object|Function
147                //              Data bindings for child widget.
148                childBindings: null,
149
150                // children: dojox/mvc/StatefulArray
151                //              The array of data model that is used to render child nodes.
152                children: null,
153
154                /*=====
155                // templateString: String
156                //              The template string for each child items. templateString in child widgets take precedence over this.
157                templateString: "",
158                =====*/
159
160                // partialRebuild: Boolean
161                //              If true, only rebuild repeat items for changed elements. Otherwise, rebuild everything if there is a change in children.
162                partialRebuild: false,
163
164                // _relTargetProp: String
165                //              The name of the property that is used by child widgets for relative data binding.
166                _relTargetProp : "children",
167
168                postMixInProperties: function(){
169                        this.inherited(arguments);
170                        if(this[childTypeAttr]){
171                                this.childType = this[childTypeAttr];
172                        }
173                        if(this[childMixinsAttr]){
174                                this.childMixins = this[childMixinsAttr];
175                        }
176                },
177
178                startup: function(){
179                        this.inherited(arguments);
180                        this._setChildrenAttr(this.children);
181                },
182
183                _setChildrenAttr: function(/*dojo/Stateful*/ value){
184                        // summary:
185                        //              Handler for calls to set("children", val).
186
187                        var children = this.children;
188                        this._set("children", value);
189                        if(this._started && (!this._builtOnce || children != value)){
190                                this._builtOnce = true;
191                                this._buildChildren(value);
192                                if(lang.isArray(value)){
193                                        var _self = this;
194                                        value.watch !== {}.watch && (this._handles = this._handles || []).push(value.watch(function(name, old, current){
195                                                if(!isNaN(name)){
196                                                        var w = _self.getChildren()[name - 0];
197                                                        w && w.set(w._relTargetProp || "target", current);
198                                                }
199                                        }));
200                                }
201                        }
202                },
203
204                _buildChildren: function(/*dojox/mvc/StatefulArray*/ children){
205                        // summary:
206                        //              Create child widgets upon children and inserts them into the container node.
207
208                        unwatchElements(this);
209                        for(var cw = this.getChildren(), w = null; w = cw.pop();){ this.removeChild(w); w.destroy(); }
210                        if(!lang.isArray(children)){ return; }
211
212                        var _self = this,
213                         seq = this._buildChildrenSeq = (this._buildChildrenSeq || 0) + 1,
214                         initial = {idx: 0, removals: [], adds: [].concat(children)},
215                         changes = [initial];
216
217                        function loadedModule(/*Object*/ change){
218                                // summary:
219                                //              The callback function called when modules associated with an array splice have been loaded.
220                                // description:
221                                //              Looks through the queued array splices and process queue entries whose modules have been loaded, by removing/adding child widgets upon the array splice.
222
223                                if(this._beingDestroyed || this._buildChildrenSeq > seq){ return; } // If this _WidgetList is being destroyed, or newer _buildChildren call comes during lazy loading, bail
224
225                                // Associate an object associated with an array splice with the module loaded
226                                var list = [].slice.call(arguments, 1);
227                                change.clz = lang.isFunction(this.childType) || lang.isFunction(this.childMixins) ? list : list[0];
228
229                                // Looks through the queued array splices
230                                for(var item = null; item = changes.shift();){
231                                        // The modules for the array splice have not been loaded, bail
232                                        if(!item.clz){
233                                                changes.unshift(item);
234                                                break;
235                                        }
236
237                                        // Remove child widgets upon the array removals
238                                        for(var i = 0, l = (item.removals || []).length; i < l; ++i){
239                                                this.removeChild(item.idx);
240                                        }
241
242                                        // Create/add child widgets upon the array adds
243                                        array.forEach(array.map(item.adds, function(child, idx){
244                                                var params = {
245                                                        ownerDocument: this.ownerDocument,
246                                                        parent: this,
247                                                        indexAtStartup: item.idx + idx // Won't be updated even if there are removals/adds of repeat items after startup
248                                                }, childClz = lang.isArray(item.clz) ? item.clz[idx] : item.clz;
249                                                params[(lang.isFunction(this.childParams) && this.childParams.call(params, this) || this.childParams || this[childParamsAttr] && evalParams.call(params, this[childParamsAttr]) || {})._relTargetProp || childClz.prototype._relTargetProp || "target"] = child;
250
251                                                var childParams = this.childParams || this[childParamsAttr] && evalParams.call(params, this[childParamsAttr]),
252                                                 childBindings = this.childBindings || this[childBindingsAttr] && evalParams.call(params, this[childBindingsAttr]);
253                                                if(this.templateString && !params.templateString && !childClz.prototype.templateString){ params.templateString = this.templateString; }
254                                                if(childBindings && !params.bindings && !childClz.prototype.bindings){ params.bindings = childBindings; }
255                                                return new childClz(lang.delegate(lang.isFunction(childParams) ? childParams.call(params, this) : childParams, params));
256                                        }, this), function(child, idx){
257                                                this.addChild(child, item.idx + idx);
258                                        }, this);
259                                }
260                        }
261
262                        lang.isFunction(children.watchElements) && (this._handles = this._handles || []).push(children.watchElements(function(idx, removals, adds){
263                                if(!removals || !adds || !_self.partialRebuild){
264                                        // If the entire array is changed, or this WidgetList should rebuild the whole child widgets with every change in array, rebuild the whole
265                                        _self._buildChildren(children);
266                                }else{
267                                        // Otherwise queue the array splice and load modules associated with the additions
268                                        var change = {idx: idx, removals: removals, adds: adds};
269                                        changes.push(change);
270                                        loadModules.call(_self, adds, lang.hitch(_self, loadedModule, change));
271                                }
272                        }));
273
274                        // Load modules associated with the initial data
275                        loadModules.call(this, children, lang.hitch(this, loadedModule, initial));
276                },
277
278                destroy: function(){
279                        unwatchElements(this);
280                        this.inherited(arguments);
281                }
282        });
283
284        WidgetList.prototype[childTypeAttr] = WidgetList.prototype[childMixinsAttr] = WidgetList.prototype[childParamsAttr] = WidgetList.prototype[childBindingsAttr] = ""; // Let parser treat these attributes as string
285        return WidgetList;
286});
Note: See TracBrowser for help on using the repository browser.