_CssStateMixin.js 9.3 KB

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