/**
* The Palette Utility
*
* @module aui-palette
*/
var Lang = A.Lang,
UI_SRC = A.Widget.UI_SRC,
getClassName = A.getClassName,
CSS_PALETTE_CONTAINER = getClassName('palette-container'),
CSS_PALETTE_ITEM = getClassName('palette-item'),
CSS_PALETTE_ITEM_HOVER = getClassName('palette-item-hover'),
CSS_PALETTE_ITEM_INNER = getClassName('palette-item-inner'),
CSS_PALETTE_ITEM_SELECTED = getClassName('palette-item-selected'),
CSS_PALETTE_ITEMS_CONTAINER = getClassName('palette-items-container'),
CSS_PALETTE_ITEMS_CONTAINER_INDEX = getClassName('palette-items-container-{index}'),
/**
* A base class for Palette.
*
* @class A.Palette
* @extends Widget
* @param {Object} config Object literal specifying widget configuration
* properties.
* @constructor
*/
Palette = A.Base.create('palette', A.Widget, [A.WidgetCssClass, A.WidgetToggle], {
CONTAINER_TEMPLATE: '<div class="' + CSS_PALETTE_CONTAINER + '">{content}</div>',
ITEMS_CONTAINER_TEMPLATE: '<ul class="' + CSS_PALETTE_ITEMS_CONTAINER + ' ' +
CSS_PALETTE_ITEMS_CONTAINER_INDEX + '">{content}</ul>',
ITEM_TEMPLATE: '<li class="' + CSS_PALETTE_ITEM +
' {selectedClassName}" data-column={column} data-index={index} data-row={row} data-value="{value}">' +
'<a href="" class="' + CSS_PALETTE_ITEM_INNER + '" onclick="return false;"></a>' + '</li>',
_items: null,
/**
* Construction logic executed during Palette instantiation. Lifecycle.
*
* @method initializer
* @protected
*/
initializer: function() {
var instance = this;
instance.after('itemsChange', instance._afterItemsChange, instance);
instance.after('selectedChange', instance._afterSelectedChange, instance);
A.after(instance._bindUIPalette, instance, 'renderUI');
instance.publish({
enter: {
defaultFn: instance._defEnterFn
},
leave: {
defaultFn: instance._defLeaveFn
},
select: {
defaultFn: instance._defSelectFn
},
unselect: {
defaultFn: instance._defUnselectFn
}
});
},
/**
* Render the Palette component instance. Lifecycle.
*
* @method renderUI
* @protected
*/
renderUI: function() {
var instance = this;
instance._uiSetItems(instance.get('items'));
},
/**
* Returns an item in the Palette by row and column.
*
* @method getItem
* @param {Number} row The row in which this item is rendered
* @param {Number} col The column on which this item is rendered
* @return {Object} The found palette item
*/
getItem: function(row, col) {
var instance = this;
return instance.getItemByIndex(
row * instance.get('columns') + col);
},
/**
* Returns an item in the Palette by its index.
*
* @method getItemByIndex
* @param {Number} index The index of the item
* @return {Object} The palette item
*/
getItemByIndex: function(index) {
var instance = this;
return instance._getIndexedItems().item(index);
},
/**
* Returns an item in the Palette by its value.
*
* @method getItemByValue
* @param {Object|Number|String} value If the value is an object, it
* should contain a property called "value" which will be used to
* retrieve the item.
* @return {Object} The palette item
*/
getItemByValue: function(value) {
var instance = this,
itemIndex = -1,
items;
if (Lang.isObject(value)) {
value = value.value;
}
items = instance.get('items');
A.Array.some(
items,
function(item, index) {
if (Lang.isObject(item)) {
item = item.value;
}
if (item === value) {
itemIndex = index;
return true;
}
}
);
return instance.getItemByIndex(itemIndex);
},
/**
* Selects an item in the Palette.
*
* @method select
* @param {Number|Object} valueOrIndex The value or index of the item
* which should be selected
*/
select: function(valueOrIndex) {
var instance = this;
instance.toggleSelection(valueOrIndex, true);
},
/**
* Toggles the section of an item. The item must be specified by its
* value or index. A second param indicates if the selection should be
* forced.
*
* @method toggleSelection
* @param {Number|Object} valueOrIndex The value or index of the item
* @param force If true, forces the selection.
*/
toggleSelection: function(valueOrIndex, force) {
var instance = this,
item = instance.getItemByIndex(valueOrIndex) ||
instance.getItemByValue(valueOrIndex);
if (item) {
item.toggleClass(CSS_PALETTE_ITEM_SELECTED, force);
}
},
/**
* Unselects an item. The item must be specified by its value or index.
*
* @method unselect
* @param {Number|Object} valueOrIndex The value or index of the item
*/
unselect: function(valueOrIndex) {
var instance = this;
instance.toggleSelection(valueOrIndex, false);
},
/**
* Updates the UI of the Palette after changing an item.
*
* @method _afterItemsChange
* @param {CustomEvent} event The event fired
* @protected
*/
_afterItemsChange: function(event) {
var instance = this;
instance._items = null;
instance._uiSetItems(event.newVal);
},
/**
* Unselects the previous item and selects a new one.
*
* @method _afterSelectedChange
* @param {CustomEvent} event The event fired. Contains the previous
* selected element as `prevVal` property and the new selected item
* as `newVal`
* @protected
*/
_afterSelectedChange: function(event) {
var instance = this;
instance.unselect(event.prevVal);
instance.select(event.newVal);
},
/**
* Binds events on the Palette.
*
* @method _bindUIPalette
* @protected
*/
_bindUIPalette: function() {
var instance = this,
boundingBox = instance.get('boundingBox');
boundingBox.delegate('click', instance._onItemClick, '.' + CSS_PALETTE_ITEM, instance);
boundingBox.delegate('hover', instance._onItemMouseEnter, instance._onItemMouseLeave, '.' +
CSS_PALETTE_ITEM, instance);
},
/**
* The default function to be executed after `enter` event.
* By default adds HOVER class to the item.
*
* @method _defEnterFn
* @param {CustomEvent} event The fired event
* @protected
*/
_defEnterFn: function(event) {
event.item.addClass(CSS_PALETTE_ITEM_HOVER);
},
/**
* The default function to be executed after `leave` event.
* By default removes HOVER class from the item.
*
* @method _defLeaveFn
* @param {CustomEvent} event The fired event
* @protected
*/
_defLeaveFn: function(event) {
event.item.removeClass(CSS_PALETTE_ITEM_HOVER);
},
/**
* The default function which executes after `select` event. By default
* updates the `selected` property with the index of the selected item.
*
* @method _defSelectFn
* @param {CustomEvent} event The fired event
* @protected
*/
_defSelectFn: function(event) {
var instance = this;
if (event.src === UI_SRC) {
instance.set('selected', event.index);
}
},
/**
* The default function which executes after `unselect` event.
* By default updates the `selected` property with -1.
*
* @method _defUnselectFn
* @param event
* @protected
*/
_defUnselectFn: function(event) {
var instance = this;
if (event.src === UI_SRC) {
instance.set('selected', -1);
}
},
/**
* Generates and retrieves the whole content of the Palette. The
* function uses the Formatter from `formatter` property to format the
* content.
*
* @method _getContent
* @param {Array} items The items in the Palette
* @param {Array} columns The columns of the Palette
* @protected
* @return {String} The generated content
*/
_getContent: function(items, columns) {
var instance = this,
formatter = instance.get('formatter'),
selected = instance.get('selected'),
column,
content,
index = 0,
result = '',
total = items.length,
row,
rows = Math.ceil(total / columns);
for (row = 0; row < rows; row++) {
content = '';
for (column = 0; column < columns; column++) {
index = (row * columns + column);
if (index >= total) {
break;
}
content += formatter.call(instance, items, index, row, column, (selected === index));
}
result += instance._getRowContent(items, index, row, content);
}
return instance._getPaletteContent(items, result);
},
/**
* Returns an Object, which contains the following properties:
* column - The current column
* item - The current item node
* index - The current item index
* row - The current row
* src - UI_SRC
* value - The value of the item.
*
* @method _getEventsPayload
* @param {CustomEvent} event The fired event
* @protected
* @return {Object} The event payload
*/
_getEventsPayload: function(event) {
var instance = this,
items = instance.get('items'),
index,
itemNode = event.currentTarget;
index = Lang.toInt(itemNode.getAttribute('data-index'));
return {
column: Lang.toInt(itemNode.getAttribute('data-column')),
item: itemNode,
index: index,
row: Lang.toInt(itemNode.getAttribute('data-row')),
src: UI_SRC,
value: items[index]
};
},
/**
* Returns the items in the Palette as an `NodeList`.
*
* @method _getIndexedItems
* @protected
* @return {NodeList} The indexed items
*/
_getIndexedItems: function() {
var instance = this;
if (!instance._items) {
instance._items = instance.get('contentBox').all('.' + CSS_PALETTE_ITEM);
}
return instance._items;
},
/**
* Replaces the Template of the Container with the content.
*
* @method _getPaletteContent
* @param {Array} items Palette items
* @param {String} content The generated content
* @protected return {String} The final content of the Palette according
* to the Container Template
*/
_getPaletteContent: function(items, content) {
var instance = this;
return Lang.sub(
instance.CONTAINER_TEMPLATE, {
className: CSS_PALETTE_CONTAINER,
content: content
}
);
},
/**
* Retrieves the content of a row (the container of the items) in the
* Palette.
*
* @method _getRowContent
* @param {Array} items The items of the Palette
* @param {Number} index The index of the item
* @param {Number} row The row number
* @param {String} content The content of the row
* @protected
* @return {String} The generated content of the row
*/
_getRowContent: function(items, index, row, content) {
var instance = this;
return Lang.sub(
instance.ITEMS_CONTAINER_TEMPLATE, {
className: CSS_PALETTE_ITEMS_CONTAINER_INDEX,
content: content,
index: row
}
);
},
/**
* Fires `select` or `unselect` events together with the payload.
*
* @method _onItemClick
* @param {CustomEvent} event The fired event
* @protected
*/
_onItemClick: function(event) {
var instance = this,
selected = instance.get('selected'),
toggleSelection = instance.get('toggleSelection'),
eventName,
itemNode = event.currentTarget,
index = Lang.toInt(itemNode.getAttribute('data-index'));
if (index !== selected) {
eventName = 'select';
}
else if (toggleSelection) {
eventName = 'unselect';
}
if (eventName) {
instance.fire(eventName, instance._getEventsPayload(event));
}
},
/**
* Fires `enter` event.
*
* @method _onItemMouseEnter
* @param {CustomEvent} event
* @protected
*/
_onItemMouseEnter: function(event) {
var instance = this;
instance.fire('enter', instance._getEventsPayload(event));
},
/**
* Fires `leave` event.
*
* @method _onItemMouseLeave
* @param {CustomEvent} event The fired event
* @protected
*/
_onItemMouseLeave: function(event) {
var instance = this;
instance.fire('leave', instance._getEventsPayload(event));
},
/**
* Calculates the needed `columns`, generates the content and adds the
* content of the `contentBox` of the Palette.
*
* @method _uiSetItems
* @param {Array} val The items of the Palette
* @protected
*/
_uiSetItems: function(val) {
var instance = this,
columns,
width = instance.get('width');
if (width) {
columns = val.length;
}
else {
columns = instance.get('columns');
if (columns === -1) {
columns = val.length;
}
}
instance.get('contentBox').setHTML(
instance.get('containerNode') || instance._getContent(val, columns));
},
/**
* Provides a default value (Function) to the `formatter` property.
*
* @method _valueFormatterFn
* @protected
* @return {Function} The formatter function
*/
_valueFormatterFn: function() {
return function(items, index, row, column, selected) {
var instance = this;
var item = items[index];
return Lang.sub(
instance.ITEM_TEMPLATE, {
column: column,
index: index,
row: row,
selectedClassName: selected ? CSS_PALETTE_ITEM_SELECTED : '',
value: Lang.isObject(item) ? item.value : item
}
);
};
}
}, {
/**
* Object hash, defining how attribute values have to be parsed from
* markup contained in the Palette's content box.
*
* @property HTML_PARSER
* @type Object
* @static
*/
HTML_PARSER: {
containerNode: '.' + CSS_PALETTE_CONTAINER
},
/**
* Static property used to define the default attribute
* configuration for the Palette.
*
* @property ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* Specifies how many columns should contain the Palette. If the
* value is a positive number, the Palette will generate as many
* columns as specified in this property and it will fit the
* provided `items` in these columns.
*
* @attribute columns
* @default -1 Value of -1 means the items won't be fit in columns.
* Otherwise, the items will be rendered in the provided number
* of columns
* @type Number
*/
columns: {
validator: Lang.isNumber,
value: -1
},
/**
* Container node of the palette. If found, palette widget will not
* generate content.
*
* @attribute containerNode
* @type Node
*/
containerNode: {},
/**
* Provides a function, which will be used to format the content
* during Palette creation.
*
* @attribute formatter
* @type Function
*/
formatter: {
validator: Lang.isFunction,
valueFn: '_valueFormatterFn'
},
/**
* An array of Palette items. These items will be rendered in the
* Palette according to the specified `columns`.
*
* @attribute items
* @default []
* @type Array
*/
items: {
validator: Lang.isArray,
value: []
},
/**
* Provides the index of currently selected item.
*
* @attribute selected
* @type Number
*/
selected: {
validator: Lang.isNumber,
value: -1
},
/**
* If true, on user interaction if the user clicks on an already
* selected element, it will be unselected.
*
* @attribute toggleSelection
* @default true
* @type Boolean
*/
toggleSelection: {
validator: Lang.isBoolean,
value: true
}
}
});
A.Palette = Palette;