_CssStateMixin.js 9.5 KB

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