]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/_CssStateMixin.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dijit / _CssStateMixin.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("dijit/_CssStateMixin", [
2 "dojo/_base/array", // array.forEach array.map
3 "dojo/_base/declare", // declare
4 "dojo/dom", // dom.isDescendant()
5 "dojo/dom-class", // domClass.toggle
6 "dojo/has",
7 "dojo/_base/lang", // lang.hitch
8 "dojo/on",
9 "dojo/ready",
10 "dojo/_base/window", // win.body
11 "./registry"
12], function(array, declare, dom, domClass, has, lang, on, ready, win, registry){
13
14// module:
15// dijit/_CssStateMixin
16
17var CssStateMixin = declare("dijit._CssStateMixin", [], {
18 // summary:
19 // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus
20 // state changes, and also higher-level state changes such becoming disabled or selected.
21 //
22 // description:
23 // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically
24 // maintain CSS classes on the widget root node (this.domNode) depending on hover,
25 // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes
26 // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it.
27 //
28 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
29 //
30 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
31 // within the widget).
32
33 // cssStateNodes: [protected] Object
34 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
35 //
36 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
37 // (like "dijitUpArrowButton"). Example:
38 // | {
39 // | "upArrowButton": "dijitUpArrowButton",
40 // | "downArrowButton": "dijitDownArrowButton"
41 // | }
42 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
43 // is hovered, etc.
44 cssStateNodes: {},
45
46 // hovering: [readonly] Boolean
47 // True if cursor is over this widget
48 hovering: false,
49
50 // active: [readonly] Boolean
51 // True if mouse was pressed while over this widget, and hasn't been released yet
52 active: false,
53
54 _applyAttributes: function(){
55 // This code would typically be in postCreate(), but putting in _applyAttributes() for
56 // performance: so the class changes happen before DOM is inserted into the document.
57 // Change back to postCreate() in 2.0. See #11635.
58
59 this.inherited(arguments);
60
61 // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
62 array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active", "_opened"], function(attr){
63 this.watch(attr, lang.hitch(this, "_setStateClass"));
64 }, this);
65
66 // Track hover and active mouse events on widget root node, plus possibly on subnodes
67 for(var ap in this.cssStateNodes){
68 this._trackMouseState(this[ap], this.cssStateNodes[ap]);
69 }
70 this._trackMouseState(this.domNode, this.baseClass);
71
72 // Set state initially; there's probably no hover/active/focus state but widget might be
73 // disabled/readonly/checked/selected so we want to set CSS classes for those conditions.
74 this._setStateClass();
75 },
76
77 _cssMouseEvent: function(/*Event*/ event){
78 // summary:
79 // Handler for CSS event on this.domNode. Sets hovering and active properties depending on mouse state,
80 // which triggers _setStateClass() to set appropriate CSS classes for this.domNode.
81
82 if(!this.disabled){
83 switch(event.type){
84 case "mouseover":
85 this._set("hovering", true);
86 this._set("active", this._mouseDown);
87 break;
88 case "mouseout":
89 this._set("hovering", false);
90 this._set("active", false);
91 break;
92 case "mousedown":
93 case "touchstart":
94 this._set("active", true);
95 break;
96 case "mouseup":
97 case "touchend":
98 this._set("active", false);
99 break;
100 }
101 }
102 },
103
104 _setStateClass: function(){
105 // summary:
106 // Update the visual state of the widget by setting the css classes on this.domNode
107 // (or this.stateNode if defined) by combining this.baseClass with
108 // various suffixes that represent the current widget state(s).
109 //
110 // description:
111 // In the case where a widget has multiple
112 // states, it sets the class based on all possible
113 // combinations. For example, an invalid form widget that is being hovered
114 // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover".
115 //
116 // The widget may have one or more of the following states, determined
117 // by this.state, this.checked, this.valid, and this.selected:
118 //
119 // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid
120 // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet
121 // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true
122 // - Selected - ex: currently selected tab will have this.selected==true
123 //
124 // In addition, it may have one or more of the following states,
125 // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused):
126 //
127 // - Disabled - if the widget is disabled
128 // - Active - if the mouse (or space/enter key?) is being pressed down
129 // - Focused - if the widget has focus
130 // - Hover - if the mouse is over the widget
131
132 // Compute new set of classes
133 var newStateClasses = this.baseClass.split(" ");
134
135 function multiply(modifier){
136 newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier);
137 }
138
139 if(!this.isLeftToRight()){
140 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
141 multiply("Rtl");
142 }
143
144 var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : "");
145 if(this.checked){
146 multiply(checkedState);
147 }
148 if(this.state){
149 multiply(this.state);
150 }
151 if(this.selected){
152 multiply("Selected");
153 }
154 if(this._opened){
155 multiply("Opened");
156 }
157
158 if(this.disabled){
159 multiply("Disabled");
160 }else if(this.readOnly){
161 multiply("ReadOnly");
162 }else{
163 if(this.active){
164 multiply("Active");
165 }else if(this.hovering){
166 multiply("Hover");
167 }
168 }
169
170 if(this.focused){
171 multiply("Focused");
172 }
173
174 // Remove old state classes and add new ones.
175 // For performance concerns we only write into domNode.className once.
176 var tn = this.stateNode || this.domNode,
177 classHash = {}; // set of all classes (state and otherwise) for node
178
179 array.forEach(tn.className.split(" "), function(c){ classHash[c] = true; });
180
181 if("_stateClasses" in this){
182 array.forEach(this._stateClasses, function(c){ delete classHash[c]; });
183 }
184
185 array.forEach(newStateClasses, function(c){ classHash[c] = true; });
186
187 var newClasses = [];
188 for(var c in classHash){
189 newClasses.push(c);
190 }
191 tn.className = newClasses.join(" ");
192
193 this._stateClasses = newStateClasses;
194 },
195
196 _subnodeCssMouseEvent: function(node, clazz, evt){
197 // summary:
198 // Handler for hover/active mouse event on widget's subnode
199 if(this.disabled || this.readOnly){
200 return;
201 }
202 function hover(isHovering){
203 domClass.toggle(node, clazz+"Hover", isHovering);
204 }
205 function active(isActive){
206 domClass.toggle(node, clazz+"Active", isActive);
207 }
208 function focused(isFocused){
209 domClass.toggle(node, clazz+"Focused", isFocused);
210 }
211 switch(evt.type){
212 case "mouseover":
213 hover(true);
214 break;
215 case "mouseout":
216 hover(false);
217 active(false);
218 break;
219 case "mousedown":
220 case "touchstart":
221 active(true);
222 break;
223 case "mouseup":
224 case "touchend":
225 active(false);
226 break;
227 case "focus":
228 case "focusin":
229 focused(true);
230 break;
231 case "blur":
232 case "focusout":
233 focused(false);
234 break;
235 }
236 },
237
238 _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){
239 // summary:
240 // Track mouse/focus events on specified node and set CSS class on that node to indicate
241 // current state. Usually not called directly, but via cssStateNodes attribute.
242 // description:
243 // Given class=foo, will set the following CSS class on the node
244 //
245 // - fooActive: if the user is currently pressing down the mouse button while over the node
246 // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button
247 // - fooFocus: if the node is focused
248 //
249 // Note that it won't set any classes if the widget is disabled.
250 // node: DomNode
251 // Should be a sub-node of the widget, not the top node (this.domNode), since the top node
252 // is handled specially and automatically just by mixing in this class.
253 // clazz: String
254 // CSS class name (ex: dijitSliderUpArrow)
255
256 // Flag for listener code below to call this._cssMouseEvent() or this._subnodeCssMouseEvent()
257 // when node is hovered/active
258 node._cssState = clazz;
259 }
260});
261
262ready(function(){
263 // Document level listener to catch hover etc. events on widget root nodes and subnodes.
264 // Note that when the mouse is moved quickly, a single onmouseenter event could signal that multiple widgets
265 // have been hovered or unhovered (try test_Accordion.html)
266 function handler(evt){
267 // Poor man's event propagation. Don't propagate event to ancestors of evt.relatedTarget,
268 // to avoid processing mouseout events moving from a widget's domNode to a descendant node;
269 // such events shouldn't be interpreted as a mouseleave on the widget.
270 if(!dom.isDescendant(evt.relatedTarget, evt.target)){
271 for(var node = evt.target; node && node != evt.relatedTarget; node = node.parentNode){
272 // Process any nodes with _cssState property. They are generally widget root nodes,
273 // but could also be sub-nodes within a widget
274 if(node._cssState){
275 var widget = registry.getEnclosingWidget(node);
276 if(widget){
277 if(node == widget.domNode){
278 // event on the widget's root node
279 widget._cssMouseEvent(evt);
280 }else{
281 // event on widget's sub-node
282 widget._subnodeCssMouseEvent(node, node._cssState, evt);
283 }
284 }
285 }
286 }
287 }
288 }
289 function ieHandler(evt){
290 evt.target = evt.srcElement;
291 handler(evt);
292 }
293
294 // Use addEventListener() (and attachEvent() on IE) to catch the relevant events even if other handlers
295 // (on individual nodes) call evt.stopPropagation() or event.stopEvent().
296 // Currently typematic.js is doing that, not sure why.
297 // Don't monitor mouseover/mouseout on mobile because iOS generates "phantom" mouseover/mouseout events when
298 // drag-scrolling, at the point in the viewport where the drag originated. Test the Tree in api viewer.
299 var body = win.body(),
300 types = (has("touch") ? [] : ["mouseover", "mouseout"]).concat(["mousedown", "touchstart", "mouseup", "touchend"]);
301 array.forEach(types, function(type){
302 if(body.addEventListener){
303 body.addEventListener(type, handler, true); // W3C
304 }else{
305 body.attachEvent("on"+type, ieHandler); // IE
306 }
307 });
308
309 // Track focus events on widget sub-nodes that have been registered via _trackMouseState().
310 // However, don't track focus events on the widget root nodes, because focus is tracked via the
311 // focus manager (and it's not really tracking focus, but rather tracking that focus is on one of the widget's
312 // nodes or a subwidget's node or a popup node, etc.)
313 // Remove for 2.0 (if focus CSS needed, just use :focus pseudo-selector).
314 on(body, "focusin, focusout", function(evt){
315 var node = evt.target;
316 if(node._cssState && !node.getAttribute("widgetId")){
317 var widget = registry.getEnclosingWidget(node);
318 widget._subnodeCssMouseEvent(node, node._cssState, evt);
319 }
320 });
321});
322
323return CssStateMixin;
324});