1 | define([ |
---|
2 | "dojo/_base/array", // array.filter array.forEach array.map array.some |
---|
3 | "dojo/_base/Deferred", |
---|
4 | "dojo/aspect", // aspect.after |
---|
5 | "dojo/data/util/sorter", // util.sorter.createSortFunction |
---|
6 | "dojo/_base/declare", // declare |
---|
7 | "dojo/dom", // dom.setSelectable |
---|
8 | "dojo/dom-class", // domClass.toggle |
---|
9 | "dojo/_base/kernel", // _scopeName |
---|
10 | "dojo/_base/lang", // lang.delegate lang.isArray lang.isObject lang.hitch |
---|
11 | "dojo/query", // query |
---|
12 | "dojo/when", |
---|
13 | "dojo/store/util/QueryResults", |
---|
14 | "./_FormValueWidget" |
---|
15 | ], function(array, Deferred, aspect, sorter, declare, dom, domClass, kernel, lang, query, when, |
---|
16 | QueryResults, _FormValueWidget){ |
---|
17 | |
---|
18 | // module: |
---|
19 | // dijit/form/_FormSelectWidget |
---|
20 | |
---|
21 | /*===== |
---|
22 | var __SelectOption = { |
---|
23 | // value: String |
---|
24 | // The value of the option. Setting to empty (or missing) will |
---|
25 | // place a separator at that location |
---|
26 | // label: String |
---|
27 | // The label for our option. It can contain html tags. |
---|
28 | // selected: Boolean |
---|
29 | // Whether or not we are a selected option |
---|
30 | // disabled: Boolean |
---|
31 | // Whether or not this specific option is disabled |
---|
32 | }; |
---|
33 | =====*/ |
---|
34 | |
---|
35 | var _FormSelectWidget = declare("dijit.form._FormSelectWidget", _FormValueWidget, { |
---|
36 | // summary: |
---|
37 | // Extends _FormValueWidget in order to provide "select-specific" |
---|
38 | // values - i.e., those values that are unique to `<select>` elements. |
---|
39 | // This also provides the mechanism for reading the elements from |
---|
40 | // a store, if desired. |
---|
41 | |
---|
42 | // multiple: [const] Boolean |
---|
43 | // Whether or not we are multi-valued |
---|
44 | multiple: false, |
---|
45 | |
---|
46 | // options: __SelectOption[] |
---|
47 | // The set of options for our select item. Roughly corresponds to |
---|
48 | // the html `<option>` tag. |
---|
49 | options: null, |
---|
50 | |
---|
51 | // store: dojo/store/api/Store |
---|
52 | // A store to use for getting our list of options - rather than reading them |
---|
53 | // from the `<option>` html tags. Should support getIdentity(). |
---|
54 | // For back-compat store can also be a dojo/data/api/Identity. |
---|
55 | store: null, |
---|
56 | |
---|
57 | // query: object |
---|
58 | // A query to use when fetching items from our store |
---|
59 | query: null, |
---|
60 | |
---|
61 | // queryOptions: object |
---|
62 | // Query options to use when fetching from the store |
---|
63 | queryOptions: null, |
---|
64 | |
---|
65 | // labelAttr: String? |
---|
66 | // The entries in the drop down list come from this attribute in the dojo.store items. |
---|
67 | // If ``store`` is set, labelAttr must be set too, unless store is an old-style |
---|
68 | // dojo.data store rather than a new dojo/store. |
---|
69 | labelAttr: "", |
---|
70 | |
---|
71 | // onFetch: Function |
---|
72 | // A callback to do with an onFetch - but before any items are actually |
---|
73 | // iterated over (i.e. to filter even further what you want to add) |
---|
74 | onFetch: null, |
---|
75 | |
---|
76 | // sortByLabel: Boolean |
---|
77 | // Flag to sort the options returned from a store by the label of |
---|
78 | // the store. |
---|
79 | sortByLabel: true, |
---|
80 | |
---|
81 | |
---|
82 | // loadChildrenOnOpen: Boolean |
---|
83 | // By default loadChildren is called when the items are fetched from the |
---|
84 | // store. This property allows delaying loadChildren (and the creation |
---|
85 | // of the options/menuitems) until the user clicks the button to open the |
---|
86 | // dropdown. |
---|
87 | loadChildrenOnOpen: false, |
---|
88 | |
---|
89 | // onLoadDeferred: [readonly] dojo.Deferred |
---|
90 | // This is the `dojo.Deferred` returned by setStore(). |
---|
91 | // Calling onLoadDeferred.then() registers your |
---|
92 | // callback to be called only once, when the prior setStore completes. |
---|
93 | onLoadDeferred: null, |
---|
94 | |
---|
95 | getOptions: function(/*anything*/ valueOrIdx){ |
---|
96 | // summary: |
---|
97 | // Returns a given option (or options). |
---|
98 | // valueOrIdx: |
---|
99 | // If passed in as a string, that string is used to look up the option |
---|
100 | // in the array of options - based on the value property. |
---|
101 | // (See dijit/form/_FormSelectWidget.__SelectOption). |
---|
102 | // |
---|
103 | // If passed in a number, then the option with the given index (0-based) |
---|
104 | // within this select will be returned. |
---|
105 | // |
---|
106 | // If passed in a dijit/form/_FormSelectWidget.__SelectOption, the same option will be |
---|
107 | // returned if and only if it exists within this select. |
---|
108 | // |
---|
109 | // If passed an array, then an array will be returned with each element |
---|
110 | // in the array being looked up. |
---|
111 | // |
---|
112 | // If not passed a value, then all options will be returned |
---|
113 | // |
---|
114 | // returns: |
---|
115 | // The option corresponding with the given value or index. |
---|
116 | // null is returned if any of the following are true: |
---|
117 | // |
---|
118 | // - A string value is passed in which doesn't exist |
---|
119 | // - An index is passed in which is outside the bounds of the array of options |
---|
120 | // - A dijit/form/_FormSelectWidget.__SelectOption is passed in which is not a part of the select |
---|
121 | |
---|
122 | // NOTE: the compare for passing in a dijit/form/_FormSelectWidget.__SelectOption checks |
---|
123 | // if the value property matches - NOT if the exact option exists |
---|
124 | // NOTE: if passing in an array, null elements will be placed in the returned |
---|
125 | // array when a value is not found. |
---|
126 | var opts = this.options || []; |
---|
127 | |
---|
128 | if(valueOrIdx == null){ |
---|
129 | return opts; // __SelectOption[] |
---|
130 | } |
---|
131 | if(lang.isArray(valueOrIdx)){ |
---|
132 | return array.map(valueOrIdx, "return this.getOptions(item);", this); // __SelectOption[] |
---|
133 | } |
---|
134 | if(lang.isString(valueOrIdx)){ |
---|
135 | valueOrIdx = { value: valueOrIdx }; |
---|
136 | } |
---|
137 | if(lang.isObject(valueOrIdx)){ |
---|
138 | // We were passed an option - so see if it's in our array (directly), |
---|
139 | // and if it's not, try and find it by value. |
---|
140 | |
---|
141 | if(!array.some(opts, function(option, idx){ |
---|
142 | for(var a in valueOrIdx){ |
---|
143 | if(!(a in option) || option[a] != valueOrIdx[a]){ // == and not === so that 100 matches '100' |
---|
144 | return false; |
---|
145 | } |
---|
146 | } |
---|
147 | valueOrIdx = idx; |
---|
148 | return true; // stops iteration through opts |
---|
149 | })){ |
---|
150 | valueOrIdx = -1; |
---|
151 | } |
---|
152 | } |
---|
153 | if(valueOrIdx >= 0 && valueOrIdx < opts.length){ |
---|
154 | return opts[valueOrIdx]; // __SelectOption |
---|
155 | } |
---|
156 | return null; // null |
---|
157 | }, |
---|
158 | |
---|
159 | addOption: function(/*__SelectOption|__SelectOption[]*/ option){ |
---|
160 | // summary: |
---|
161 | // Adds an option or options to the end of the select. If value |
---|
162 | // of the option is empty or missing, a separator is created instead. |
---|
163 | // Passing in an array of options will yield slightly better performance |
---|
164 | // since the children are only loaded once. |
---|
165 | array.forEach(lang.isArray(option) ? option : [option], function(i){ |
---|
166 | if(i && lang.isObject(i)){ |
---|
167 | this.options.push(i); |
---|
168 | } |
---|
169 | }, this); |
---|
170 | this._loadChildren(); |
---|
171 | }, |
---|
172 | |
---|
173 | removeOption: function(/*String|__SelectOption|Number|Array*/ valueOrIdx){ |
---|
174 | // summary: |
---|
175 | // Removes the given option or options. You can remove by string |
---|
176 | // (in which case the value is removed), number (in which case the |
---|
177 | // index in the options array is removed), or select option (in |
---|
178 | // which case, the select option with a matching value is removed). |
---|
179 | // You can also pass in an array of those values for a slightly |
---|
180 | // better performance since the children are only loaded once. |
---|
181 | // For numeric option values, specify {value: number} as the argument. |
---|
182 | var oldOpts = this.getOptions(lang.isArray(valueOrIdx) ? valueOrIdx : [valueOrIdx]); |
---|
183 | array.forEach(oldOpts, function(option){ |
---|
184 | // We can get null back in our array - if our option was not found. In |
---|
185 | // that case, we don't want to blow up... |
---|
186 | if(option){ |
---|
187 | this.options = array.filter(this.options, function(node){ |
---|
188 | return (node.value !== option.value || node.label !== option.label); |
---|
189 | }); |
---|
190 | this._removeOptionItem(option); |
---|
191 | } |
---|
192 | }, this); |
---|
193 | this._loadChildren(); |
---|
194 | }, |
---|
195 | |
---|
196 | updateOption: function(/*__SelectOption|__SelectOption[]*/ newOption){ |
---|
197 | // summary: |
---|
198 | // Updates the values of the given option. The option to update |
---|
199 | // is matched based on the value of the entered option. Passing |
---|
200 | // in an array of new options will yield better performance since |
---|
201 | // the children will only be loaded once. |
---|
202 | array.forEach(lang.isArray(newOption) ? newOption : [newOption], function(i){ |
---|
203 | var oldOpt = this.getOptions({ value: i.value }), k; |
---|
204 | if(oldOpt){ |
---|
205 | for(k in i){ |
---|
206 | oldOpt[k] = i[k]; |
---|
207 | } |
---|
208 | } |
---|
209 | }, this); |
---|
210 | this._loadChildren(); |
---|
211 | }, |
---|
212 | |
---|
213 | setStore: function(store, selectedValue, fetchArgs){ |
---|
214 | // summary: |
---|
215 | // Sets the store you would like to use with this select widget. |
---|
216 | // The selected value is the value of the new store to set. This |
---|
217 | // function returns the original store, in case you want to reuse |
---|
218 | // it or something. |
---|
219 | // store: dojo/store/api/Store |
---|
220 | // The dojo.store you would like to use - it MUST implement getIdentity() |
---|
221 | // and MAY implement observe(). |
---|
222 | // For backwards-compatibility this can also be a data.data store, in which case |
---|
223 | // it MUST implement dojo/data/api/Identity, |
---|
224 | // and MAY implement dojo/data/api/Notification. |
---|
225 | // selectedValue: anything? |
---|
226 | // The value that this widget should set itself to *after* the store |
---|
227 | // has been loaded |
---|
228 | // fetchArgs: Object? |
---|
229 | // Hash of parameters to set filter on store, etc. |
---|
230 | // |
---|
231 | // - query: new value for Select.query, |
---|
232 | // - queryOptions: new value for Select.queryOptions, |
---|
233 | // - onFetch: callback function for each item in data (Deprecated) |
---|
234 | var oStore = this.store; |
---|
235 | fetchArgs = fetchArgs || {}; |
---|
236 | |
---|
237 | if(oStore !== store){ |
---|
238 | // Our store has changed, so cancel any listeners on old store (remove for 2.0) |
---|
239 | var h; |
---|
240 | while((h = this._notifyConnections.pop())){ |
---|
241 | h.remove(); |
---|
242 | } |
---|
243 | |
---|
244 | // For backwards-compatibility, accept dojo.data store in addition to dojo.store.store. Remove in 2.0. |
---|
245 | if(!store.get){ |
---|
246 | lang.mixin(store, { |
---|
247 | _oldAPI: true, |
---|
248 | get: function(id){ |
---|
249 | // summary: |
---|
250 | // Retrieves an object by it's identity. This will trigger a fetchItemByIdentity. |
---|
251 | // Like dojo.store.DataStore.get() except returns native item. |
---|
252 | var deferred = new Deferred(); |
---|
253 | this.fetchItemByIdentity({ |
---|
254 | identity: id, |
---|
255 | onItem: function(object){ |
---|
256 | deferred.resolve(object); |
---|
257 | }, |
---|
258 | onError: function(error){ |
---|
259 | deferred.reject(error); |
---|
260 | } |
---|
261 | }); |
---|
262 | return deferred.promise; |
---|
263 | }, |
---|
264 | query: function(query, options){ |
---|
265 | // summary: |
---|
266 | // Queries the store for objects. Like dojo/store/DataStore.query() |
---|
267 | // except returned Deferred contains array of native items. |
---|
268 | var deferred = new Deferred(function(){ |
---|
269 | if(fetchHandle.abort){ |
---|
270 | fetchHandle.abort(); |
---|
271 | } |
---|
272 | }); |
---|
273 | deferred.total = new Deferred(); |
---|
274 | var fetchHandle = this.fetch(lang.mixin({ |
---|
275 | query: query, |
---|
276 | onBegin: function(count){ |
---|
277 | deferred.total.resolve(count); |
---|
278 | }, |
---|
279 | onComplete: function(results){ |
---|
280 | deferred.resolve(results); |
---|
281 | }, |
---|
282 | onError: function(error){ |
---|
283 | deferred.reject(error); |
---|
284 | } |
---|
285 | }, options)); |
---|
286 | return new QueryResults(deferred); |
---|
287 | } |
---|
288 | }); |
---|
289 | |
---|
290 | if(store.getFeatures()["dojo.data.api.Notification"]){ |
---|
291 | this._notifyConnections = [ |
---|
292 | aspect.after(store, "onNew", lang.hitch(this, "_onNewItem"), true), |
---|
293 | aspect.after(store, "onDelete", lang.hitch(this, "_onDeleteItem"), true), |
---|
294 | aspect.after(store, "onSet", lang.hitch(this, "_onSetItem"), true) |
---|
295 | ]; |
---|
296 | } |
---|
297 | } |
---|
298 | this._set("store", store); // Our store has changed, so update our notifications |
---|
299 | } |
---|
300 | |
---|
301 | // Remove existing options (if there are any) |
---|
302 | if(this.options && this.options.length){ |
---|
303 | this.removeOption(this.options); |
---|
304 | } |
---|
305 | |
---|
306 | // Cancel listener for updates to old (dojo.data) store |
---|
307 | if(this._queryRes && this._queryRes.close){ |
---|
308 | this._queryRes.close(); |
---|
309 | } |
---|
310 | |
---|
311 | // Cancel listener for updates to new (dojo.store) store |
---|
312 | if(this._observeHandle && this._observeHandle.remove){ |
---|
313 | this._observeHandle.remove(); |
---|
314 | this._observeHandle = null; |
---|
315 | } |
---|
316 | |
---|
317 | // If user has specified new query and query options along with this new store, then use them. |
---|
318 | if(fetchArgs.query){ |
---|
319 | this._set("query", fetchArgs.query); |
---|
320 | this._set("queryOptions", fetchArgs.queryOptions); |
---|
321 | } |
---|
322 | |
---|
323 | // Add our new options |
---|
324 | if(store){ |
---|
325 | this._loadingStore = true; |
---|
326 | this.onLoadDeferred = new Deferred(); |
---|
327 | |
---|
328 | // Run query |
---|
329 | // Save result in this._queryRes so we can cancel the listeners we register below |
---|
330 | this._queryRes = store.query(this.query, this.queryOptions); |
---|
331 | when(this._queryRes, lang.hitch(this, function(items){ |
---|
332 | |
---|
333 | if(this.sortByLabel && !fetchArgs.sort && items.length){ |
---|
334 | if(store.getValue){ |
---|
335 | // Old dojo.data API to access items, remove for 2.0 |
---|
336 | items.sort(sorter.createSortFunction([ |
---|
337 | { |
---|
338 | attribute: store.getLabelAttributes(items[0])[0] |
---|
339 | } |
---|
340 | ], store)); |
---|
341 | }else{ |
---|
342 | // TODO: remove sortByLabel completely for 2.0? It can be handled by queryOptions: {sort: ... }. |
---|
343 | var labelAttr = this.labelAttr; |
---|
344 | items.sort(function(a, b){ |
---|
345 | return a[labelAttr] > b[labelAttr] ? 1 : b[labelAttr] > a[labelAttr] ? -1 : 0; |
---|
346 | }); |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | if(fetchArgs.onFetch){ |
---|
351 | items = fetchArgs.onFetch.call(this, items, fetchArgs); |
---|
352 | } |
---|
353 | |
---|
354 | // TODO: Add these guys as a batch, instead of separately |
---|
355 | array.forEach(items, function(i){ |
---|
356 | this._addOptionForItem(i); |
---|
357 | }, this); |
---|
358 | |
---|
359 | // Register listener for store updates |
---|
360 | if(this._queryRes.observe){ |
---|
361 | // observe returns yet another handle that needs its own explicit gc |
---|
362 | this._observeHandle = this._queryRes.observe(lang.hitch(this, function(object, deletedFrom, insertedInto){ |
---|
363 | if(deletedFrom == insertedInto){ |
---|
364 | this._onSetItem(object); |
---|
365 | }else{ |
---|
366 | if(deletedFrom != -1){ |
---|
367 | this._onDeleteItem(object); |
---|
368 | } |
---|
369 | if(insertedInto != -1){ |
---|
370 | this._onNewItem(object); |
---|
371 | } |
---|
372 | } |
---|
373 | }), true); |
---|
374 | } |
---|
375 | |
---|
376 | // Set our value (which might be undefined), and then tweak |
---|
377 | // it to send a change event with the real value |
---|
378 | this._loadingStore = false; |
---|
379 | this.set("value", "_pendingValue" in this ? this._pendingValue : selectedValue); |
---|
380 | delete this._pendingValue; |
---|
381 | |
---|
382 | if(!this.loadChildrenOnOpen){ |
---|
383 | this._loadChildren(); |
---|
384 | }else{ |
---|
385 | this._pseudoLoadChildren(items); |
---|
386 | } |
---|
387 | this.onLoadDeferred.resolve(true); |
---|
388 | this.onSetStore(); |
---|
389 | }), function(err){ |
---|
390 | console.error('dijit.form.Select: ' + err.toString()); |
---|
391 | this.onLoadDeferred.reject(err); |
---|
392 | }); |
---|
393 | } |
---|
394 | return oStore; // dojo/data/api/Identity |
---|
395 | }, |
---|
396 | |
---|
397 | // TODO: implement set() and watch() for store and query, although not sure how to handle |
---|
398 | // setting them individually rather than together (as in setStore() above) |
---|
399 | |
---|
400 | _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ |
---|
401 | // summary: |
---|
402 | // set the value of the widget. |
---|
403 | // If a string is passed, then we set our value from looking it up. |
---|
404 | if(!this._onChangeActive){ |
---|
405 | priorityChange = null; |
---|
406 | } |
---|
407 | if(this._loadingStore){ |
---|
408 | // Our store is loading - so save our value, and we'll set it when |
---|
409 | // we're done |
---|
410 | this._pendingValue = newValue; |
---|
411 | return; |
---|
412 | } |
---|
413 | if(newValue == null){ |
---|
414 | return; |
---|
415 | } |
---|
416 | if(lang.isArray(newValue)){ |
---|
417 | newValue = array.map(newValue, function(value){ |
---|
418 | return lang.isObject(value) ? value : { value: value }; |
---|
419 | }); // __SelectOption[] |
---|
420 | }else if(lang.isObject(newValue)){ |
---|
421 | newValue = [newValue]; |
---|
422 | }else{ |
---|
423 | newValue = [ |
---|
424 | { value: newValue } |
---|
425 | ]; |
---|
426 | } |
---|
427 | newValue = array.filter(this.getOptions(newValue), function(i){ |
---|
428 | return i && i.value; |
---|
429 | }); |
---|
430 | var opts = this.getOptions() || []; |
---|
431 | if(!this.multiple && (!newValue[0] || !newValue[0].value) && !!opts.length){ |
---|
432 | newValue[0] = opts[0]; |
---|
433 | } |
---|
434 | array.forEach(opts, function(opt){ |
---|
435 | opt.selected = array.some(newValue, function(v){ |
---|
436 | return v.value === opt.value; |
---|
437 | }); |
---|
438 | }); |
---|
439 | var val = array.map(newValue, function(opt){ |
---|
440 | return opt.value; |
---|
441 | }); |
---|
442 | |
---|
443 | if(typeof val == "undefined" || typeof val[0] == "undefined"){ |
---|
444 | return; |
---|
445 | } // not fully initialized yet or a failed value lookup |
---|
446 | var disp = array.map(newValue, function(opt){ |
---|
447 | return opt.label; |
---|
448 | }); |
---|
449 | this._setDisplay(this.multiple ? disp : disp[0]); |
---|
450 | this.inherited(arguments, [ this.multiple ? val : val[0], priorityChange ]); |
---|
451 | this._updateSelection(); |
---|
452 | }, |
---|
453 | |
---|
454 | _getDisplayedValueAttr: function(){ |
---|
455 | // summary: |
---|
456 | // returns the displayed value of the widget |
---|
457 | var ret = array.map([].concat(this.get('selectedOptions')), function(v){ |
---|
458 | if(v && "label" in v){ |
---|
459 | return v.label; |
---|
460 | }else if(v){ |
---|
461 | return v.value; |
---|
462 | } |
---|
463 | return null; |
---|
464 | }, this); |
---|
465 | return this.multiple ? ret : ret[0]; |
---|
466 | }, |
---|
467 | |
---|
468 | _setDisplayedValueAttr: function(label){ |
---|
469 | // summary: |
---|
470 | // Sets the displayed value of the widget |
---|
471 | this.set('value', this.getOptions(typeof label == "string" ? { label: label } : label)); |
---|
472 | }, |
---|
473 | |
---|
474 | _loadChildren: function(){ |
---|
475 | // summary: |
---|
476 | // Loads the children represented by this widget's options. |
---|
477 | // reset the menu to make it populatable on the next click |
---|
478 | if(this._loadingStore){ |
---|
479 | return; |
---|
480 | } |
---|
481 | array.forEach(this._getChildren(), function(child){ |
---|
482 | child.destroyRecursive(); |
---|
483 | }); |
---|
484 | // Add each menu item |
---|
485 | array.forEach(this.options, this._addOptionItem, this); |
---|
486 | |
---|
487 | // Update states |
---|
488 | this._updateSelection(); |
---|
489 | }, |
---|
490 | |
---|
491 | _updateSelection: function(){ |
---|
492 | // summary: |
---|
493 | // Sets the "selected" class on the item for styling purposes |
---|
494 | this.focusedChild = null; |
---|
495 | this._set("value", this._getValueFromOpts()); |
---|
496 | var val = [].concat(this.value); |
---|
497 | if(val && val[0]){ |
---|
498 | var self = this; |
---|
499 | array.forEach(this._getChildren(), function(child){ |
---|
500 | var isSelected = array.some(val, function(v){ |
---|
501 | return child.option && (v === child.option.value); |
---|
502 | }); |
---|
503 | if(isSelected && !self.multiple){ |
---|
504 | self.focusedChild = child; |
---|
505 | } |
---|
506 | domClass.toggle(child.domNode, this.baseClass.replace(/\s+|$/g, "SelectedOption "), isSelected); |
---|
507 | child.domNode.setAttribute("aria-selected", isSelected ? "true" : "false"); |
---|
508 | }, this); |
---|
509 | } |
---|
510 | }, |
---|
511 | |
---|
512 | _getValueFromOpts: function(){ |
---|
513 | // summary: |
---|
514 | // Returns the value of the widget by reading the options for |
---|
515 | // the selected flag |
---|
516 | var opts = this.getOptions() || []; |
---|
517 | if(!this.multiple && opts.length){ |
---|
518 | // Mirror what a select does - choose the first one |
---|
519 | var opt = array.filter(opts, function(i){ |
---|
520 | return i.selected; |
---|
521 | })[0]; |
---|
522 | if(opt && opt.value){ |
---|
523 | return opt.value; |
---|
524 | }else{ |
---|
525 | opts[0].selected = true; |
---|
526 | return opts[0].value; |
---|
527 | } |
---|
528 | }else if(this.multiple){ |
---|
529 | // Set value to be the sum of all selected |
---|
530 | return array.map(array.filter(opts, function(i){ |
---|
531 | return i.selected; |
---|
532 | }), function(i){ |
---|
533 | return i.value; |
---|
534 | }) || []; |
---|
535 | } |
---|
536 | return ""; |
---|
537 | }, |
---|
538 | |
---|
539 | // Internal functions to call when we have store notifications come in |
---|
540 | _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){ |
---|
541 | if(!parentInfo || !parentInfo.parent){ |
---|
542 | // Only add it if we are top-level |
---|
543 | this._addOptionForItem(item); |
---|
544 | } |
---|
545 | }, |
---|
546 | _onDeleteItem: function(/*item*/ item){ |
---|
547 | var store = this.store; |
---|
548 | this.removeOption({value: store.getIdentity(item) }); |
---|
549 | }, |
---|
550 | _onSetItem: function(/*item*/ item){ |
---|
551 | this.updateOption(this._getOptionObjForItem(item)); |
---|
552 | }, |
---|
553 | |
---|
554 | _getOptionObjForItem: function(item){ |
---|
555 | // summary: |
---|
556 | // Returns an option object based off the given item. The "value" |
---|
557 | // of the option item will be the identity of the item, the "label" |
---|
558 | // of the option will be the label of the item. |
---|
559 | |
---|
560 | // remove getLabel() call for 2.0 (it's to support the old dojo.data API) |
---|
561 | var store = this.store, |
---|
562 | label = (this.labelAttr && this.labelAttr in item) ? item[this.labelAttr] : store.getLabel(item), |
---|
563 | value = (label ? store.getIdentity(item) : null); |
---|
564 | return {value: value, label: label, item: item}; // __SelectOption |
---|
565 | }, |
---|
566 | |
---|
567 | _addOptionForItem: function(/*item*/ item){ |
---|
568 | // summary: |
---|
569 | // Creates (and adds) the option for the given item |
---|
570 | var store = this.store; |
---|
571 | if(store.isItemLoaded && !store.isItemLoaded(item)){ |
---|
572 | // We are not loaded - so let's load it and add later. |
---|
573 | // Remove for 2.0 (it's the old dojo.data API) |
---|
574 | store.loadItem({item: item, onItem: function(i){ |
---|
575 | this._addOptionForItem(i); |
---|
576 | }, |
---|
577 | scope: this}); |
---|
578 | return; |
---|
579 | } |
---|
580 | var newOpt = this._getOptionObjForItem(item); |
---|
581 | this.addOption(newOpt); |
---|
582 | }, |
---|
583 | |
---|
584 | constructor: function(params /*===== , srcNodeRef =====*/){ |
---|
585 | // summary: |
---|
586 | // Create the widget. |
---|
587 | // params: Object|null |
---|
588 | // Hash of initialization parameters for widget, including scalar values (like title, duration etc.) |
---|
589 | // and functions, typically callbacks like onClick. |
---|
590 | // The hash can contain any of the widget's properties, excluding read-only properties. |
---|
591 | // srcNodeRef: DOMNode|String? |
---|
592 | // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree |
---|
593 | |
---|
594 | // Saves off our value, if we have an initial one set so we |
---|
595 | // can use it if we have a store as well (see startup()) |
---|
596 | this._oValue = (params || {}).value || null; |
---|
597 | this._notifyConnections = []; // remove for 2.0 |
---|
598 | }, |
---|
599 | |
---|
600 | buildRendering: function(){ |
---|
601 | this.inherited(arguments); |
---|
602 | dom.setSelectable(this.focusNode, false); |
---|
603 | }, |
---|
604 | |
---|
605 | _fillContent: function(){ |
---|
606 | // summary: |
---|
607 | // Loads our options and sets up our dropdown correctly. We |
---|
608 | // don't want any content, so we don't call any inherit chain |
---|
609 | // function. |
---|
610 | if(!this.options){ |
---|
611 | this.options = |
---|
612 | this.srcNodeRef |
---|
613 | ? query("> *", this.srcNodeRef).map( |
---|
614 | function(node){ |
---|
615 | if(node.getAttribute("type") === "separator"){ |
---|
616 | return { value: "", label: "", selected: false, disabled: false }; |
---|
617 | } |
---|
618 | return { |
---|
619 | value: (node.getAttribute("data-" + kernel._scopeName + "-value") || node.getAttribute("value")), |
---|
620 | label: String(node.innerHTML), |
---|
621 | // FIXME: disabled and selected are not valid on complex markup children (which is why we're |
---|
622 | // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?) |
---|
623 | // decide before 1.6 |
---|
624 | selected: node.getAttribute("selected") || false, |
---|
625 | disabled: node.getAttribute("disabled") || false |
---|
626 | }; |
---|
627 | }, |
---|
628 | this) |
---|
629 | : []; |
---|
630 | } |
---|
631 | if(!this.value){ |
---|
632 | this._set("value", this._getValueFromOpts()); |
---|
633 | }else if(this.multiple && typeof this.value == "string"){ |
---|
634 | this._set("value", this.value.split(",")); |
---|
635 | } |
---|
636 | }, |
---|
637 | |
---|
638 | postCreate: function(){ |
---|
639 | // summary: |
---|
640 | // sets up our event handling that we need for functioning |
---|
641 | // as a select |
---|
642 | this.inherited(arguments); |
---|
643 | |
---|
644 | // Make our event connections for updating state |
---|
645 | aspect.after(this, "onChange", lang.hitch(this, "_updateSelection")); |
---|
646 | |
---|
647 | // moved from startup |
---|
648 | // Connects in our store, if we have one defined |
---|
649 | var store = this.store; |
---|
650 | if(store && (store.getIdentity || store.getFeatures()["dojo.data.api.Identity"])){ |
---|
651 | // Temporarily set our store to null so that it will get set |
---|
652 | // and connected appropriately |
---|
653 | this.store = null; |
---|
654 | this.setStore(store, this._oValue); |
---|
655 | } |
---|
656 | }, |
---|
657 | |
---|
658 | startup: function(){ |
---|
659 | // summary: |
---|
660 | this._loadChildren(); |
---|
661 | this.inherited(arguments); |
---|
662 | }, |
---|
663 | |
---|
664 | destroy: function(){ |
---|
665 | // summary: |
---|
666 | // Clean up our connections |
---|
667 | |
---|
668 | var h; |
---|
669 | while((h = this._notifyConnections.pop())){ |
---|
670 | h.remove(); |
---|
671 | } |
---|
672 | |
---|
673 | // Cancel listener for store updates |
---|
674 | if(this._queryRes && this._queryRes.close){ |
---|
675 | this._queryRes.close(); |
---|
676 | } |
---|
677 | |
---|
678 | // Cancel listener for updates to new (dojo.store) store |
---|
679 | if(this._observeHandle && this._observeHandle.remove){ |
---|
680 | this._observeHandle.remove(); |
---|
681 | this._observeHandle = null; |
---|
682 | } |
---|
683 | |
---|
684 | this.inherited(arguments); |
---|
685 | }, |
---|
686 | |
---|
687 | _addOptionItem: function(/*__SelectOption*/ /*===== option =====*/){ |
---|
688 | // summary: |
---|
689 | // User-overridable function which, for the given option, adds an |
---|
690 | // item to the select. If the option doesn't have a value, then a |
---|
691 | // separator is added in that place. Make sure to store the option |
---|
692 | // in the created option widget. |
---|
693 | }, |
---|
694 | |
---|
695 | _removeOptionItem: function(/*__SelectOption*/ /*===== option =====*/){ |
---|
696 | // summary: |
---|
697 | // User-overridable function which, for the given option, removes |
---|
698 | // its item from the select. |
---|
699 | }, |
---|
700 | |
---|
701 | _setDisplay: function(/*String or String[]*/ /*===== newDisplay =====*/){ |
---|
702 | // summary: |
---|
703 | // Overridable function which will set the display for the |
---|
704 | // widget. newDisplay is either a string (in the case of |
---|
705 | // single selects) or array of strings (in the case of multi-selects) |
---|
706 | }, |
---|
707 | |
---|
708 | _getChildren: function(){ |
---|
709 | // summary: |
---|
710 | // Overridable function to return the children that this widget contains. |
---|
711 | return []; |
---|
712 | }, |
---|
713 | |
---|
714 | _getSelectedOptionsAttr: function(){ |
---|
715 | // summary: |
---|
716 | // hooks into this.attr to provide a mechanism for getting the |
---|
717 | // option items for the current value of the widget. |
---|
718 | return this.getOptions({ selected: true }); |
---|
719 | }, |
---|
720 | |
---|
721 | _pseudoLoadChildren: function(/*item[]*/ /*===== items =====*/){ |
---|
722 | // summary: |
---|
723 | // a function that will "fake" loading children, if needed, and |
---|
724 | // if we have set to not load children until the widget opens. |
---|
725 | // items: |
---|
726 | // An array of items that will be loaded, when needed |
---|
727 | }, |
---|
728 | |
---|
729 | onSetStore: function(){ |
---|
730 | // summary: |
---|
731 | // a function that can be connected to in order to receive a |
---|
732 | // notification that the store has finished loading and all options |
---|
733 | // from that store are available |
---|
734 | } |
---|
735 | }); |
---|
736 | |
---|
737 | /*===== |
---|
738 | _FormSelectWidget.__SelectOption = __SelectOption; |
---|
739 | =====*/ |
---|
740 | |
---|
741 | return _FormSelectWidget; |
---|
742 | }); |
---|