]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/_CssStateMixin.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dijit / _CssStateMixin.js.uncompressed.js
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 });