1 | define([ |
---|
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 | }); |
---|