source: Dev/branches/rest-dojo-ui/client/dijit/_CssStateMixin.js @ 274

Last change on this file since 274 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: 9.5 KB
Line 
1define([
2        "dojo/touch",
3        "dojo/_base/array", // array.forEach array.map
4        "dojo/_base/declare",   // declare
5        "dojo/dom-class", // domClass.toggle
6        "dojo/_base/lang", // lang.hitch
7        "dojo/_base/window" // win.body
8], function(touch, array, declare, domClass, lang, win){
9
10// module:
11//              dijit/_CssStateMixin
12// summary:
13//              Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
14//              state changes, and also higher-level state changes such becoming disabled or selected.
15
16return declare("dijit._CssStateMixin", [], {
17        // summary:
18        //              Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
19        //              state changes, and also higher-level state changes such becoming disabled or selected.
20        //
21        // description:
22        //              By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
23        //              maintain CSS classes on the widget root node (this.domNode) depending on hover,
24        //              active, focus, etc. state.   Ex: with a baseClass of dijitButton, it will apply the classes
25        //              dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
26        //
27        //              It also sets CSS like dijitButtonDisabled based on widget semantic state.
28        //
29        //              By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
30        //              within the widget).
31
32        // cssStateNodes: [protected] Object
33        //              List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
34        //.
35        //              Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
36        //              (like "dijitUpArrowButton"). Example:
37        //      |               {
38        //      |                       "upArrowButton": "dijitUpArrowButton",
39        //      |                       "downArrowButton": "dijitDownArrowButton"
40        //      |               }
41        //              The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
42        //              is hovered, etc.
43        cssStateNodes: {},
44
45        // hovering: [readonly] Boolean
46        //              True if cursor is over this widget
47        hovering: false,
48
49        // active: [readonly] Boolean
50        //              True if mouse was pressed while over this widget, and hasn't been released yet
51        active: false,
52
53        _applyAttributes: function(){
54                // This code would typically be in postCreate(), but putting in _applyAttributes() for
55                // performance: so the class changes happen before DOM is inserted into the document.
56                // Change back to postCreate() in 2.0.  See #11635.
57
58                this.inherited(arguments);
59
60                // Automatically monitor mouse events (essentially :hover and :active) on this.domNode
61                array.forEach(["onmouseenter", "onmouseleave", touch.press], function(e){
62                        this.connect(this.domNode, e, "_cssMouseEvent");
63                }, this);
64
65                // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
66                array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){
67                        this.watch(attr, lang.hitch(this, "_setStateClass"));
68                }, this);
69
70                // Events on sub nodes within the widget
71                for(var ap in this.cssStateNodes){
72                        this._trackMouseState(this[ap], this.cssStateNodes[ap]);
73                }
74                // Set state initially; there's probably no hover/active/focus state but widget might be
75                // disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
76                this._setStateClass();
77        },
78
79        _cssMouseEvent: function(/*Event*/ event){
80                // summary:
81                //      Sets hovering and active properties depending on mouse state,
82                //      which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
83
84                if(!this.disabled){
85                        switch(event.type){
86                                case "mouseenter":
87                                case "mouseover":       // generated on non-IE browsers even though we connected to mouseenter
88                                        this._set("hovering", true);
89                                        this._set("active", this._mouseDown);
90                                        break;
91
92                                case "mouseleave":
93                                case "mouseout":        // generated on non-IE browsers even though we connected to mouseleave
94                                        this._set("hovering", false);
95                                        this._set("active", false);
96                                        break;
97
98                                case "mousedown":
99                                case "touchpress":
100                                        this._set("active", true);
101                                        this._mouseDown = true;
102                                        // Set a global event to handle mouseup, so it fires properly
103                                        // even if the cursor leaves this.domNode before the mouse up event.
104                                        // Alternately could set active=false on mouseout.
105                                        var mouseUpConnector = this.connect(win.body(), touch.release, function(){
106                                                this._mouseDown = false;
107                                                this._set("active", false);
108                                                this.disconnect(mouseUpConnector);
109                                        });
110                                        break;
111                        }
112                }
113        },
114
115        _setStateClass: function(){
116                // summary:
117                //              Update the visual state of the widget by setting the css classes on this.domNode
118                //              (or this.stateNode if defined) by combining this.baseClass with
119                //              various suffixes that represent the current widget state(s).
120                //
121                // description:
122                //              In the case where a widget has multiple
123                //              states, it sets the class based on all possible
124                //              combinations.  For example, an invalid form widget that is being hovered
125                //              will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
126                //
127                //              The widget may have one or more of the following states, determined
128                //              by this.state, this.checked, this.valid, and this.selected:
129                //                      - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
130                //                      - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
131                //                      - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
132                //                      - Selected - ex: currently selected tab will have this.selected==true
133                //
134                //              In addition, it may have one or more of the following states,
135                //              based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
136                //                      - Disabled      - if the widget is disabled
137                //                      - Active                - if the mouse (or space/enter key?) is being pressed down
138                //                      - Focused               - if the widget has focus
139                //                      - Hover         - if the mouse is over the widget
140
141                // Compute new set of classes
142                var newStateClasses = this.baseClass.split(" ");
143
144                function multiply(modifier){
145                        newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
146                }
147
148                if(!this.isLeftToRight()){
149                        // For RTL mode we need to set an addition class like dijitTextBoxRtl.
150                        multiply("Rtl");
151                }
152
153                var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : "");
154                if(this.checked){
155                        multiply(checkedState);
156                }
157                if(this.state){
158                        multiply(this.state);
159                }
160                if(this.selected){
161                        multiply("Selected");
162                }
163
164                if(this.disabled){
165                        multiply("Disabled");
166                }else if(this.readOnly){
167                        multiply("ReadOnly");
168                }else{
169                        if(this.active){
170                                multiply("Active");
171                        }else if(this.hovering){
172                                multiply("Hover");
173                        }
174                }
175
176                if(this.focused){
177                        multiply("Focused");
178                }
179
180                // Remove old state classes and add new ones.
181                // For performance concerns we only write into domNode.className once.
182                var tn = this.stateNode || this.domNode,
183                        classHash = {}; // set of all classes (state and otherwise) for node
184
185                array.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
186
187                if("_stateClasses" in this){
188                        array.forEach(this._stateClasses, function(c){ delete classHash[c]; });
189                }
190
191                array.forEach(newStateClasses, function(c){ classHash[c] = true; });
192
193                var newClasses = [];
194                for(var c in classHash){
195                        newClasses.push(c);
196                }
197                tn.className = newClasses.join(" ");
198
199                this._stateClasses = newStateClasses;
200        },
201
202        _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
203                // summary:
204                //              Track mouse/focus events on specified node and set CSS class on that node to indicate
205                //              current state.   Usually not called directly, but via cssStateNodes attribute.
206                // description:
207                //              Given class=foo, will set the following CSS class on the node
208                //                      - fooActive: if the user is currently pressing down the mouse button while over the node
209                //                      - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
210                //                      - fooFocus: if the node is focused
211                //
212                //              Note that it won't set any classes if the widget is disabled.
213                // node: DomNode
214                //              Should be a sub-node of the widget, not the top node (this.domNode), since the top node
215                //              is handled specially and automatically just by mixing in this class.
216                // clazz: String
217                //              CSS class name (ex: dijitSliderUpArrow).
218
219                // Current state of node (initially false)
220                // NB: setting specifically to false because domClass.toggle() needs true boolean as third arg
221                var hovering=false, active=false, focused=false;
222
223                var self = this,
224                        cn = lang.hitch(this, "connect", node);
225
226                function setClass(){
227                        var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly);
228                        domClass.toggle(node, clazz+"Hover", hovering && !active && !disabled);
229                        domClass.toggle(node, clazz+"Active", active && !disabled);
230                        domClass.toggle(node, clazz+"Focused", focused && !disabled);
231                }
232
233                // Mouse
234                cn("onmouseenter", function(){
235                        hovering = true;
236                        setClass();
237                });
238                cn("onmouseleave", function(){
239                        hovering = false;
240                        active = false;
241                        setClass();
242                });
243                cn(touch.press, function(){
244                        active = true;
245                        setClass();
246                });
247                cn(touch.release, function(){
248                        active = false;
249                        setClass();
250                });
251
252                // Focus
253                cn("onfocus", function(){
254                        focused = true;
255                        setClass();
256                });
257                cn("onblur", function(){
258                        focused = false;
259                        setClass();
260                });
261
262                // Just in case widget is enabled/disabled while it has focus/hover/active state.
263                // Maybe this is overkill.
264                this.watch("disabled", setClass);
265                this.watch("readOnly", setClass);
266        }
267});
268});
Note: See TracBrowser for help on using the repository browser.