source: Dev/trunk/src/client/dojox/form/CheckedMultiSelect.js

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

Added Dojo 1.9.3 release.

File size: 14.9 KB
Line 
1define([
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
32var 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
128var 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
207var 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
284var 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
548return formCheckedMultiSelect;
549});
Note: See TracBrowser for help on using the repository browser.