tt-rss/lib/dijit/_CssStateMixin.js.uncompressed.js
2012-08-14 18:59:18 +04:00

268 lines
9.5 KiB
JavaScript

define("dijit/_CssStateMixin", [
"dojo/touch",
"dojo/_base/array", // array.forEach array.map
"dojo/_base/declare", // declare
"dojo/dom-class", // domClass.toggle
"dojo/_base/lang", // lang.hitch
"dojo/_base/window" // win.body
], function(touch, array, declare, domClass, lang, win){
// module:
// 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.
return 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);
// Automatically monitor mouse events (essentially :hover and :active) on this.domNode
array.forEach(["onmouseenter", "onmouseleave", touch.press], function(e){
this.connect(this.domNode, e, "_cssMouseEvent");
}, this);
// Monitoring changes to disabled, readonly, etc. state, and update CSS class of root node
array.forEach(["disabled", "readOnly", "checked", "selected", "focused", "state", "hovering", "active"], function(attr){
this.watch(attr, lang.hitch(this, "_setStateClass"));
}, this);
// Events on sub nodes within the widget
for(var ap in this.cssStateNodes){
this._trackMouseState(this[ap], this.cssStateNodes[ap]);
}
// 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:
// 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 "mouseenter":
case "mouseover": // generated on non-IE browsers even though we connected to mouseenter
this._set("hovering", true);
this._set("active", this._mouseDown);
break;
case "mouseleave":
case "mouseout": // generated on non-IE browsers even though we connected to mouseleave
this._set("hovering", false);
this._set("active", false);
break;
case "mousedown":
case "touchpress":
this._set("active", true);
this._mouseDown = true;
// Set a global event to handle mouseup, so it fires properly
// even if the cursor leaves this.domNode before the mouse up event.
// Alternately could set active=false on mouseout.
var mouseUpConnector = this.connect(win.body(), touch.release, function(){
this._mouseDown = false;
this._set("active", false);
this.disconnect(mouseUpConnector);
});
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.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;
},
_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).
// Current state of node (initially false)
// NB: setting specifically to false because domClass.toggle() needs true boolean as third arg
var hovering=false, active=false, focused=false;
var self = this,
cn = lang.hitch(this, "connect", node);
function setClass(){
var disabled = ("disabled" in self && self.disabled) || ("readonly" in self && self.readonly);
domClass.toggle(node, clazz+"Hover", hovering && !active && !disabled);
domClass.toggle(node, clazz+"Active", active && !disabled);
domClass.toggle(node, clazz+"Focused", focused && !disabled);
}
// Mouse
cn("onmouseenter", function(){
hovering = true;
setClass();
});
cn("onmouseleave", function(){
hovering = false;
active = false;
setClass();
});
cn(touch.press, function(){
active = true;
setClass();
});
cn(touch.release, function(){
active = false;
setClass();
});
// Focus
cn("onfocus", function(){
focused = true;
setClass();
});
cn("onblur", function(){
focused = false;
setClass();
});
// Just in case widget is enabled/disabled while it has focus/hover/active state.
// Maybe this is overkill.
this.watch("disabled", setClass);
this.watch("readOnly", setClass);
}
});
});