/**
* The ButtonItem Utility
*
* @module aui-button-item
*/
var Lang = A.Lang,
isString = Lang.isString,
getClassName = A.getClassName,
NAME = 'buttonitem',
BOUNDING_BOX = 'boundingBox',
BUTTON = 'button',
CONTENT_BOX = 'contentBox',
DOT = '.',
HANDLER = 'handler',
ICON = 'icon',
ICON_NODE = 'iconNode',
LABEL = 'label',
LABEL_NODE = 'labelNode',
ONLY = 'only',
RESET = 'reset',
STATE = 'state',
SUBMIT = 'submit',
TITLE = 'title',
TYPE = 'type',
CSS_BUTTON = getClassName(NAME),
CSS_BUTTON_ICON = getClassName(NAME, ICON),
CSS_BUTTON_LABEL = getClassName(NAME, LABEL),
CSS_BUTTON_ICON_LABEL = getClassName(NAME, ICON, LABEL),
CSS_BUTTON_ICON_ONLY = getClassName(NAME, ICON, ONLY),
CSS_BUTTON_LABEL_ONLY = getClassName(NAME, LABEL, ONLY),
CSS_ICON = getClassName(ICON),
REGEX_ICON = new RegExp(CSS_ICON + '-([a-zA-Z0-9-]+)'),
TPL_BUTTON = '<button type="{0}"></button>',
TPL_ICON = '<span class="' + [CSS_BUTTON_ICON, CSS_ICON].join(' ') + '"></span>',
TPL_LABEL = '<span class="' + CSS_BUTTON_LABEL + '"></span>';
/**
* A base class for ButtonItem, providing:
* <ul>
* <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
* <li>An optional icon or label</li>
* <li>Managed user interaction states (default, active, hover)</li>
* <li>Keyboard accessible</li>
* </ul>
*
* Quick Example:<br/>
*
* <pre><code>var instance = new A.ButtonItem({
* icon: 'gear',
* label: 'Configuration'
* }).render();
* </code></pre>
*
* Check the list of <a href="ButtonItem.html#configattributes">Configuration Attributes</a> available for
* ButtonItem.
*
* @param config {Object} Object literal specifying widget configuration properties.
*
* @class ButtonItem
* @constructor
* @extends Component
* @uses WidgetChild
*/
var ButtonItem = A.Component.create({
/**
* Static property provides a string to identify the class.
*
* @property ButtonItem.NAME
* @type String
* @static
*/
NAME: NAME,
/**
* Static property provides an array to specify which classes augment this one.
*
* @property ButtonItem.AUGMENTS
* @type array
* @static
*/
AUGMENTS: [A.WidgetChild],
/**
* Static property used to define the default attribute
* configuration for the ButtonItem.
*
* @property ButtonItem.ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* Whether to track the active state of the button.
*
* @attribute activeState
* @default false
* @type Boolean
*/
activeState: {
value: false
},
/**
* An object map of the CSS class names to use for the different interaction states.
*
* @attribute classNames
* @type Object
*/
classNames: {},
/**
* Whether to apply the default interaction state to the button
*
* @attribute defaultState
* @default true
* @type Boolean
*/
defaultState: {},
/**
* An event callback to handle when a user interacts with the button.
* This can either be a function that will be attached on click, or
* an object map that accepts the following keys:
* <code>{fn: // The function to execute
* context: // The context to execute the function in
* type: // The type of event to listen for (defaults to "click")
* }</code>
*
* @attribute handler
* @default false
* @type Function | Object
*/
handler: {
lazyAdd: false,
value: null
},
/**
* Whether to track the hover interaction state of the button.
*
* @attribute hoverState
* @default true
* @type Boolean
*/
hoverState: {},
/**
* The icon to use inside of the button. Possible values are:
*
* @attribute icon
* @type String
*/
icon: {
value: ''
},
/**
* DOM Node to display the icon of the ButtonItem. If not
* specified try to query using HTML_PARSER an element inside
* boundingBox which matches <code>aui-button-icon</code>.
*
* @attribute iconNode
* @default Generated div element.
* @type String
*/
iconNode: {
valueFn: function() {
return A.Node.create(TPL_ICON);
}
},
/**
* An id that can be used to identify a button.
*
* @attribute hoverState
* @type Boolean
*/
id: {
valueFn: function() {
return A.guid();
}
},
/**
* Text to use inside of the button.
*
* @attribute label
* @type String
*/
label: {
value: ''
},
/**
* DOM Node to display the text of the ButtonItem. If not
* specified try to query using HTML_PARSER an element inside
* boundingBox which matches <code>aui-button-label</code>.
*
* @attribute labelNode
* @default Generated div element.
* @type String
*/
labelNode: {
valueFn: function() {
return A.Node.create(TPL_LABEL);
}
},
/**
* Text to use as the title attribute of the button.
*
* @attribute title
* @type String
*/
title: {
setter: '_setTitle',
value: false
},
/**
* Button type.
*
* @attribute type
* @default button
* @type String
*/
type: {
validator: function(val) {
return (val === BUTTON || val === SUBMIT || val === RESET);
},
value: BUTTON
}
},
/**
* Object hash, defining how attribute values are to be parsed from
* markup contained in the widget's content box.
*
* @property ButtonItem.HTML_PARSER
* @type Object
* @static
*/
HTML_PARSER: {
iconNode: function(srcNode) {
return srcNode.one(DOT + CSS_BUTTON_ICON);
},
labelNode: function(srcNode) {
return srcNode.one(DOT + CSS_BUTTON_LABEL);
},
icon: function(srcNode) {
var iconNode = srcNode.one(DOT + CSS_BUTTON_ICON);
if (iconNode) {
this.set(ICON_NODE, iconNode);
var cssClass = iconNode.attr('class');
var match = cssClass.match(REGEX_ICON);
return match && match[1];
}
},
label: function(srcNode) {
var labelNode = srcNode.one(DOT + CSS_BUTTON_LABEL);
if (labelNode) {
this.set(LABEL_NODE, labelNode);
return labelNode.text();
}
}
},
constructor: function(config) {
var instance = this;
var buttonType = 'button';
if (config) {
if (isString(config)) {
config = {
icon: config
};
}
else if (config.type) {
buttonType = config.type;
}
}
instance.BOUNDING_TEMPLATE = Lang.sub(TPL_BUTTON, [buttonType]);
ButtonItem.superclass.constructor.call(instance, config);
},
UI_ATTRS: [HANDLER, ICON, LABEL, TITLE, TYPE],
prototype: {
BOUNDING_TEMPLATE: TPL_BUTTON,
CONTENT_TEMPLATE: null,
/**
* Create the DOM structure for the ButtonItem. Lifecycle.
*
* @method renderUI
* @protected
*/
renderUI: function() {
var instance = this;
instance._renderStates();
instance._renderIconNode();
instance._renderLabelNode();
},
/**
* Sync the ButtonItem UI. Lifecycle.
*
* @method syncUI
* @protected
*/
syncUI: function() {
var instance = this;
var icon = instance.get('icon');
var label = instance.get('label');
var title = instance.get('title');
if (icon) {
instance._uiSetIcon(icon);
}
if (label) {
instance._uiSetLabel(label);
}
if (title) {
instance._uiSetTitle(title);
}
},
/**
* Renders the underlying markup for the <a href="ButtonItem.html#config_icon">icon</a>.
*
* @method _renderIconNode
* @protected
* @return {Node}
*/
_renderIconNode: function() {
var instance = this;
instance.get(CONTENT_BOX).append(
instance.get(ICON_NODE)
);
},
/**
* Renders the underlying markup for the <a href="ButtonItem.html#config_label">label</a>.
*
* @method _renderLabelNode
* @protected
* @return {Node}
*/
_renderLabelNode: function() {
var instance = this;
instance.get(CONTENT_BOX).append(
instance.get(LABEL_NODE)
);
},
/**
* Retrieves the state value from either the current instance, or if defined, the
* parent widget.
*
* @method _getState
* @param {String} key The state name to retrieve
* @param {Object} parent The parent widget to attempt to retrieve the state from
* @protected
* @return {Object}
*/
_getState: function(key, parent) {
var instance = this;
var value = instance.get(key);
var state = value;
if (parent) {
value = parent.get(key);
if (!Lang.isUndefined(value)) {
state = value;
}
}
return state;
},
/**
* Attaches state interaction management to the widget.
*
* @method _renderStates
* @param {EventFacade} event
* @protected
*/
_renderStates: function(event) {
var instance = this;
var parent = instance.get('parent');
var activeState = instance._getState('activeState', parent);
var classNames = instance._getState('classNames', parent);
var defaultState = instance._getState('defaultState', parent);
var hoverState = instance._getState('hoverState', parent);
instance.plug(
A.Plugin.StateInteraction, {
activeState: activeState,
classNames: classNames,
defaultState: defaultState,
hoverState: hoverState
}
);
},
/**
* Setter for the title attribute
*
* @method _setTitle
* @protected
*/
_setTitle: function(value) {
var instance = this;
if (value === null) {
value = instance.get('label');
}
else if (value === false) {
value = '';
}
return String(value);
},
/**
* Syncs the boundingBox class names to reflect whether the children only have icons or labels or both.
*
* @method _syncChildrenStates
* @protected
*/
_syncChildrenStates: function() {
var instance = this;
var icon = instance.get('icon');
var label = instance.get('label');
var hasIconAndLabel = (icon && label);
var hasLabelOnly = (!icon && label);
var hasIconOnly = (icon && !label);
var boundingBox = instance.get(BOUNDING_BOX);
boundingBox.toggleClass(CSS_BUTTON_ICON_LABEL, hasIconAndLabel);
boundingBox.toggleClass(CSS_BUTTON_ICON_ONLY, hasIconOnly);
boundingBox.toggleClass(CSS_BUTTON_LABEL_ONLY, hasLabelOnly);
},
_uiSetDisabled: function(val) {
var instance = this,
boundingBox = instance.get(BOUNDING_BOX);
if (val) {
boundingBox.attr('disabled', 'disabled');
}
else {
boundingBox.removeAttribute('disabled');
}
A.ButtonItem.superclass._uiSetDisabled.apply(instance, arguments);
},
/**
* Updates the UI for the icon in response to the <a href="ButtonItem.html#event_HandlerChange">Handler</a> event.
*
* @method _uiSetHandler
* @param {String} val Handler name
* @protected
*/
_uiSetHandler: function(value) {
var instance = this;
var fn = value;
var parent = instance.get('parent');
var context = (parent && parent._DEFAULT_CONTEXT) || instance._DEFAULT_CONTEXT || instance;
var type = 'click';
var args = instance;
var customArgs;
if (Lang.isObject(fn)) {
var handlerConfig = fn;
fn = handlerConfig.fn || fn;
context = handlerConfig.context || context;
type = handlerConfig.type || type;
customArgs = handlerConfig.args;
}
if (Lang.isFunction(fn)) {
var interactionHandle = instance._interactionHandle;
if (interactionHandle) {
interactionHandle.detach();
}
var boundFn = A.rbind.apply(A, [fn, context, args].concat(customArgs || []));
instance._interactionHandle = instance.on(
type,
function() {
if (!instance.get('disabled')) {
boundFn.apply(this, arguments);
}
}
);
}
},
/**
* Updates the UI for the icon in response to the <a href="ButtonItem.html#event_iconChange">iconChange</a> event.
*
* @method _uiSetIcon
* @param {String} val Icon name
* @protected
*/
_uiSetIcon: function(val) {
var instance = this;
var iconNode = instance.get(ICON_NODE);
var action = 'show';
if (!val) {
action = 'hide';
}
val = getClassName(ICON, val);
iconNode.replaceClass(instance._iconPrevVal, val);
iconNode[action]();
instance._syncChildrenStates();
instance._iconPrevVal = val;
},
/**
* Updates the UI for the label in response to the <a href="ButtonItem.html#event_labelChange">labelChange</a> event.
*
* @method _uiSetLabel
* @param {String} val The new value
* @protected
*/
_uiSetLabel: function(val) {
var instance = this;
var labelNode = instance.get(LABEL_NODE);
var action = 'show';
if (!val) {
action = 'hide';
}
labelNode.text(val);
labelNode[action]();
instance._syncChildrenStates();
},
/**
* Updates the UI for the title in response to the <a href="ButtonItem.html#event_titleChange">titleChange</a> event.
*
* @method _uiSetTitle
* @param {String} val The new value
* @protected
*/
_uiSetTitle: function(val) {
var instance = this;
var boundingBox = instance.get(BOUNDING_BOX);
boundingBox.setAttribute(TITLE, val);
},
/**
* Updates the UI for the type in response to the <a href="ButtonItem.html#event_typeChange">typeChange</a> event.
*
* @method _uiSetType
* @param {String} val The new value
* @protected
*/
_uiSetType: function(val) {
var instance = this;
var boundingBox = instance.get(BOUNDING_BOX);
boundingBox.setAttribute(TYPE, val);
}
}
});
A.ButtonItem = ButtonItem;