source: Dev/branches/rest-dojo-ui/client/dojox/form/CheckedMultiSelect.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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