AUI.add('aui-tree-node', function(A) {
/**
* The TreeNode Utility
*
* @module aui-tree
* @submodule aui-tree-node
*/
var L = A.Lang,
isString = L.isString,
isBoolean = L.isBoolean,
ALWAYS_SHOW_HITAREA = 'alwaysShowHitArea',
BLANK = '',
BOUNDING_BOX = 'boundingBox',
CHILDREN = 'children',
CLEARFIX = 'clearfix',
COLLAPSED = 'collapsed',
CONTAINER = 'container',
CONTENT = 'content',
CONTENT_BOX = 'contentBox',
EXPANDED = 'expanded',
HELPER = 'helper',
HIDDEN = 'hidden',
HITAREA = 'hitarea',
HIT_AREA_EL = 'hitAreaEl',
ICON = 'icon',
ICON_EL = 'iconEl',
ID = 'id',
LABEL = 'label',
LABEL_EL = 'labelEl',
LAST_SELECTED = 'lastSelected',
LEAF = 'leaf',
NODE = 'node',
OVER = 'over',
OWNER_TREE = 'ownerTree',
PARENT_NODE = 'parentNode',
SELECTED = 'selected',
SPACE = ' ',
TREE = 'tree',
TREE_NODE = 'tree-node',
concat = function() {
return Array.prototype.slice.call(arguments).join(SPACE);
},
isTreeNode = function(v) {
return ( v instanceof A.TreeNode );
},
isTreeView = function(v) {
return ( v instanceof A.TreeView );
},
getCN = A.ClassNameManager.getClassName,
CSS_HELPER_CLEARFIX = getCN(HELPER, CLEARFIX),
CSS_TREE_COLLAPSED = getCN(TREE, COLLAPSED),
CSS_TREE_CONTAINER = getCN(TREE, CONTAINER),
CSS_TREE_EXPANDED = getCN(TREE, EXPANDED),
CSS_TREE_HIDDEN = getCN(TREE, HIDDEN),
CSS_TREE_HITAREA = getCN(TREE, HITAREA),
CSS_TREE_ICON = getCN(TREE, ICON),
CSS_TREE_LABEL = getCN(TREE, LABEL),
CSS_TREE_NODE_CONTENT = getCN(TREE, NODE, CONTENT),
CSS_TREE_NODE_HIDDEN_HITAREA = getCN(TREE, NODE, HIDDEN, HITAREA),
CSS_TREE_NODE_LEAF = getCN(TREE, NODE, LEAF),
CSS_TREE_NODE_OVER = getCN(TREE, NODE, OVER),
CSS_TREE_NODE_SELECTED = getCN(TREE, NODE, SELECTED),
HIT_AREA_TPL = '<div class="'+CSS_TREE_HITAREA+'"></div>',
ICON_TPL = '<div class="'+CSS_TREE_ICON+'"></div>',
LABEL_TPL = '<div class="'+CSS_TREE_LABEL+'"></div>',
NODE_CONTAINER_TPL = '<ul></ul>',
NODE_BOUNDING_TEMPLATE = '<li></li>',
NODE_CONTENT_TEMPLATE = '<div class="'+concat(CSS_HELPER_CLEARFIX, CSS_TREE_NODE_CONTENT)+'"></div>';
/**
* A base class for TreeNode, providing:
* <ul>
* <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
* <li>The node for the TreeView component</li>
* </ul>
*
* Quick Example:<br/>
*
* <pre><code>var instance = new A.TreeNode({
boundingBox: ''
}).render();
* </code></pre>
*
* Check the list of <a href="TreeNode.html#configattributes">Configuration Attributes</a> available for
* TreeNode.
*
* @param config {Object} Object literal specifying widget configuration properties.
*
* @class TreeNode
* @constructor
* @extends TreeData
*/
var TreeNode = A.Component.create(
{
/**
* Static property provides a string to identify the class.
*
* @property TreeNode.NAME
* @type String
* @static
*/
NAME: TREE_NODE,
/**
* Static property used to define the default attribute
* configuration for the TreeNode.
*
* @property TreeNode.ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* If true the TreeNode is draggable.
*
* @attribute draggable
* @default true
* @type boolean
*/
draggable: {
value: true,
validator: isBoolean
},
/**
* TreeView which contains the current TreeNode.
*
* @attribute ownerTree
* @default null
* @type TreeView
*/
ownerTree: {
value: null
},
/**
* Label of the TreeNode.
*
* @attribute label
* @default ''
* @type String
*/
label: {
value: BLANK,
validator: isString
},
/**
* Whether the TreeNode is expanded by default.
*
* @attribute expanded
* @default false
* @type boolean
*/
expanded: {
value: false,
validator: isBoolean
},
/**
* Id of the TreeNode.
*
* @attribute id
* @default null
* @type String
*/
id: {
validator: isString
},
/**
* Whether the TreeNode could have children or not (i.e. if any
* children is present the TreeNode is a leaf).
*
* @attribute leaf
* @default true
* @type boolean
*/
leaf: {
value: true,
setter: function(v) {
// if has children it's not a leaf
if (v && this.get(CHILDREN).length) {
return false;
}
return v;
},
validator: isBoolean
},
/**
* Next sibling of the current TreeNode.
*
* @attribute nextSibling
* @default null
* @type TreeNode
*/
nextSibling: {
value: null,
validator: isTreeNode
},
/**
* Previous sibling of the current TreeNode.
*
* @attribute prevSibling
* @default null
* @type TreeNode
*/
prevSibling: {
value: null,
validator: isTreeNode
},
/**
* Parent node of the current TreeNode.
*
* @attribute parentNode
* @default null
* @type TreeNode
*/
parentNode: {
value: null,
validator: function(val) {
return isTreeNode(val) || isTreeView(val);
}
},
/**
* Label element to house the <code>label</code> attribute.
*
* @attribute labelEl
* @default Generated DOM element.
* @type Node | String
*/
labelEl: {
setter: A.one,
valueFn: function() {
var label = this.get(LABEL);
return A.Node.create(LABEL_TPL).html(label).unselectable();
}
},
/**
* Hitarea element.
*
* @attribute hitAreaEl
* @default Generated DOM element.
* @type Node | String
*/
hitAreaEl: {
setter: A.one,
valueFn: function() {
return A.Node.create(HIT_AREA_TPL);
}
},
/**
* Always show the hitarea icon.
*
* @attribute alwaysShowHitArea
* @default true
* @type boolean
*/
alwaysShowHitArea: {
value: true,
validator: isBoolean
},
/**
* Icon element.
*
* @attribute iconEl
* @type Node | String
*/
iconEl: {
setter: A.one,
valueFn: function() {
return A.Node.create(ICON_TPL);
}
},
tabIndex: {
value: null
}
},
EXTENDS: A.TreeData,
prototype: {
/**
* Replaced BOUNDING_TEMPLATE with NODE_BOUNDING_TEMPLATE.
*
* @property BOUNDING_TEMPLATE
* @type String
* @protected
*/
BOUNDING_TEMPLATE: NODE_BOUNDING_TEMPLATE,
/**
* Replaced CONTENT_TEMPLATE with NODE_CONTENT_TEMPLATE.
*
* @property CONTENT_TEMPLATE
* @type String
* @protected
*/
CONTENT_TEMPLATE: NODE_CONTENT_TEMPLATE,
/**
* Construction logic executed during TreeNode instantiation. Lifecycle.
*
* @method initializer
* @protected
*/
initializer: function() {
var instance = this;
// Sync the Widget TreeNode id with the BOUNDING_BOX id
instance._syncTreeNodeBBId();
// invoking TreeData initializer
TreeNode.superclass.initializer.apply(this, arguments);
},
/**
* Bind the events on the TreeNode UI. Lifecycle.
*
* @method bindUI
* @protected
*/
bindUI: function() {
var instance = this;
// binding collapse/expand
instance.publish('collapse', { defaultFn: instance._collapse });
instance.publish('expand', { defaultFn: instance._expand });
instance.after('childrenChange', A.bind(instance._afterSetChildren, instance));
instance.after('idChange', instance._afterSetId, instance);
},
/**
* Create the DOM structure for the TreeNode. Lifecycle. Overloading
* private _renderUI, don't call this._renderBox method avoid render node on
* the body.
*
* @method _renderUI
* @protected
*/
_renderUI: function(parentNode) {
this._renderBoxClassNames();
// this._renderBox(parentNode);
},
/**
* Create the DOM structure for the TreeNode. Lifecycle.
*
* @method renderUI
* @protected
*/
renderUI: function() {
var instance = this;
instance._renderBoundingBox();
instance._renderContentBox();
},
/**
* Sync the TreeNode UI. Lifecycle.
*
* @method syncUI
* @protected
*/
syncUI: function() {
var instance = this;
instance._syncHitArea( instance.get( CHILDREN ) );
},
/**
* Render the <code>contentBox</code> node.
*
* @method _renderContentBox
* @protected
* @return {Node}
*/
_renderContentBox: function(v) {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
if (instance.isLeaf()) {
// add leaf css classes
contentBox.addClass(CSS_TREE_NODE_LEAF);
}
else {
var expanded = instance.get(EXPANDED);
// add folder css classes state
contentBox.addClass(
expanded ? CSS_TREE_EXPANDED : CSS_TREE_COLLAPSED
);
if (expanded) {
instance.expand();
}
}
return contentBox;
},
/**
* Render the <code>boundingBox</code> node.
*
* @method _renderBoundingBox
* @protected
* @return {Node}
*/
_renderBoundingBox: function() {
var instance = this;
var boundingBox = instance.get(BOUNDING_BOX);
var contentBox = instance.get(CONTENT_BOX);
var nodeContainer = null;
if (!instance.isLeaf()) {
// append hitarea element
contentBox.append( instance.get(HIT_AREA_EL) );
// if has children append them to this model
nodeContainer = instance._createNodeContainer();
}
contentBox.append( instance.get(ICON_EL) );
contentBox.append( instance.get(LABEL_EL) );
boundingBox.append(contentBox);
if (nodeContainer) {
if (!instance.get(EXPANDED)) {
nodeContainer.addClass(CSS_TREE_HIDDEN);
}
boundingBox.append(nodeContainer);
}
return boundingBox;
},
/**
* Render the node container.
*
* @method _createNodeContainer
* @protected
* @return {Node}
*/
_createNodeContainer: function() {
var instance = this;
// creating <ul class="aui-tree-container">
var nodeContainer = instance.get(CONTAINER) || A.Node.create(NODE_CONTAINER_TPL);
nodeContainer.addClass(CSS_TREE_CONTAINER);
// when it's not a leaf it has a <ul> container
instance.set(CONTAINER, nodeContainer);
instance.eachChildren(function(node) {
instance.appendChild(node);
});
return nodeContainer;
},
/**
* Sync the hitarea UI.
*
* @method _syncHitArea
* @param {Array} children
* @protected
*/
_syncHitArea: function(children) {
var instance = this;
if (instance.get(ALWAYS_SHOW_HITAREA) || children.length) {
instance.showHitArea();
}
else {
instance.hideHitArea();
instance.collapse();
}
},
/*
* Methods
*/
appendChild: function() {
var instance = this;
if (!instance.isLeaf()) {
TreeNode.superclass.appendChild.apply(instance, arguments);
}
},
/**
* Collapse the current TreeNode.
*
* @method collapse
*/
collapse: function() {
var instance = this;
if (instance.get(EXPANDED)) {
var output = instance.getEventOutputMap(instance);
instance.bubbleEvent('collapse', output);
}
},
/**
* Collapse the current TreeNode.
*
* @method _collapse
* @protected
*/
_collapse: function(event) {
// stopActionPropagation while bubbling
if (event.stopActionPropagation) {
return false;
}
var instance = this;
if (!instance.isLeaf()) {
var container = instance.get(CONTAINER);
var contentBox = instance.get(CONTENT_BOX);
contentBox.replaceClass(CSS_TREE_EXPANDED, CSS_TREE_COLLAPSED);
if (container) {
container.addClass(CSS_TREE_HIDDEN);
}
instance.set(EXPANDED, false);
}
},
collapseAll: function() {
var instance = this;
TreeNode.superclass.collapseAll.apply(instance, arguments);
// instance is also a node, so collapse itself
instance.collapse();
},
/**
* Check if the current TreeNode contains the passed <code>node</code>.
*
* @method contains
* @param {TreeNode} node
* @return {boolean}
*/
contains: function(node) {
return node.isAncestor(this);
},
/**
* Expand the current TreeNode.
*
* @method expand
*/
expand: function() {
var instance = this;
if (!instance.get(EXPANDED)) {
var output = instance.getEventOutputMap(instance);
instance.bubbleEvent('expand', output);
}
},
/**
* Expand the current TreeNode.
*
* @method _expand
*/
_expand: function(event) {
// stopActionPropagation while bubbling
if (event.stopActionPropagation) {
return false;
}
var instance = this;
if (!instance.isLeaf()) {
var container = instance.get(CONTAINER);
var contentBox = instance.get(CONTENT_BOX);
contentBox.replaceClass(CSS_TREE_COLLAPSED, CSS_TREE_EXPANDED);
if (container) {
container.removeClass(CSS_TREE_HIDDEN);
}
instance.set(EXPANDED, true);
}
},
expandAll: function() {
var instance = this;
TreeNode.superclass.expandAll.apply(instance, arguments);
// instance is also a node, so expand itself
instance.expand();
},
/**
* Get the depth of the current TreeNode.
*
* @method getDepth
* @return {Number}
*/
getDepth: function() {
var depth = 0;
var instance = this;
var parentNode = instance.get(PARENT_NODE);
while (parentNode) {
++depth;
parentNode = parentNode.get(PARENT_NODE);
}
return depth;
},
hasChildNodes: function() {
var instance = this;
return (!instance.isLeaf() &&
TreeNode.superclass.hasChildNodes.apply(this, arguments));
},
/**
* Whether the current TreeNode is selected or not.
*
* @method isSelected
* @return {boolean}
*/
isSelected: function() {
return this.get(CONTENT_BOX).hasClass(CSS_TREE_NODE_SELECTED);
},
/**
* Whether the current TreeNode is a leaf or not.
*
* @method isLeaf
* @return {boolean}
*/
isLeaf: function() {
var instance = this;
return instance.get(LEAF);
},
/**
* Whether the current TreeNode is ancestor of the passed <code>node</code> or not.
*
* @method isLeaf
* @return {boolean}
*/
isAncestor: function(node) {
var instance = this;
var parentNode = instance.get(PARENT_NODE);
while (parentNode) {
if (parentNode == node) {
return true;
}
parentNode = parentNode.get(PARENT_NODE);
}
return false;
},
insertAfter: function(node, refNode) {
var instance = this;
TreeNode.superclass.insertAfter.apply(this, [node, instance]);
},
insertBefore: function(node) {
var instance = this;
TreeNode.superclass.insertBefore.apply(this, [node, instance]);
},
removeChild: function(node) {
var instance = this;
if (!instance.isLeaf()) {
TreeNode.superclass.removeChild.apply(instance, arguments);
}
},
/**
* Toggle the current TreeNode, <code>collapsed</code> or <code>expanded</code>.
*
* @method toggle
*/
toggle: function() {
var instance = this;
if (instance.get(EXPANDED)) {
instance.collapse();
}
else {
instance.expand();
}
},
/*
* Select the current TreeNode.
*
* @method select
*/
select: function() {
var instance = this;
var ownerTree = instance.get(OWNER_TREE);
if (ownerTree) {
ownerTree.set(LAST_SELECTED, instance);
}
instance.get(CONTENT_BOX).addClass(CSS_TREE_NODE_SELECTED);
instance.fire('select');
},
/*
* Unselect the current TreeNode.
*
* @method unselect
*/
unselect: function() {
var instance = this;
instance.get(CONTENT_BOX).removeClass(CSS_TREE_NODE_SELECTED);
instance.fire('unselect');
},
/*
* Fires when <code>mouseover</code> the current TreeNode.
*
* @method over
*/
over: function() {
this.get(CONTENT_BOX).addClass(CSS_TREE_NODE_OVER);
},
/*
* Fires when <code>mouseout</code> the current TreeNode.
*
* @method over
*/
out: function() {
this.get(CONTENT_BOX).removeClass(CSS_TREE_NODE_OVER);
},
/*
* Show hitarea icon.
*
* @method showHitArea
*/
showHitArea: function() {
var instance = this;
var hitAreaEl = instance.get(HIT_AREA_EL);
hitAreaEl.removeClass(CSS_TREE_NODE_HIDDEN_HITAREA);
},
/*
* Hide hitarea icon.
*
* @method hideHitArea
*/
hideHitArea: function() {
var instance = this;
var hitAreaEl = instance.get(HIT_AREA_EL);
hitAreaEl.addClass(CSS_TREE_NODE_HIDDEN_HITAREA);
},
/**
* Set the <code>boundingBox</code> id.
*
* @method _syncTreeNodeBBId
* @param {String} id
* @protected
*/
_syncTreeNodeBBId: function(id) {
var instance = this;
instance.get(BOUNDING_BOX).attr(
ID,
instance.get(ID)
);
},
/**
* Fires after set children.
*
* @method _afterSetChildren
* @param {EventFacade} event
* @protected
*/
_afterSetChildren: function(event) {
var instance = this;
instance._syncHitArea(event.newVal);
}
}
}
);
A.TreeNode = TreeNode;
/*
* TreeNodeIO
*/
var isFunction = L.isFunction,
isObject = L.isObject,
isValue = L.isValue,
CACHE = 'cache',
END = 'end',
IO = 'io',
LIMIT = 'limit',
LOADED = 'loaded',
LOADING = 'loading',
PAGINATOR = 'paginator',
START = 'start',
TREE_NODE_IO = 'tree-node-io',
EV_TREE_NODE_PAGINATOR_CLICK = 'paginatorClick',
CSS_TREE_NODE_PAGINATOR = getCN(TREE, NODE, PAGINATOR),
CSS_TREE_NODE_IO_LOADING = getCN(TREE, NODE, IO, LOADING),
TPL_PAGINATOR = '<a class="'+CSS_TREE_NODE_PAGINATOR+'" href="javascript:void(0);">Load more results</a>';
/**
* A base class for TreeNodeIO, providing:
* <ul>
* <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
* <li>Ajax support to load the children of the current TreeNode</li>
* </ul>
*
* Quick Example:<br/>
*
* <pre><code>var treeNodeIO = new A.TreeNodeIO({
* label: 'TreeNodeIO',
* cache: false,
* io: {
* url: 'assets/content.html'
* }
* });
* </code></pre>
*
* Check the list of <a href="TreeNodeIO.html#configattributes">Configuration Attributes</a> available for
* TreeNodeIO.
*
* @param config {Object} Object literal specifying widget configuration properties.
*
* @class TreeNodeIO
* @constructor
* @extends TreeNode
*/
var TreeNodeIO = A.Component.create(
{
/**
* Static property provides a string to identify the class.
*
* @property TreeNode.NAME
* @type String
* @static
*/
NAME: TREE_NODE_IO,
/**
* Static property used to define the default attribute
* configuration for the TreeNode.
*
* @property TreeNode.ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* IO options for the current TreeNode load the children.
*
* @attribute io
* @default Default IO Configuration.
* @type Object
*/
io: {
lazyAdd: false,
value: null,
setter: function(v) {
return this._setIO(v);
}
},
/**
* Whether the current TreeNode IO transaction is loading.
*
* @attribute loading
* @default false
* @type boolean
*/
loading: {
value: false,
validator: isBoolean
},
/**
* Whether the current TreeNode has loaded the content.
*
* @attribute loaded
* @default false
* @type boolean
*/
loaded: {
value: false,
validator: isBoolean
},
/**
* Whether the current TreeNode should cache the loaded content or not.
*
* @attribute cache
* @default true
* @type boolean
*/
cache: {
value: true,
validator: isBoolean
},
leaf: {
value: false,
validator: isBoolean
},
paginator: {
setter: function(val) {
return A.merge(
{
alwaysVisible: false,
autoFocus: true,
element: A.Node.create(TPL_PAGINATOR),
endParam: END,
limitParam: LIMIT,
start: 0,
startParam: START
},
val
);
},
validator: isObject
}
},
EXTENDS: A.TreeNode,
prototype: {
/**
* Create the DOM structure for the TreeNodeIO. Lifecycle.
*
* @method renderUI
* @protected
*/
renderUI: function() {
var instance = this;
instance._inheritOwnerTreeAttrs();
TreeNodeIO.superclass.renderUI.apply(this, arguments);
},
/**
* Bind the events on the TreeNodeIO UI. Lifecycle.
*
* @method bindUI
* @protected
*/
bindUI: function() {
var instance = this;
TreeNodeIO.superclass.bindUI.apply(this, arguments);
instance._bindPaginatorUI();
instance._createEvents();
},
/**
* Bind events to the paginator "show more" link.
*
* @method _bindPaginatorUI
* @protected
*/
_bindPaginatorUI: function() {
var instance = this;
var paginator = instance.get(PAGINATOR);
if (paginator) {
var handlePaginator = A.bind(instance._handlePaginatorClickEvent, instance);
paginator.element.on('click', handlePaginator);
}
},
/*
* Methods
*/
createNode: function(nodes) {
var instance = this;
A.each(nodes, function(node) {
var newNode = TreeNodeIO.superclass.createNode.apply(instance, [node]);
instance.appendChild(newNode);
});
instance._syncPaginatorUI(nodes);
},
expand: function() {
var instance = this;
var cache = instance.get(CACHE);
var io = instance.get(IO);
var loaded = instance.get(LOADED);
var loading = instance.get(LOADING);
if (!cache) {
// if cache is false on expand, always set LOADED to false
instance.set(LOADED, false);
}
if (!io || loaded) {
TreeNodeIO.superclass.expand.apply(this, arguments);
}
else {
if (!loading) {
if (!cache) {
// remove all children to reload
instance.empty();
}
instance.initIO();
}
}
},
/**
* Initialize the IO transaction setup on the <a
* href="TreeNode.html#config_io">io</a> attribute.
*
* @method initIO
*/
initIO: function() {
var instance = this;
var io = instance.get(IO);
if (isFunction(io.cfg.data)) {
io.cfg.data = io.cfg.data.apply(instance, [instance]);
}
instance._syncPaginatorIOData(io);
if (isFunction(io.loader)) {
var loader = A.bind(io.loader, instance);
// apply loader in the TreeNodeIO scope
loader(io.url, io.cfg, instance);
}
else {
A.io(io.url, io.cfg);
}
},
/**
* IO Start handler.
*
* @method ioStartHandler
*/
ioStartHandler: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
instance.set(LOADING, true);
contentBox.addClass(CSS_TREE_NODE_IO_LOADING);
},
/**
* IO Complete handler.
*
* @method ioCompleteHandler
*/
ioCompleteHandler: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
instance.set(LOADING, false);
instance.set(LOADED, true);
contentBox.removeClass(CSS_TREE_NODE_IO_LOADING);
},
/**
* IO Success handler.
*
* @method ioSuccessHandler
*/
ioSuccessHandler: function() {
var instance = this;
var io = instance.get(IO);
var args = Array.prototype.slice.call(arguments);
var length = args.length;
// if using the first argument as the JSON object
var nodes = args[0];
// if using (id, o) yui callback syntax
if (length >= 2) {
var o = args[1];
// try to convert responseText to JSON
try {
nodes = A.JSON.parse(o.responseText);
}
catch(e) {}
}
var formatter = io.formatter;
if (formatter) {
nodes = formatter(nodes);
}
instance.createNode(nodes);
instance.expand();
},
/**
* IO Failure handler.
*
* @method ioFailureHandler
*/
ioFailureHandler: function() {
var instance = this;
instance.set(LOADING, false);
instance.set(LOADED, false);
},
/**
* Create custom events.
*
* @method _createEvents
* @private
*/
_createEvents: function() {
var instance = this;
instance.publish(
EV_TREE_NODE_PAGINATOR_CLICK,
{
defaultFn: instance._defPaginatorClickFn,
prefix: TREE_NODE_IO
}
);
},
/**
* Default paginatorClick event handler. Increment the
* <code>paginator.start</code> to the next <code>paginator.limit</code>.
*
* @method _defPaginatorClickFn
* @param {EventFacade} event The Event object
* @protected
*/
_defPaginatorClickFn: function(event) {
var instance = this;
var paginator = instance.get(PAGINATOR);
if (isValue(paginator.limit)) {
paginator.start += paginator.limit;
}
if (instance.get(IO)) {
instance.initIO();
}
},
/**
* Fires the paginatorClick event.
*
* @method _handlePaginatorClickEvent
* @param {EventFacade} event paginatorClick event facade
* @protected
*/
_handlePaginatorClickEvent: function(event) {
var instance = this;
var ownerTree = instance.get(OWNER_TREE);
var output = instance.getEventOutputMap(instance);
instance.fire(EV_TREE_NODE_PAGINATOR_CLICK, output);
if (ownerTree) {
ownerTree.fire(EV_TREE_NODE_PAGINATOR_CLICK, output);
}
event.halt();
},
/**
* If not specified on the TreeNode some attributes are inherited from the
* ownerTree by this method.
*
* @method _inheritOwnerTreeAttrs
* @protected
*/
_inheritOwnerTreeAttrs: function() {
var instance = this;
var ownerTree = instance.get(OWNER_TREE);
if (ownerTree) {
if (!instance.get(IO)) {
instance.set(IO, A.clone(ownerTree.get(IO)));
}
if (!instance.get(PAGINATOR)) {
var otPaginator = ownerTree.get(PAGINATOR);
// make sure we are not using the same element passed to the ownerTree on the TreeNode
if (otPaginator && otPaginator.element) {
otPaginator.element = otPaginator.element.clone();
}
instance.set(PAGINATOR, otPaginator);
}
}
},
/**
* Setter for <a href="TreeNodeIO.html#config_io">io</a>.
*
* @method _setIO
* @protected
* @param {Object} v
* @return {Object}
*/
_setIO: function(v) {
var instance = this;
if (!v) {
return null;
}
else if (isString(v)) {
v = { url: v };
}
v = v || {};
v.cfg = v.cfg || {};
v.cfg.on = v.cfg.on || {};
var defCallbacks = {
start: A.bind(instance.ioStartHandler, instance),
complete: A.bind(instance.ioCompleteHandler, instance),
success: A.bind(instance.ioSuccessHandler, instance),
failure: A.bind(instance.ioFailureHandler, instance)
};
A.each(defCallbacks, function(fn, name) {
var userFn = v.cfg.on[name];
if (isFunction(userFn)) {
// wrapping user callback and default callback, invoking both handlers
var wrappedFn = function() {
fn.apply(instance, arguments);
userFn.apply(instance, arguments);
};
v.cfg.on[name] = A.bind(wrappedFn, instance);
}
else {
// get from defCallbacks map
v.cfg.on[name] = fn;
}
});
return v;
},
/**
* Adds two extra IO data parameter to the request to handle the
* paginator. By default these parameters are <code>limit</code> and
* <code>start</code>.
*
* @method _syncPaginatorIOData
* @protected
*/
_syncPaginatorIOData: function(io) {
var instance = this;
var paginator = instance.get(PAGINATOR);
if (paginator && isValue(paginator.limit)) {
var data = io.cfg.data || {};
data[ paginator.limitParam ] = paginator.limit;
data[ paginator.startParam ] = paginator.start;
data[ paginator.endParam ] = (paginator.start + paginator.limit);
io.cfg.data = data;
}
},
/**
* Sync the paginator link UI.
*
* @method _syncPaginatorUI
* @protected
*/
_syncPaginatorUI: function(newNodes) {
var instance = this;
var children = instance.get(CHILDREN);
var paginator = instance.get(PAGINATOR);
if (paginator) {
var hasMoreData = (newNodes && newNodes.length);
var showPaginator = hasMoreData && (children.length >= paginator.limit);
if (paginator.alwaysVisible || showPaginator) {
instance.get(CONTAINER).append(
paginator.element.show()
);
if (paginator.autoFocus) {
try {
paginator.element.focus();
}
catch(e) {}
}
}
else {
paginator.element.hide();
}
}
}
}
}
);
A.TreeNodeIO = TreeNodeIO;
/*
* TreeNodeCheck
*/
var CHECKBOX = 'checkbox',
CHECKED = 'checked',
CHECK_CONTAINER_EL = 'checkContainerEl',
CHECK_EL = 'checkEl',
CHECK_NAME = 'checkName',
DOT = '.',
NAME = 'name',
TREE_NODE_CHECK = 'tree-node-check',
CSS_TREE_NODE_CHECKBOX = getCN(TREE, NODE, CHECKBOX),
CSS_TREE_NODE_CHECKBOX_CONTAINER = getCN(TREE, NODE, CHECKBOX, CONTAINER),
CSS_TREE_NODE_CHECKED = getCN(TREE, NODE, CHECKED),
CHECKBOX_CONTAINER_TPL = '<div class="'+CSS_TREE_NODE_CHECKBOX_CONTAINER+'"></div>',
CHECKBOX_TPL = '<input class="'+CSS_TREE_NODE_CHECKBOX+'" type="checkbox" />';
/**
* <p><img src="assets/images/aui-tree-nod-check/main.png"/></p>
*
* A base class for TreeNodeCheck, providing:
* <ul>
* <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
* <li>Checkbox support for the TreeNode</li>
* </ul>
*
* Check the list of <a href="TreeNodeCheck.html#configattributes">Configuration Attributes</a> available for
* TreeNodeCheck.
*
* @param config {Object} Object literal specifying widget configuration properties.
*
* @class TreeNodeCheck
* @constructor
* @extends TreeNodeIO
*/
var TreeNodeCheck = A.Component.create(
{
/**
* Static property provides a string to identify the class.
*
* @property TreeNode.NAME
* @type String
* @static
*/
NAME: TREE_NODE_CHECK,
/**
* Static property used to define the default attribute
* configuration for the TreeNode.
*
* @property TreeNode.ATTRS
* @type Object
* @static
*/
ATTRS: {
/**
* Whether the TreeNode is checked or not.
*
* @attribute checked
* @default false
* @type boolean
*/
checked: {
value: false,
validator: isBoolean
},
/**
* Name of the checkbox element used on the current TreeNode.
*
* @attribute checkName
* @default 'tree-node-check'
* @type String
*/
checkName: {
value: TREE_NODE_CHECK,
validator: isString
},
/**
* Container element for the checkbox.
*
* @attribute checkContainerEl
* @default Generated DOM element.
* @type Node | String
*/
checkContainerEl: {
setter: A.one,
valueFn: function() {
return A.Node.create(CHECKBOX_CONTAINER_TPL);
}
},
/**
* Checkbox element.
*
* @attribute checkEl
* @default Generated DOM element.
* @type Node | String
*/
checkEl: {
setter: A.one,
valueFn: function() {
var checkName = this.get(CHECK_NAME);
return A.Node.create(CHECKBOX_TPL).attr(NAME, checkName);
}
}
},
EXTENDS: A.TreeNodeIO,
prototype: {
/*
* Lifecycle
*/
renderUI: function() {
var instance = this;
TreeNodeCheck.superclass.renderUI.apply(instance, arguments);
var labelEl = instance.get(LABEL_EL);
var checkEl = instance.get(CHECK_EL);
var checkContainerEl = instance.get(CHECK_CONTAINER_EL);
checkEl.hide();
checkContainerEl.append(checkEl);
labelEl.placeBefore(checkContainerEl);
if (instance.isChecked()) {
instance.check();
}
},
bindUI: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
var labelEl = instance.get(LABEL_EL);
TreeNodeCheck.superclass.bindUI.apply(instance, arguments);
instance.publish('check');
instance.publish('uncheck');
contentBox.delegate('click', A.bind(instance.toggleCheck, instance), DOT+CSS_TREE_NODE_CHECKBOX_CONTAINER);
contentBox.delegate('click', A.bind(instance.toggleCheck, instance), DOT+CSS_TREE_LABEL);
// cancel dblclick because of the check
labelEl.swallowEvent('dblclick');
},
/**
* Check the current TreeNode.
*
* @method check
*/
check: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
var checkEl = instance.get(CHECK_EL);
contentBox.addClass(CSS_TREE_NODE_CHECKED);
instance.set(CHECKED, true);
checkEl.attr(CHECKED, CHECKED);
instance.fire('check');
},
/**
* Uncheck the current TreeNode.
*
* @method uncheck
*/
uncheck: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
var checkEl = instance.get(CHECK_EL);
contentBox.removeClass(CSS_TREE_NODE_CHECKED);
instance.set(CHECKED, false);
checkEl.attr(CHECKED, BLANK);
instance.fire('uncheck');
},
/**
* Toggle the check status of the current TreeNode.
*
* @method toggleCheck
*/
toggleCheck: function() {
var instance = this;
var checkEl = instance.get(CHECK_EL);
var checked = checkEl.attr(CHECKED);
if (!checked) {
instance.check();
}
else {
instance.uncheck();
}
},
/*
* Whether the current TreeNodeCheck is checked.
*
* @method isChecked
* @return boolean
*/
isChecked: function() {
var instance = this;
return instance.get(CHECKED);
}
}
}
);
A.TreeNodeCheck = TreeNodeCheck;
/*
* TreeNodeTask
*/
var CHILD = 'child',
TREE_NODE_TASK = 'tree-node-task',
UNCHECKED = 'unchecked',
isTreeNodeTask = function(node) {
return node instanceof A.TreeNodeCheck;
},
CSS_TREE_NODE_CHILD_UNCHECKED = getCN(TREE, NODE, CHILD, UNCHECKED);
/**
* <p><img src="assets/images/aui-treeNodeTask/main.png"/></p>
*
* A base class for TreeNodeTask, providing:
* <ul>
* <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
* <li>3 states checkbox support</li>
* <li>Automatic check/uncheck the parent status based on the children checked status</li>
* </ul>
*
* Check the list of <a href="TreeNodeTask.html#configattributes">Configuration Attributes</a> available for
* TreeNodeTask.
*
* @param config {Object} Object literal specifying widget configuration properties.
*
* @class TreeNodeTask
* @constructor
* @extends TreeNodeCheck
*/
var TreeNodeTask = A.Component.create(
{
/**
* Static property provides a string to identify the class.
*
* @property TreeNode.NAME
* @type String
* @static
*/
NAME: TREE_NODE_TASK,
EXTENDS: A.TreeNodeCheck,
prototype: {
/*
* Methods
*/
check: function(stopPropagation) {
var instance = this;
var parentNode = instance.get(PARENT_NODE);
var contentBox = instance.get(CONTENT_BOX);
// invoke default check logic
TreeNodeTask.superclass.check.apply(this, arguments);
if (!stopPropagation) {
// always remove the CSS_TREE_NODE_CHILD_UNCHECKED of the checked node
contentBox.removeClass(CSS_TREE_NODE_CHILD_UNCHECKED);
// loop all parentNodes
instance.eachParent(
function(parentNode) {
// if isTreeNodeTask and isChecked
if (isTreeNodeTask(parentNode)) {
var hasUnchecked = false;
// after check a child always check the parentNode temporary
// and add the CSS_TREE_NODE_CHILD_UNCHECKED state until the !hasUnchecked check
parentNode.check(true);
parentNode.get(CONTENT_BOX).addClass(CSS_TREE_NODE_CHILD_UNCHECKED);
// check if has at least one child uncheked
parentNode.eachChildren(function(child) {
if (isTreeNodeTask(child) && !child.isChecked()) {
hasUnchecked = true;
}
}, true);
// if doesn't have unchecked children remove the CSS_TREE_NODE_CHILD_UNCHECKED class
if (!hasUnchecked) {
parentNode.get(CONTENT_BOX).removeClass(CSS_TREE_NODE_CHILD_UNCHECKED);
}
}
}
);
if (!instance.isLeaf()) {
// check all TreeNodeTask children
instance.eachChildren(function(child) {
if (isTreeNodeTask(child)) {
child.check();
}
});
}
}
},
uncheck: function() {
var instance = this;
var contentBox = instance.get(CONTENT_BOX);
// invoke default uncheck logic
TreeNodeTask.superclass.uncheck.apply(this, arguments);
// always remove the CSS_TREE_NODE_CHILD_UNCHECKED of the clicked node
contentBox.removeClass(CSS_TREE_NODE_CHILD_UNCHECKED);
instance.eachParent(
function(parentNode) {
// if isTreeNodeTask and isChecked
if (isTreeNodeTask(parentNode) && parentNode.isChecked()) {
// add CSS_TREE_NODE_CHILD_UNCHECKED class
parentNode.get(CONTENT_BOX).addClass(CSS_TREE_NODE_CHILD_UNCHECKED);
}
}
);
if (!instance.isLeaf()) {
// uncheck all TreeNodeTask children
instance.eachChildren(function(child) {
if (child instanceof A.TreeNodeCheck) {
child.uncheck();
}
});
}
}
}
}
);
A.TreeNodeTask = TreeNodeTask;
/**
* TreeNode types hash map.
*
* <pre><code>A.TreeNode.nodeTypes = {
* task: A.TreeNodeTask,
* check: A.TreeNodeCheck,
* node: A.TreeNode,
* io: A.TreeNodeIO
*};</code></pre>
*
* @for TreeNode
* @property A.TreeNode.nodeTypes
* @type Object
*/
A.TreeNode.nodeTypes = {
task: A.TreeNodeTask,
check: A.TreeNodeCheck,
node: A.TreeNode,
io: A.TreeNodeIO
};
}, '@VERSION@' ,{requires:['aui-tree-data','io-base','json','querystring-stringify'], skinnable:false});