diff options
Diffstat (limited to 'lib/dijit/_CssStateMixin.js.uncompressed.js')
-rw-r--r-- | lib/dijit/_CssStateMixin.js.uncompressed.js | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/lib/dijit/_CssStateMixin.js.uncompressed.js b/lib/dijit/_CssStateMixin.js.uncompressed.js new file mode 100644 index 000000000..6453c3603 --- /dev/null +++ b/lib/dijit/_CssStateMixin.js.uncompressed.js @@ -0,0 +1,324 @@ +define("dijit/_CssStateMixin", [ + "dojo/_base/array", // array.forEach array.map + "dojo/_base/declare", // declare + "dojo/dom", // dom.isDescendant() + "dojo/dom-class", // domClass.toggle + "dojo/has", + "dojo/_base/lang", // lang.hitch + "dojo/on", + "dojo/ready", + "dojo/_base/window", // win.body + "./registry" +], function(array, declare, dom, domClass, has, lang, on, ready, win, registry){ + +// module: +// dijit/_CssStateMixin + +var CssStateMixin = declare("dijit._CssStateMixin", [], { + // summary: + // Mixin for widgets to set CSS classes on the widget DOM nodes depending on hover/mouse press/focus + // state changes, and also higher-level state changes such becoming disabled or selected. + // + // description: + // By mixing this class into your widget, and setting the this.baseClass attribute, it will automatically + // maintain CSS classes on the widget root node (this.domNode) depending on hover, + // active, focus, etc. state. Ex: with a baseClass of dijitButton, it will apply the classes + // dijitButtonHovered and dijitButtonActive, as the user moves the mouse over the widget and clicks it. + // + // It also sets CSS like dijitButtonDisabled based on widget semantic state. + // + // By setting the cssStateNodes attribute, a widget can also track events on subnodes (like buttons + // within the widget). + + // cssStateNodes: [protected] Object + // List of sub-nodes within the widget that need CSS classes applied on mouse hover/press and focus + // + // Each entry in the hash is a an attachpoint names (like "upArrowButton") mapped to a CSS class names + // (like "dijitUpArrowButton"). Example: + // | { + // | "upArrowButton": "dijitUpArrowButton", + // | "downArrowButton": "dijitDownArrowButton" + // | } + // The above will set the CSS class dijitUpArrowButton to the this.upArrowButton DOMNode when it + // is hovered, etc. + cssStateNodes: {}, + + // hovering: [readonly] Boolean + // True if cursor is over this widget + hovering: false, + + // active: [readonly] Boolean + // True if mouse was pressed while over this widget, and hasn't been released yet + active: false, + + _applyAttributes: function(){ + // This code would typically be in postCreate(), but putting in _applyAttributes() for + // performance: so the class changes happen before DOM is inserted into the document. + // Change back to postCreate() in 2.0. See #11635. + + this.inherited(arguments); + + // Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node + array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active", "_opened"], function(attr){ + this.watch(attr, lang.hitch(this, "_setStateClass")); + }, this); + + // Track hover and active mouse events on widget root node, plus possibly on subnodes + for(var ap in this.cssStateNodes){ + this._trackMouseState(this[ap], this.cssStateNodes[ap]); + } + this._trackMouseState(this.domNode, this.baseClass); + + // Set state initially; there's probably no hover/active/focus state but widget might be + // disabled/readonly/checked/selected so we want to set CSS classes for those conditions. + this._setStateClass(); + }, + + _cssMouseEvent: function(/*Event*/ event){ + // summary: + // Handler for CSS event on this.domNode. Sets hovering and active properties depending on mouse state, + // which triggers _setStateClass() to set appropriate CSS classes for this.domNode. + + if(!this.disabled){ + switch(event.type){ + case "mouseover": + this._set("hovering", true); + this._set("active", this._mouseDown); + break; + case "mouseout": + this._set("hovering", false); + this._set("active", false); + break; + case "mousedown": + case "touchstart": + this._set("active", true); + break; + case "mouseup": + case "touchend": + this._set("active", false); + break; + } + } + }, + + _setStateClass: function(){ + // summary: + // Update the visual state of the widget by setting the css classes on this.domNode + // (or this.stateNode if defined) by combining this.baseClass with + // various suffixes that represent the current widget state(s). + // + // description: + // In the case where a widget has multiple + // states, it sets the class based on all possible + // combinations. For example, an invalid form widget that is being hovered + // will be "dijitInput dijitInputInvalid dijitInputHover dijitInputInvalidHover". + // + // The widget may have one or more of the following states, determined + // by this.state, this.checked, this.valid, and this.selected: + // + // - Error - ValidationTextBox sets this.state to "Error" if the current input value is invalid + // - Incomplete - ValidationTextBox sets this.state to "Incomplete" if the current input value is not finished yet + // - Checked - ex: a checkmark or a ToggleButton in a checked state, will have this.checked==true + // - Selected - ex: currently selected tab will have this.selected==true + // + // In addition, it may have one or more of the following states, + // based on this.disabled and flags set in _onMouse (this.active, this.hovering) and from focus manager (this.focused): + // + // - Disabled - if the widget is disabled + // - Active - if the mouse (or space/enter key?) is being pressed down + // - Focused - if the widget has focus + // - Hover - if the mouse is over the widget + + // Compute new set of classes + var newStateClasses = this.baseClass.split(" "); + + function multiply(modifier){ + newStateClasses = newStateClasses.concat(array.map(newStateClasses, function(c){ return c+modifier; }), "dijit"+modifier); + } + + if(!this.isLeftToRight()){ + // For RTL mode we need to set an addition class like dijitTextBoxRtl. + multiply("Rtl"); + } + + var checkedState = this.checked == "mixed" ? "Mixed" : (this.checked ? "Checked" : ""); + if(this.checked){ + multiply(checkedState); + } + if(this.state){ + multiply(this.state); + } + if(this.selected){ + multiply("Selected"); + } + if(this._opened){ + multiply("Opened"); + } + + if(this.disabled){ + multiply("Disabled"); + }else if(this.readOnly){ + multiply("ReadOnly"); + }else{ + if(this.active){ + multiply("Active"); + }else if(this.hovering){ + multiply("Hover"); + } + } + + if(this.focused){ + multiply("Focused"); + } + + // Remove old state classes and add new ones. + // For performance concerns we only write into domNode.className once. + var tn = this.stateNode || this.domNode, + classHash = {}; // set of all classes (state and otherwise) for node + + array.forEach(tn.className.split(" "), function(c){ classHash[c] = true; }); + + if("_stateClasses" in this){ + array.forEach(this._stateClasses, function(c){ delete classHash[c]; }); + } + + array.forEach(newStateClasses, function(c){ classHash[c] = true; }); + + var newClasses = []; + for(var c in classHash){ + newClasses.push(c); + } + tn.className = newClasses.join(" "); + + this._stateClasses = newStateClasses; + }, + + _subnodeCssMouseEvent: function(node, clazz, evt){ + // summary: + // Handler for hover/active mouse event on widget's subnode + if(this.disabled || this.readOnly){ + return; + } + function hover(isHovering){ + domClass.toggle(node, clazz+"Hover", isHovering); + } + function active(isActive){ + domClass.toggle(node, clazz+"Active", isActive); + } + function focused(isFocused){ + domClass.toggle(node, clazz+"Focused", isFocused); + } + switch(evt.type){ + case "mouseover": + hover(true); + break; + case "mouseout": + hover(false); + active(false); + break; + case "mousedown": + case "touchstart": + active(true); + break; + case "mouseup": + case "touchend": + active(false); + break; + case "focus": + case "focusin": + focused(true); + break; + case "blur": + case "focusout": + focused(false); + break; + } + }, + + _trackMouseState: function(/*DomNode*/ node, /*String*/ clazz){ + // summary: + // Track mouse/focus events on specified node and set CSS class on that node to indicate + // current state. Usually not called directly, but via cssStateNodes attribute. + // description: + // Given class=foo, will set the following CSS class on the node + // + // - fooActive: if the user is currently pressing down the mouse button while over the node + // - fooHover: if the user is hovering the mouse over the node, but not pressing down a button + // - fooFocus: if the node is focused + // + // Note that it won't set any classes if the widget is disabled. + // node: DomNode + // Should be a sub-node of the widget, not the top node (this.domNode), since the top node + // is handled specially and automatically just by mixing in this class. + // clazz: String + // CSS class name (ex: dijitSliderUpArrow) + + // Flag for listener code below to call this._cssMouseEvent() or this._subnodeCssMouseEvent() + // when node is hovered/active + node._cssState = clazz; + } +}); + +ready(function(){ + // Document level listener to catch hover etc. events on widget root nodes and subnodes. + // Note that when the mouse is moved quickly, a single onmouseenter event could signal that multiple widgets + // have been hovered or unhovered (try test_Accordion.html) + function handler(evt){ + // Poor man's event propagation. Don't propagate event to ancestors of evt.relatedTarget, + // to avoid processing mouseout events moving from a widget's domNode to a descendant node; + // such events shouldn't be interpreted as a mouseleave on the widget. + if(!dom.isDescendant(evt.relatedTarget, evt.target)){ + for(var node = evt.target; node && node != evt.relatedTarget; node = node.parentNode){ + // Process any nodes with _cssState property. They are generally widget root nodes, + // but could also be sub-nodes within a widget + if(node._cssState){ + var widget = registry.getEnclosingWidget(node); + if(widget){ + if(node == widget.domNode){ + // event on the widget's root node + widget._cssMouseEvent(evt); + }else{ + // event on widget's sub-node + widget._subnodeCssMouseEvent(node, node._cssState, evt); + } + } + } + } + } + } + function ieHandler(evt){ + evt.target = evt.srcElement; + handler(evt); + } + + // Use addEventListener() (and attachEvent() on IE) to catch the relevant events even if other handlers + // (on individual nodes) call evt.stopPropagation() or event.stopEvent(). + // Currently typematic.js is doing that, not sure why. + // Don't monitor mouseover/mouseout on mobile because iOS generates "phantom" mouseover/mouseout events when + // drag-scrolling, at the point in the viewport where the drag originated. Test the Tree in api viewer. + var body = win.body(), + types = (has("touch") ? [] : ["mouseover", "mouseout"]).concat(["mousedown", "touchstart", "mouseup", "touchend"]); + array.forEach(types, function(type){ + if(body.addEventListener){ + body.addEventListener(type, handler, true); // W3C + }else{ + body.attachEvent("on"+type, ieHandler); // IE + } + }); + + // Track focus events on widget sub-nodes that have been registered via _trackMouseState(). + // However, don't track focus events on the widget root nodes, because focus is tracked via the + // focus manager (and it's not really tracking focus, but rather tracking that focus is on one of the widget's + // nodes or a subwidget's node or a popup node, etc.) + // Remove for 2.0 (if focus CSS needed, just use :focus pseudo-selector). + on(body, "focusin, focusout", function(evt){ + var node = evt.target; + if(node._cssState && !node.getAttribute("widgetId")){ + var widget = registry.getEnclosingWidget(node); + widget._subnodeCssMouseEvent(node, node._cssState, evt); + } + }); +}); + +return CssStateMixin; +}); |