]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/_CssStateMixin.js.uncompressed.js
1 define("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
7 "dojo/_base/lang", // lang.hitch
10 "dojo/_base/window", // win.body
12 ], function(array
, declare
, dom
, domClass
, has
, lang
, on
, ready
, win
, registry
){
15 // dijit/_CssStateMixin
17 var CssStateMixin
= declare("dijit._CssStateMixin", [], {
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.
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.
28 // It also sets CSS like dijitButtonDisabled based on widget semantic state.
30 // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons
31 // within the widget).
33 // cssStateNodes: [protected] Object
34 // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus
36 // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names
37 // (like "dijitUpArrowButton"). Example:
39 // | "upArrowButton": "dijitUpArrowButton",
40 // | "downArrowButton": "dijitDownArrowButton"
42 // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it
46 // hovering: [readonly] Boolean
47 // True if cursor is over this widget
50 // active: [readonly] Boolean
51 // True if mouse was pressed while over this widget, and hasn't been released yet
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.
59 this.inherited(arguments
);
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"));
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
]);
70 this._trackMouseState(this.domNode
, this.baseClass
);
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();
77 _cssMouseEvent: function(/*Event*/ event
){
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.
85 this._set("hovering", true);
86 this._set("active", this._mouseDown
);
89 this._set("hovering", false);
90 this._set("active", false);
94 this._set("active", true);
98 this._set("active", false);
104 _setStateClass: function(){
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).
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".
116 // The widget may have one or more of the following states, determined
117 // by this.state, this.checked, this.valid, and this.selected:
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
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):
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
132 // Compute new set of classes
133 var newStateClasses
= this.baseClass
.split(" ");
135 function multiply(modifier
){
136 newStateClasses
= newStateClasses
.concat(array
.map(newStateClasses
, function(c
){ return c
+modifier
; }), "dijit"+modifier
);
139 if(!this.isLeftToRight()){
140 // For RTL mode we need to set an addition class like dijitTextBoxRtl.
144 var checkedState
= this.checked
== "mixed" ? "Mixed" : (this.checked
? "Checked" : "");
146 multiply(checkedState
);
149 multiply(this.state
);
152 multiply("Selected");
159 multiply("Disabled");
160 }else if(this.readOnly
){
161 multiply("ReadOnly");
165 }else if(this.hovering
){
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
179 array
.forEach(tn
.className
.split(" "), function(c
){ classHash
[c
] = true; });
181 if("_stateClasses" in this){
182 array
.forEach(this._stateClasses
, function(c
){ delete classHash
[c
]; });
185 array
.forEach(newStateClasses
, function(c
){ classHash
[c
] = true; });
188 for(var c
in classHash
){
191 tn
.className
= newClasses
.join(" ");
193 this._stateClasses
= newStateClasses
;
196 _subnodeCssMouseEvent: function(node
, clazz
, evt
){
198 // Handler for hover/active mouse event on widget's subnode
199 if(this.disabled
|| this.readOnly
){
202 function hover(isHovering
){
203 domClass
.toggle(node
, clazz
+"Hover", isHovering
);
205 function active(isActive
){
206 domClass
.toggle(node
, clazz
+"Active", isActive
);
208 function focused(isFocused
){
209 domClass
.toggle(node
, clazz
+"Focused", isFocused
);
238 _trackMouseState: function(/*DomNode*/ node
, /*String*/ clazz
){
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.
243 // Given class=foo, will set the following CSS class on the node
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
249 // Note that it won't set any classes if the widget is disabled.
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.
254 // CSS class name (ex: dijitSliderUpArrow)
256 // Flag for listener code below to call this._cssMouseEvent() or this._subnodeCssMouseEvent()
257 // when node is hovered/active
258 node
._cssState
= clazz
;
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
275 var widget
= registry
.getEnclosingWidget(node
);
277 if(node
== widget
.domNode
){
278 // event on the widget's root node
279 widget
._cssMouseEvent(evt
);
281 // event on widget's sub-node
282 widget
._subnodeCssMouseEvent(node
, node
._cssState
, evt
);
289 function ieHandler(evt
){
290 evt
.target
= evt
.srcElement
;
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
305 body
.attachEvent("on"+type
, ieHandler
); // IE
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
);
323 return CssStateMixin
;