]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
1 | define("dijit/_CssStateMixin", [ |
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 | ||
16 | return 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 | }); |