1 | define([ |
---|
2 | "dojo/_base/declare", |
---|
3 | "dojo/_base/lang", |
---|
4 | "dojo/_base/array", |
---|
5 | "dojo/_base/event", |
---|
6 | "dojo/dom-geometry", |
---|
7 | "dojo/dom-class", |
---|
8 | "dojo/dom-construct", |
---|
9 | "dojo/i18n", |
---|
10 | "dijit/_WidgetBase", |
---|
11 | "dijit/_TemplatedMixin", |
---|
12 | "dijit/_WidgetsInTemplateMixin", |
---|
13 | "dijit/registry", |
---|
14 | "dijit/Menu", |
---|
15 | "dijit/MenuItem", |
---|
16 | "dijit/Tooltip", |
---|
17 | "dijit/form/_FormSelectWidget", |
---|
18 | "dijit/form/ComboButton", |
---|
19 | "dojo/text!dojox/form/resources/_CheckedMultiSelectMenuItem.html", |
---|
20 | "dojo/text!dojox/form/resources/_CheckedMultiSelectItem.html", |
---|
21 | "dojo/text!dojox/form/resources/CheckedMultiSelect.html", |
---|
22 | "dojo/i18n!dojox/form/nls/CheckedMultiSelect", |
---|
23 | "dijit/form/CheckBox" // template |
---|
24 | ], function(declare, lang, array, event, domGeometry, domClass, domConstruct, i18n, Widget, TemplatedMixin, WidgetsInTemplateMixin, registry, Menu, MenuItem, Tooltip, FormSelectWidget, ComboButton, CheckedMultiSelectMenuItem, CheckedMultiSelectItem, CheckedMultiSelect, nlsCheckedMultiSelect){ |
---|
25 | |
---|
26 | // module: |
---|
27 | // dojox/form/CheckedMultiSelect |
---|
28 | // summary: |
---|
29 | // Extends the core dojox.form.CheckedMultiSelect to provide a "checkbox" selector |
---|
30 | |
---|
31 | |
---|
32 | var formCheckedMultiSelectItem = declare("dojox.form._CheckedMultiSelectItem", [Widget, TemplatedMixin, WidgetsInTemplateMixin], { |
---|
33 | // summary: |
---|
34 | // The individual items for a CheckedMultiSelect |
---|
35 | |
---|
36 | templateString: CheckedMultiSelectItem, |
---|
37 | |
---|
38 | baseClass: "dojoxMultiSelectItem", |
---|
39 | |
---|
40 | // option: dojox.form.__SelectOption |
---|
41 | // The option that is associated with this item |
---|
42 | option: null, |
---|
43 | parent: null, |
---|
44 | |
---|
45 | // disabled: boolean |
---|
46 | // Whether or not this widget is disabled |
---|
47 | disabled: false, |
---|
48 | |
---|
49 | // readOnly: boolean |
---|
50 | // Whether or not this widget is readOnly |
---|
51 | readOnly: false, |
---|
52 | |
---|
53 | postMixInProperties: function(){ |
---|
54 | // summary: |
---|
55 | // Set the appropriate _subClass value - based on if we are multi- |
---|
56 | // or single-select |
---|
57 | this._type = this.parent.multiple ? |
---|
58 | {type: "checkbox", baseClass: "dijitCheckBox"} : |
---|
59 | {type: "radio", baseClass: "dijitRadio"}; |
---|
60 | // use global disabled/readOnly if set to true, otherwise use per-option setting |
---|
61 | if(!this.disabled){ |
---|
62 | this.disabled = this.option.disabled = this.option.disabled||false; |
---|
63 | } |
---|
64 | if(!this.readOnly){ |
---|
65 | this.readOnly = this.option.readOnly = this.option.readOnly||false; |
---|
66 | } |
---|
67 | this.inherited(arguments); |
---|
68 | }, |
---|
69 | |
---|
70 | postCreate: function(){ |
---|
71 | // summary: |
---|
72 | // Set innerHTML here - since the template gets messed up sometimes |
---|
73 | // with rich text |
---|
74 | this.inherited(arguments); |
---|
75 | this.labelNode.innerHTML = this.option.label; |
---|
76 | }, |
---|
77 | |
---|
78 | _changeBox: function(){ |
---|
79 | // summary: |
---|
80 | // Called to force the select to match the state of the check box |
---|
81 | // (only on click of the checkbox) Radio-based calls _setValueAttr |
---|
82 | // instead. |
---|
83 | if(this.get("disabled") || this.get("readOnly")){ return; } |
---|
84 | if(this.parent.multiple){ |
---|
85 | this.option.selected = this.checkBox.get('value') && true; |
---|
86 | }else{ |
---|
87 | this.parent.set('value', this.option.value); |
---|
88 | } |
---|
89 | // fire the parent's change |
---|
90 | this.parent._updateSelection(); |
---|
91 | |
---|
92 | // refocus the parent |
---|
93 | this.parent.focus(); |
---|
94 | }, |
---|
95 | |
---|
96 | _onClick: function(e){ |
---|
97 | // summary: |
---|
98 | // Sets the click state (passes through to the check box) |
---|
99 | if(this.get("disabled") || this.get("readOnly")){ |
---|
100 | event.stop(e); |
---|
101 | }else{ |
---|
102 | this.checkBox._onClick(e); |
---|
103 | } |
---|
104 | }, |
---|
105 | |
---|
106 | _updateBox: function(){ |
---|
107 | // summary: |
---|
108 | // Called to force the box to match the state of the select |
---|
109 | this.checkBox.set('value', this.option.selected); |
---|
110 | }, |
---|
111 | |
---|
112 | _setDisabledAttr: function(value){ |
---|
113 | // summary: |
---|
114 | // Disables (or enables) all the children as well |
---|
115 | this.disabled = value||this.option.disabled; |
---|
116 | this.checkBox.set("disabled", this.disabled); |
---|
117 | domClass.toggle(this.domNode, "dojoxMultiSelectDisabled", this.disabled); |
---|
118 | }, |
---|
119 | |
---|
120 | _setReadOnlyAttr: function(value){ |
---|
121 | // summary: |
---|
122 | // Sets read only (or unsets) all the children as well |
---|
123 | this.checkBox.set("readOnly", value); |
---|
124 | this.readOnly = value; |
---|
125 | } |
---|
126 | }); |
---|
127 | |
---|
128 | var formCheckedMultiSelectMenu = declare("dojox.form._CheckedMultiSelectMenu", Menu, { |
---|
129 | // summary: |
---|
130 | // An internally-used menu for dropdown that allows us a vertical scrollbar |
---|
131 | |
---|
132 | multiple: false, |
---|
133 | |
---|
134 | buildRendering: function(){ |
---|
135 | // summary: |
---|
136 | // Stub in our own changes, so that our domNode is not a table |
---|
137 | // otherwise, we won't respond correctly to heights/overflows |
---|
138 | this.inherited(arguments); |
---|
139 | var o = (this.menuTableNode = this.domNode), |
---|
140 | n = (this.domNode = domConstruct.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); |
---|
141 | if(o.parentNode){ |
---|
142 | o.parentNode.replaceChild(n, o); |
---|
143 | } |
---|
144 | domClass.remove(o, "dijitMenuTable"); |
---|
145 | n.className = o.className + " dojoxCheckedMultiSelectMenu"; |
---|
146 | o.className = "dijitReset dijitMenuTable"; |
---|
147 | o.setAttribute("role", "listbox"); |
---|
148 | n.setAttribute("role", "presentation"); |
---|
149 | n.appendChild(o); |
---|
150 | }, |
---|
151 | |
---|
152 | resize: function(/*Object*/ mb){ |
---|
153 | // summary: |
---|
154 | // Overridden so that we are able to handle resizing our |
---|
155 | // internal widget. Note that this is not a "full" resize |
---|
156 | // implementation - it only works correctly if you pass it a |
---|
157 | // marginBox. |
---|
158 | // |
---|
159 | // mb: Object |
---|
160 | // The margin box to set this dropdown to. |
---|
161 | if(mb){ |
---|
162 | domGeometry.setMarginBox(this.domNode, mb); |
---|
163 | if("w" in mb){ |
---|
164 | // We've explicitly set the wrapper <div>'s width, so set <table> width to match. |
---|
165 | // 100% is safer than a pixel value because there may be a scroll bar with |
---|
166 | // browser/OS specific width. |
---|
167 | this.menuTableNode.style.width = "100%"; |
---|
168 | } |
---|
169 | } |
---|
170 | }, |
---|
171 | |
---|
172 | onClose: function(){ |
---|
173 | this.inherited(arguments); |
---|
174 | if(this.menuTableNode){ |
---|
175 | // Erase possible width: 100% setting from _SelectMenu.resize(). |
---|
176 | // Leaving it would interfere with the next openDropDown() call, which |
---|
177 | // queries the natural size of the drop down. |
---|
178 | this.menuTableNode.style.width = ""; |
---|
179 | } |
---|
180 | }, |
---|
181 | |
---|
182 | onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ |
---|
183 | // summary: |
---|
184 | // Handle clicks on an item. |
---|
185 | // tags: |
---|
186 | // private |
---|
187 | |
---|
188 | // this can't be done in _onFocus since the _onFocus events occurs asynchronously |
---|
189 | if(typeof this.isShowingNow == 'undefined'){ // non-popup menu |
---|
190 | this._markActive(); |
---|
191 | } |
---|
192 | |
---|
193 | this.focusChild(item); |
---|
194 | |
---|
195 | if(item.disabled || item.readOnly){ return false; } |
---|
196 | |
---|
197 | if(!this.multiple){ |
---|
198 | // before calling user defined handler, close hierarchy of menus |
---|
199 | // and restore focus to place it was when menu was opened |
---|
200 | this.onExecute(); |
---|
201 | } |
---|
202 | // user defined handler for click |
---|
203 | item.onClick(evt); |
---|
204 | } |
---|
205 | }); |
---|
206 | |
---|
207 | var formCheckedMultiSelectMenuItem = declare("dojox.form._CheckedMultiSelectMenuItem", MenuItem, { |
---|
208 | // summary: |
---|
209 | // A checkbox-like menu item for toggling on and off |
---|
210 | |
---|
211 | templateString: CheckedMultiSelectMenuItem, |
---|
212 | |
---|
213 | // option: dojox.form.__SelectOption |
---|
214 | // The option that is associated with this item |
---|
215 | option: null, |
---|
216 | |
---|
217 | // reference of dojox.form._CheckedMultiSelectMenu |
---|
218 | parent: null, |
---|
219 | |
---|
220 | // icon of the checkbox/radio button |
---|
221 | iconClass: "", |
---|
222 | |
---|
223 | postMixInProperties: function(){ |
---|
224 | // summary: |
---|
225 | // Set the appropriate _subClass value - based on if we are multi- |
---|
226 | // or single-select |
---|
227 | if(this.parent.multiple){ |
---|
228 | this._iconClass = "dojoxCheckedMultiSelectMenuCheckBoxItemIcon"; |
---|
229 | this._type = {type: "checkbox"}; |
---|
230 | }else{ |
---|
231 | this._iconClass = ""; |
---|
232 | this._type = {type: "hidden"}; |
---|
233 | } |
---|
234 | this.disabled = this.option.disabled; |
---|
235 | this.checked = this.option.selected; |
---|
236 | this.label = this.option.label; |
---|
237 | this.readOnly = this.option.readOnly; |
---|
238 | this.inherited(arguments); |
---|
239 | }, |
---|
240 | |
---|
241 | onChange: function(/*Boolean*/ checked){ |
---|
242 | // summary: |
---|
243 | // User defined function to handle check/uncheck events |
---|
244 | // tags: |
---|
245 | // callback |
---|
246 | }, |
---|
247 | |
---|
248 | _updateBox: function(){ |
---|
249 | // summary: |
---|
250 | // Called to force the box to match the state of the select |
---|
251 | domClass.toggle(this.domNode, "dojoxCheckedMultiSelectMenuItemChecked", !!this.option.selected); |
---|
252 | this.domNode.setAttribute("aria-checked", this.option.selected); |
---|
253 | this.inputNode.checked = this.option.selected; |
---|
254 | if(!this.parent.multiple){ |
---|
255 | domClass.toggle(this.domNode, "dijitSelectSelectedOption", !!this.option.selected); |
---|
256 | } |
---|
257 | }, |
---|
258 | |
---|
259 | _onClick: function(/*Event*/ e){ |
---|
260 | // summary: |
---|
261 | // Clicking this item just toggles its state |
---|
262 | // tags: |
---|
263 | // private |
---|
264 | if(!this.disabled && !this.readOnly){ |
---|
265 | if(this.parent.multiple){ |
---|
266 | this.option.selected = !this.option.selected; |
---|
267 | this.parent.onChange(); |
---|
268 | this.onChange(this.option.selected); |
---|
269 | }else{ |
---|
270 | if(!this.option.selected){ |
---|
271 | array.forEach(this.parent.getChildren(), function(item){ |
---|
272 | item.option.selected = false; |
---|
273 | }); |
---|
274 | this.option.selected = true; |
---|
275 | this.parent.onChange(); |
---|
276 | this.onChange(this.option.selected); |
---|
277 | } |
---|
278 | } |
---|
279 | } |
---|
280 | this.inherited(arguments); |
---|
281 | } |
---|
282 | }); |
---|
283 | |
---|
284 | var formCheckedMultiSelect = declare("dojox.form.CheckedMultiSelect", FormSelectWidget, { |
---|
285 | // summary: |
---|
286 | // Extends the core dijit MultiSelect to provide a "checkbox" selector |
---|
287 | |
---|
288 | templateString: CheckedMultiSelect, |
---|
289 | |
---|
290 | baseClass: "dojoxCheckedMultiSelect", |
---|
291 | |
---|
292 | // required: Boolean |
---|
293 | // User is required to check at least one item. |
---|
294 | required: false, |
---|
295 | |
---|
296 | // invalidMessage: String |
---|
297 | // The message to display if value is invalid. |
---|
298 | invalidMessage: "$_unset_$", |
---|
299 | |
---|
300 | // _message: String |
---|
301 | // Currently displayed message |
---|
302 | _message: "", |
---|
303 | |
---|
304 | // dropDown: Boolean |
---|
305 | // Drop down version or not |
---|
306 | dropDown: false, |
---|
307 | |
---|
308 | // labelText: String |
---|
309 | // Label of the drop down button |
---|
310 | labelText: "", |
---|
311 | |
---|
312 | // tooltipPosition: String[] |
---|
313 | // See description of `Tooltip.defaultPosition` for details on this parameter. |
---|
314 | tooltipPosition: [], |
---|
315 | |
---|
316 | postMixInProperties: function(){ |
---|
317 | this.inherited(arguments); |
---|
318 | this._nlsResources = i18n.getLocalization("dojox.form", "CheckedMultiSelect", this.lang); |
---|
319 | if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this._nlsResources.invalidMessage; } |
---|
320 | }, |
---|
321 | |
---|
322 | _fillContent: function(){ |
---|
323 | // summary: |
---|
324 | // Set the value to be the first, or the selected index |
---|
325 | this.inherited(arguments); |
---|
326 | |
---|
327 | // set value from selected option |
---|
328 | if(this.options.length && !this.value && this.srcNodeRef){ |
---|
329 | var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT |
---|
330 | this.value = this.options[si >= 0 ? si : 0].value; |
---|
331 | } |
---|
332 | if(this.dropDown){ |
---|
333 | domClass.toggle(this.selectNode, "dojoxCheckedMultiSelectHidden"); |
---|
334 | this.dropDownMenu = new formCheckedMultiSelectMenu({ |
---|
335 | id: this.id + "_menu", |
---|
336 | style: "display: none;", |
---|
337 | multiple: this.multiple, |
---|
338 | onChange: lang.hitch(this, "_updateSelection") |
---|
339 | }); |
---|
340 | } |
---|
341 | }, |
---|
342 | |
---|
343 | startup: function(){ |
---|
344 | // summary: |
---|
345 | // Set the value to be the first, or the selected index |
---|
346 | if(this.dropDown){ |
---|
347 | this.dropDownButton = new ComboButton({ |
---|
348 | label: this.labelText, |
---|
349 | dropDown: this.dropDownMenu, |
---|
350 | baseClass: "dojoxCheckedMultiSelectButton", |
---|
351 | maxHeight: this.maxHeight |
---|
352 | }, this.comboButtonNode); |
---|
353 | } |
---|
354 | this.inherited(arguments); |
---|
355 | }, |
---|
356 | |
---|
357 | _onMouseDown: function(e){ |
---|
358 | // summary: |
---|
359 | // Cancels the mousedown event to prevent others from stealing |
---|
360 | // focus |
---|
361 | event.stop(e); |
---|
362 | }, |
---|
363 | |
---|
364 | validator: function(){ |
---|
365 | // summary: |
---|
366 | // Overridable function used to validate that an item is selected if required = |
---|
367 | // true. |
---|
368 | // tags: |
---|
369 | // protected |
---|
370 | if(!this.required){ return true; } |
---|
371 | return array.some(this.getOptions(), function(opt){ |
---|
372 | return opt.selected && opt.value != null && opt.value.toString().length != 0; |
---|
373 | }); |
---|
374 | }, |
---|
375 | |
---|
376 | validate: function(isFocused){ |
---|
377 | Tooltip.hide(this.domNode); |
---|
378 | var isValid = this.isValid(isFocused); |
---|
379 | if(!isValid){ this.displayMessage(this.invalidMessage); } |
---|
380 | return isValid; |
---|
381 | }, |
---|
382 | |
---|
383 | isValid: function(/*Boolean*/ isFocused){ |
---|
384 | // summary: |
---|
385 | // Tests if the required items are selected. |
---|
386 | // Can override with your own routine in a subclass. |
---|
387 | // tags: |
---|
388 | // protected |
---|
389 | return this.validator(); |
---|
390 | }, |
---|
391 | |
---|
392 | getErrorMessage: function(/*Boolean*/ isFocused){ |
---|
393 | // summary: |
---|
394 | // Return an error message to show if appropriate |
---|
395 | // tags: |
---|
396 | // protected |
---|
397 | return this.invalidMessage; |
---|
398 | }, |
---|
399 | |
---|
400 | displayMessage: function(/*String*/ message){ |
---|
401 | // summary: |
---|
402 | // Overridable method to display validation errors/hints. |
---|
403 | // By default uses a tooltip. |
---|
404 | // tags: |
---|
405 | // extension |
---|
406 | Tooltip.hide(this.domNode); |
---|
407 | if(message){ |
---|
408 | Tooltip.show(message, this.domNode, this.tooltipPosition); |
---|
409 | } |
---|
410 | }, |
---|
411 | |
---|
412 | onAfterAddOptionItem: function(item, option){ |
---|
413 | // summary: |
---|
414 | // a function that can be connected to in order to receive a |
---|
415 | // notification that an item as been added to this dijit. |
---|
416 | }, |
---|
417 | |
---|
418 | _addOptionItem: function(/*dojox.form.__SelectOption*/ option){ |
---|
419 | var item; |
---|
420 | if(this.dropDown){ |
---|
421 | item = new formCheckedMultiSelectMenuItem({ |
---|
422 | option: option, |
---|
423 | parent: this.dropDownMenu |
---|
424 | }); |
---|
425 | this.dropDownMenu.addChild(item); |
---|
426 | }else{ |
---|
427 | item = new formCheckedMultiSelectItem({ |
---|
428 | option: option, |
---|
429 | parent: this, |
---|
430 | disabled: this.disabled, |
---|
431 | readOnly: this.readOnly |
---|
432 | }); |
---|
433 | this.wrapperDiv.appendChild(item.domNode); |
---|
434 | } |
---|
435 | this.onAfterAddOptionItem(item, option); |
---|
436 | }, |
---|
437 | |
---|
438 | _refreshState: function(){ |
---|
439 | // summary: |
---|
440 | // Validate if selection changes. |
---|
441 | this.validate(this.focused); |
---|
442 | }, |
---|
443 | |
---|
444 | onChange: function(newValue){ |
---|
445 | // summary: |
---|
446 | // Validate if selection changes. |
---|
447 | this._refreshState(); |
---|
448 | }, |
---|
449 | |
---|
450 | reset: function(){ |
---|
451 | // Overridden so that the state will be cleared. |
---|
452 | this.inherited(arguments); |
---|
453 | Tooltip.hide(this.domNode); |
---|
454 | }, |
---|
455 | |
---|
456 | _updateSelection: function(){ |
---|
457 | this.inherited(arguments); |
---|
458 | this._handleOnChange(this.value); |
---|
459 | array.forEach(this._getChildren(), function(item){ |
---|
460 | item._updateBox(); |
---|
461 | }); |
---|
462 | domConstruct.empty(this.containerNode); |
---|
463 | var self = this; |
---|
464 | array.forEach(this.value, function(item){ |
---|
465 | var opt = domConstruct.create("option", { |
---|
466 | "value": item, |
---|
467 | "label": item, |
---|
468 | "selected": "selected" |
---|
469 | }); |
---|
470 | domConstruct.place(opt, self.containerNode); |
---|
471 | }); |
---|
472 | if(this.dropDown && this.dropDownButton){ |
---|
473 | var i = 0, label = ""; |
---|
474 | array.forEach(this.options, function(option){ |
---|
475 | if(option.selected){ |
---|
476 | i++; |
---|
477 | label = option.label; |
---|
478 | } |
---|
479 | }); |
---|
480 | this.dropDownButton.set("label", this.multiple ? |
---|
481 | lang.replace(this._nlsResources.multiSelectLabelText, {num: i}) : |
---|
482 | label); |
---|
483 | } |
---|
484 | }, |
---|
485 | |
---|
486 | _getChildren: function(){ |
---|
487 | if(this.dropDown){ |
---|
488 | return this.dropDownMenu.getChildren(); |
---|
489 | }else{ |
---|
490 | return array.map(this.wrapperDiv.childNodes, function(n){ |
---|
491 | return registry.byNode(n); |
---|
492 | }); |
---|
493 | } |
---|
494 | }, |
---|
495 | |
---|
496 | invertSelection: function(onChange){ |
---|
497 | // summary: |
---|
498 | // Invert the selection |
---|
499 | // onChange: Boolean |
---|
500 | // If null, onChange is not fired. |
---|
501 | if(this.multiple){ |
---|
502 | array.forEach(this.options, function(i){ |
---|
503 | i.selected = !i.selected; |
---|
504 | }); |
---|
505 | this._updateSelection(); |
---|
506 | } |
---|
507 | }, |
---|
508 | |
---|
509 | _setDisabledAttr: function(value){ |
---|
510 | // summary: |
---|
511 | // Disable (or enable) all the children as well |
---|
512 | this.inherited(arguments); |
---|
513 | if(this.dropDown){ |
---|
514 | this.dropDownButton.set("disabled", value); |
---|
515 | } |
---|
516 | array.forEach(this._getChildren(), function(node){ |
---|
517 | if(node && node.set){ |
---|
518 | node.set("disabled", value); |
---|
519 | } |
---|
520 | }); |
---|
521 | }, |
---|
522 | |
---|
523 | _setReadOnlyAttr: function(value){ |
---|
524 | // summary: |
---|
525 | // Sets read only (or unsets) all the children as well |
---|
526 | this.inherited(arguments); |
---|
527 | if("readOnly" in this.attributeMap){ |
---|
528 | this[this.attributeMap.readOnly].setAttribute("readonly", value); |
---|
529 | } |
---|
530 | this.readOnly = value; |
---|
531 | array.forEach(this._getChildren(), function(node){ |
---|
532 | if(node && node.set){ |
---|
533 | node.set("readOnly", value); |
---|
534 | } |
---|
535 | }); |
---|
536 | }, |
---|
537 | |
---|
538 | uninitialize: function(){ |
---|
539 | Tooltip.hide(this.domNode); |
---|
540 | // Make sure these children are destroyed |
---|
541 | array.forEach(this._getChildren(), function(child){ |
---|
542 | child.destroyRecursive(); |
---|
543 | }); |
---|
544 | this.inherited(arguments); |
---|
545 | } |
---|
546 | }); |
---|
547 | |
---|
548 | return formCheckedMultiSelect; |
---|
549 | }); |
---|