Alloy UI

aui-tree  1.0.1

 
Filters
AUI.add('aui-tree-data', function(A) {
/**
 * The TreeData Utility
 *
 * @module aui-tree
 * @submodule aui-tree-data
 */

var L = A.Lang,
	isArray = L.isArray,
	isObject = L.isObject,
	isString = L.isString,
	isUndefined = L.isUndefined,

	BOUNDING_BOX = 'boundingBox',
	CHILDREN = 'children',
	CONTAINER = 'container',
	DOT = '.',
	ID = 'id',
	INDEX = 'index',
	NEXT_SIBLING = 'nextSibling',
	NODE = 'node',
	OWNER_TREE = 'ownerTree',
	PARENT_NODE = 'parentNode',
	PREV_SIBLING = 'prevSibling',
	PREVIOUS_SIBLING = 'previousSibling',
	TREE = 'tree',
	TREE_DATA = 'tree-data',

	isTreeNode = function(v) {
		return ( v instanceof A.TreeNode );
	},

	getCN = A.ClassNameManager.getClassName,

	CSS_TREE_NODE = getCN(TREE, NODE);

/**
 * A base class for TreeData, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *    <li>Handle the data of the tree</li>
 *    <li>Basic DOM implementation (append/remove/insert)</li>
 *    <li>Indexing management to handle the children nodes</li>
 * </ul>
 *
 * Check the list of <a href="TreeData.html#configattributes">Configuration Attributes</a> available for
 * TreeData.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class TreeData
 * @constructor
 * @extends Base
 */
var TreeData = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property TreeData.NAME
		 * @type String
		 * @static
		 */
		NAME: TREE_DATA,

		/**
		 * Static property used to define the default attribute
		 * configuration for the TreeData.
		 *
		 * @property TreeData.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * Container to nest children nodes. If has cntainer it's not a leaf.
			 *
			 * @attribute container
			 * @default null
			 * @type Node | String
			 */
			container: {
				setter: A.one
			},

			/**
			 * Array of children (i.e. could be a JSON metadata object or a TreeNode instance).
			 *
			 * @attribute children
			 * @default []
			 * @type Array
			 */
			children: {
				value: [],
				validator: isArray,
				setter: function(v) {
					return this._setChildren(v);
				}
			},

			/**
			 * Index the nodes.
			 *
			 * @attribute index
			 * @default {}
			 * @type Object
			 */
			index: {
				value: {}
			}
		},

		prototype: {
			/**
			 * Empty UI_EVENTS.
			 *
			 * @property UI_EVENTS
			 * @type Object
			 * @protected
			 */
			UI_EVENTS: {},

			/**
			 * Construction logic executed during TreeData instantiation. Lifecycle.
			 *
			 * @method initializer
			 * @protected
			 */
			initializer: function() {
				var instance = this;

				// binding on initializer, needed before .render() phase
				instance.publish('move');
				instance.publish('collapseAll', { defaultFn: instance._collapseAll });
				instance.publish('expandAll', { defaultFn: instance._expandAll });
				instance.publish('append', { defaultFn: instance._appendChild });
				instance.publish('remove', { defaultFn: instance._removeChild });

				TreeData.superclass.initializer.apply(this, arguments);
			},

			/**
			 * Get a TreeNode by id.
			 *
			 * @method getNodeById
			 * @param {String} uid
			 * @return {TreeNode}
			 */
			getNodeById: function(uid) {
				var instance = this;

				return instance.get(INDEX)[uid];
			},

			/**
			 * Whether the TreeNode is registered on this TreeData.
			 *
			 * @method isRegistered
			 * @param {TreeNode} node
			 * @return {boolean}
			 */
			isRegistered: function(node) {
				var instance = this;

				return !!(instance.get(INDEX)[ node.get(ID) ]);
			},

			/**
			 * Update the references of the passed TreeNode.
			 *
			 * @method updateReferences
			 * @param {node} TreeNode
			 * @param {parentNode} TreeNode
			 * @param {ownerTree} TreeView
			 */
			updateReferences: function(node, parentNode, ownerTree) {
				var instance = this;
				var oldParent = node.get(PARENT_NODE);
				var oldOwnerTree = node.get(OWNER_TREE);
				var moved = oldParent && (oldParent != parentNode);

				if (oldParent) {
					if (moved) {
						// when moved update the oldParent children
						var children = oldParent.get(CHILDREN);

						A.Array.removeItem(children, instance);

						oldParent.set(CHILDREN, children);
					}

					oldParent.unregisterNode(node);
				}

				if (oldOwnerTree) {
					oldOwnerTree.unregisterNode(node);
				}

				// update parent reference when registered
				node.set(PARENT_NODE, parentNode);

				// update the ownerTree of the node
				node.set(OWNER_TREE, ownerTree);

				if (parentNode) {
					// register the new node on the parentNode index
					parentNode.registerNode(node);
				}

				if (ownerTree) {
					// register the new node to the ownerTree index
					ownerTree.registerNode(node);
				}

				if (oldOwnerTree != ownerTree) {
					// when change the OWNER_TREE update the children references also
					node.eachChildren(function(child) {
						instance.updateReferences(child, child.get(PARENT_NODE), ownerTree);
					});
				}

				// trigger move event
				if (moved) {
					var output = instance.getEventOutputMap(node);

					output.tree.oldParent = oldParent;
					output.tree.oldOwnerTree = oldOwnerTree;

					instance.bubbleEvent('move', output);
				}
			},

			/**
			 * Refresh the index (i.e. re-index all nodes).
			 *
			 * @method refreshIndex
			 */
			refreshIndex: function() {
				var instance = this;

				// reset index
				instance.updateIndex({});

				// get all descendent children - deep
				instance.eachChildren(function(node) {
					instance.registerNode(node);
				}, true);
			},

			/**
			 * Register the passed TreeNode on this TreeData.
			 *
			 * @method registerNode
			 * @param {TreeNode} node
			 */
			registerNode: function(node) {
				var instance = this;
				var uid = node.get(ID);
				var index = instance.get(INDEX);

				if (uid) {
					index[uid] = node;
				}

				instance.updateIndex(index);
			},

			/**
			 * Update the <a href="TreeData.html#config_index">index</a> attribute value.
			 *
			 * @method updateIndex
			 * @param {Object} index
			 */
			updateIndex: function(index) {
				var instance = this;

				if (index) {
					instance.set(INDEX, index);
				}
			},

			/**
			 * Unregister the passed TreeNode from this TreeData.
			 *
			 * @method unregisterNode
			 * @param {TreeNode} node
			 */
			unregisterNode: function(node) {
				var instance = this;
				var index = instance.get(INDEX);

				delete index[ node.get(ID) ];

				instance.updateIndex(index);
			},

			/**
			 * Collapse all children of the TreeData.
			 *
			 * @method collapseAll
			 */
			collapseAll: function() {
				var instance = this;
				var output = instance.getEventOutputMap(instance);

				instance.fire('collapseAll', output);
			},

			/**
			 * Collapse all children of the TreeData.
			 *
			 * @method _collapseAll
			 * @protected
			 */
			_collapseAll: function(event) {
				var instance = this;

				instance.eachChildren(function(node) {
					node.collapse();
				}, true);
			},

			/**
			 * Expand all children of the TreeData.
			 *
			 * @method expandAll
			 */
			expandAll: function() {
				var instance = this;
				var output = instance.getEventOutputMap(instance);

				instance.fire('expandAll', output);
			},

			/**
			 * Expand all children of the TreeData.
			 *
			 * @method _expandAll
			 * @protected
			 */
			_expandAll: function(event) {
				var instance = this;

				instance.eachChildren(function(node) {
					node.expand();
				}, true);
			},

			/**
			 * Select all children of the TreeData.
			 *
			 * @method selectAll
			 */
			selectAll: function() {
				var instance = this;

				instance.eachChildren(function(child) {
					child.select();
				}, true);
			},

			/**
			 * Unselect all children of the TreeData.
			 *
			 * @method selectAll
			 */
			unselectAll: function() {
				var instance = this;

				instance.eachChildren(function(child) {
					child.unselect();
				}, true);
			},

			/**
			 * Loop each children and execute the <code>fn</code> callback.
			 *
			 * @method eachChildren
			 * @param {function} fn callback
			 * @param {boolean} fn recursive
			 */
			eachChildren: function(fn, deep) {
				var instance = this;
				var children = instance.getChildren(deep);

				A.Array.each(children, function(node) {
					if (node) {
						fn.apply(instance, arguments);
					}
				});
			},

			/**
			 * Loop each parent node and execute the <code>fn</code> callback.
			 *
			 * @method eachChildren
			 * @param {function} fn callback
			 */
			eachParent: function(fn) {
				var instance = this;
				var parentNode = instance.get(PARENT_NODE);

				while (parentNode) {
					if (parentNode) {
						fn.apply(instance, [parentNode]);
					}
					parentNode = parentNode.get(PARENT_NODE);
				}
			},

			/**
			 * Bubble event to all parent nodes.
			 *
			 * @method bubbleEvent
			 * @param {String} eventType
			 * @param {Array} args
			 * @param {boolean} cancelBubbling
			 * @param {boolean} stopActionPropagation
			 */
			bubbleEvent: function(eventType, args, cancelBubbling, stopActionPropagation) {
				var instance = this;

				// event.stopActionPropagation == undefined, invoke the event native action
				instance.fire(eventType, args);

				if (!cancelBubbling) {
					var parentNode = instance.get(PARENT_NODE);

					// Avoid execution of the native action (private methods) while propagate
					// for example: private _appendChild() is invoked only on the first level of the bubbling
					// the intention is only invoke the user callback on parent nodes.
					args = args || {};

					if (isUndefined(stopActionPropagation)) {
						stopActionPropagation = true;
					}

					args.stopActionPropagation = stopActionPropagation;

					while(parentNode) {
						parentNode.fire(eventType, args);
						parentNode = parentNode.get(PARENT_NODE);
					}
				}
			},

			/**
			 * Create a TreeNode instance.
			 *
			 * @method createNode
			 * @param {Object} options
			 * @return {TreeNode}
			 */
			createNode: function(options) {
				var instance = this;
				var classType = options.type;

				if (isString(classType) && A.TreeNode.nodeTypes) {
					classType = A.TreeNode.nodeTypes[classType];
				}

				if (!classType) {
					classType = A.TreeNode;
				}

				return new classType(options);
			},

			/**
			 * Append a child node to the TreeData.
			 *
			 * @method appendChild
			 * @param {TreeNode} node
			 * @param {boolean} cancelBubbling
			 */
			appendChild: function(node, cancelBubbling) {
				var instance = this;
				var output = instance.getEventOutputMap(node);

				instance.bubbleEvent('append', output, cancelBubbling);
			},

			/**
			 * Append a child node to the TreeData.
			 *
			 * @method _appendChild
			 * @param {TreeNode} node
			 * @param {boolean} cancelBubbling
			 * @protected
			 */
			_appendChild: function(event) {
				// stopActionPropagation while bubbling
				if (event.stopActionPropagation) {
					return false;
				}

				var instance = this;
				var node = event.tree.node;
				var ownerTree = instance.get(OWNER_TREE);
				var children = instance.get(CHILDREN);

				// updateReferences first
				instance.updateReferences(node, instance, ownerTree);
				// and then set the children, to have the appendChild propagation
				// the PARENT_NODE references should be updated
				var length = children.push(node);
				instance.set(CHILDREN, children);

				// updating prev/nextSibling attributes
				var prevIndex = length - 2;
				var prevSibling = instance.item(prevIndex);

				node.set(NEXT_SIBLING, null);
				node.set(PREV_SIBLING, prevSibling);

				instance.get(CONTAINER).append(
					node.get(BOUNDING_BOX)
				);

				// render node after it's appended
				node.render();
			},

			/**
			 * Get a TreeNode children by index.
			 *
			 * @method item
			 * @param {Number} index
			 * @return {TreeNode}
			 */
			item: function(index) {
				var instance = this;

				return instance.get(CHILDREN)[index];
			},

			/**
			 * Index of the passed TreeNode on the <a
		     * href="TreeData.html#config_children">children</a> attribute.
			 *
			 * @method indexOf
			 * @param {TreeNode} node
			 * @return {Number}
			 */
			indexOf: function(node) {
				var instance = this;

				return A.Array.indexOf( instance.get(CHILDREN), node );
			},

			/**
			 * Whether the TreeData contains children or not.
			 *
			 * @method hasChildNodes
			 * @return {boolean}
			 */
			hasChildNodes: function() {
				return ( this.get(CHILDREN).length > 0 );
			},

			/**
			 * Get an Array of the children nodes of the current TreeData.
			 *
			 * @method getChildren
			 * @param {boolean} deep
			 * @return {Array}
			 */
			getChildren: function(deep) {
				var instance = this;
				var cNodes = [];
				var children = instance.get(CHILDREN);

				cNodes = cNodes.concat(children);

				if (deep) {
					instance.eachChildren(function(child) {
						cNodes = cNodes.concat( child.getChildren(deep) );
					});
				}

				return cNodes;
			},

			/**
			 * Get an object containing metadata for the custom events.
			 *
			 * @method getEventOutputMap
			 * @param {TreeData} node
			 * @return {Object}
			 */
			getEventOutputMap: function(node) {
				var instance = this;

				return {
					tree: {
						instance: instance,
						node: node || instance
					}
				};
			},

			/**
			 * Remove the passed <code>node</code> from the current TreeData.
			 *
			 * @method removeChild
			 * @param {TreeData} node
			 */
			removeChild: function(node) {
				var instance = this;
				var output = instance.getEventOutputMap(node);

				instance.bubbleEvent('remove', output);
			},

			/**
			 * Remove the passed <code>node</code> from the current TreeData.
			 *
			 * @method _removeChild
			 * @param {TreeData} node
			 */
			_removeChild: function(event) {
				// stopActionPropagation while bubbling
				if (event.stopActionPropagation) {
					return false;
				}

				var instance = this;
				var node = event.tree.node;
				var ownerTree = instance.get(OWNER_TREE);

				if (instance.isRegistered(node)) {
					// update parent reference when removed
					node.set(PARENT_NODE, null);

					// unregister the node
					instance.unregisterNode(node);

					// no parent, no ownerTree
					node.set(OWNER_TREE, null);

					if (ownerTree) {
						// unregister the removed node from the tree index
						ownerTree.unregisterNode(node);
					}

					// remove child from the container
					node.get(BOUNDING_BOX).remove();

					var children = instance.get(CHILDREN);

					A.Array.removeItem(children, node);
					instance.set(CHILDREN, children);
				}
			},

			/**
			 * Delete all children of the current TreeData.
			 *
			 * @method empty
			 */
			empty: function() {
				var instance = this;

				instance.eachChildren(function(node) {
					var parentNode = node.get(PARENT_NODE);

					if (parentNode) {
						parentNode.removeChild(node);
					}
				});
			},

			/**
			 * Insert <code>treeNode</code> before or after the <code>refTreeNode</code>.
			 *
			 * @method insert
			 * @param {TreeNode} treeNode
			 * @param {TreeNode} refTreeNode
			 * @param {TreeNode} where 'before' or 'after'
			 */
			insert: function(treeNode, refTreeNode, where) {
				var instance = this;
				refTreeNode = refTreeNode || this;

				if (refTreeNode == treeNode) {
					return false; // NOTE: return
				}
				var refParentTreeNode = refTreeNode.get(PARENT_NODE);

				if (treeNode && refParentTreeNode) {
					var nodeBoundinBox = treeNode.get(BOUNDING_BOX);
					var refBoundinBox = refTreeNode.get(BOUNDING_BOX);
					var ownerTree = refTreeNode.get(OWNER_TREE);

					if (where == 'before') {
						refBoundinBox.placeBefore(nodeBoundinBox);
					}
					else if (where == 'after') {
						refBoundinBox.placeAfter(nodeBoundinBox);
					}

					var refSiblings = [];
					// using the YUI selector to regenerate the index based on the real dom
					// this avoid misscalculations on the nodes index number
					var DOMChildren = refParentTreeNode.get(BOUNDING_BOX).all('> ul > li');

					DOMChildren.each(function(child) {
						refSiblings.push( A.Widget.getByNode(child) );
					});

					// updating prev/nextSibling attributes
					treeNode.set(
						NEXT_SIBLING,
						A.Widget.getByNode( nodeBoundinBox.get(NEXT_SIBLING) )
					);
					treeNode.set(
						PREV_SIBLING,
						A.Widget.getByNode( nodeBoundinBox.get(PREVIOUS_SIBLING) )
					);

					// update all references
					refTreeNode.updateReferences(treeNode, refParentTreeNode, ownerTree);

					// updating refParentTreeNode childTreeNodes
					refParentTreeNode.set(CHILDREN, refSiblings);
				}

				// render treeNode after it's inserted
				treeNode.render();

				// invoking insert event
				var output = refTreeNode.getEventOutputMap(treeNode);

				output.tree.refTreeNode = refTreeNode;

				refTreeNode.bubbleEvent('insert', output);
			},

			/**
			 * Insert <code>treeNode</code> after the <code>refTreeNode</code>.
			 *
			 * @method insertAfter
			 * @param {TreeNode} treeNode
			 * @param {TreeNode} refTreeNode
			 */
			insertAfter: function(treeNode, refTreeNode) {
				refTreeNode.insert(treeNode, refTreeNode, 'after');
			},

			/**
			 * Insert <code>treeNode</code> before the <code>refTreeNode</code>.
			 *
			 * @method insertBefore
			 * @param {TreeNode} treeNode
			 * @param {TreeNode} refTreeNode
			 */
			insertBefore: function(treeNode, refTreeNode) {
				refTreeNode.insert(treeNode, refTreeNode, 'before');
			},

			/**
			 * Get a TreeNode instance by a child DOM Node.
			 *
			 * @method getNodeByChild
			 * @param {Node} child
			 * @return {TreeNode}
			 */
			getNodeByChild: function(child) {
				var instance = this;
				var treeNodeEl = child.ancestor(DOT+CSS_TREE_NODE);

				if (treeNodeEl) {
					return instance.getNodeById( treeNodeEl.attr(ID) );
				}

				return null;
			},

			/**
			 * Setter for <a href="TreeData.html#config_children">children</a>.
			 *
			 * @method _setChildren
			 * @protected
			 * @param {Array} v
			 * @return {Array}
			 */
			_setChildren: function(v) {
				var instance = this;
				var childNodes = [];

				A.Array.each(v, function(node) {
					if (node) {
						if (!isTreeNode(node) && isObject(node)) {
							// creating node from json
							node = instance.createNode(node);
						}

						// before render the node, make sure the PARENT_NODE and OWNER_TREE references are updated
						// this is required on the render phase of the TreeNode (_createNodeContainer)
						// to propagate the events callback (appendChild/expand)
						if (!isTreeNode(instance)) {
							node.set(OWNER_TREE, instance);
						}

						node.render();

						// avoid duplicated children on the childNodes list
						if (A.Array.indexOf(childNodes, node) == -1) {
							childNodes.push(node);
						}
					}
				});

				return childNodes;
			}
		}
	}
);

A.TreeData = TreeData;

}, '@VERSION@' ,{requires:['aui-base'], skinnable:false});
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});
AUI.add('aui-tree-view', function(A) {
/**
 * The TreeView Utility
 *
 * @module aui-tree
 * @submodule aui-tree-view
 */

var L = A.Lang,
	isString = L.isString,

	BOUNDING_BOX = 'boundingBox',
	CHILDREN = 'children',
	CONTAINER = 'container',
	CONTENT = 'content',
	CONTENT_BOX = 'contentBox',
	DOT = '.',
	FILE = 'file',
	HITAREA = 'hitarea',
	ICON = 'icon',
	LABEL = 'label',
	LAST_SELECTED = 'lastSelected',
	LEAF = 'leaf',
	NODE = 'node',
	OWNER_TREE = 'ownerTree',
	ROOT = 'root',
	SPACE = ' ',
	TREE = 'tree',
	TREE_VIEW = 'tree-view',
	TYPE = 'type',
	VIEW = 'view',

	concat = function() {
		return Array.prototype.slice.call(arguments).join(SPACE);
	},

	isTreeNode = function(v) {
		return ( v instanceof A.TreeNode );
	},

	getCN = A.ClassNameManager.getClassName,

	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_ROOT_CONTAINER = getCN(TREE, ROOT, CONTAINER),
	CSS_TREE_VIEW_CONTENT = getCN(TREE, VIEW, CONTENT);

/**
 * <p><img src="assets/images/aui-tree-view/main.png"/></p>
 *
 * A base class for TreeView, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 * </ul>
 *
 * Quick Example:<br/>
 *
 * <pre><code>var tree2 = new A.TreeView({
 *  	width: 200,
 *  	type: 'normal',
 *  	boundingBox: '#tree',
 *  	children: [
 *  		{ label: 'Folder 1', children: [ { label: 'file' }, { label: 'file' }, { label: 'file' } ] },
 *  		{ label: 'Folder 2', expanded: true, children: [ { label: 'file' }, { label: 'file' } ] },
 *  		{ label: 'Folder 3', children: [ { label: 'file' } ] },
 *  		{ label: 'Folder 4', expanded: true, children: [ { label: 'Folder 4-1', expanded: true, children: [ { label: 'file' } ] } ] }
 *  	]
 *  })
 *  .render();
 * </code></pre>
 *
 * Check the list of <a href="TreeView.html#configattributes">Configuration Attributes</a> available for
 * TreeView.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class TreeView
 * @constructor
 * @extends TreeData
 */
var TreeView = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property TreeView.NAME
		 * @type String
		 * @static
		 */
		NAME: TREE_VIEW,

		/**
		 * Static property used to define the default attribute
		 * configuration for the TreeView.
		 *
		 * @property TreeView.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * Type of the treeview (i.e. could be 'file' or 'normal').
			 *
			 * @attribute type
			 * @default 'file'
			 * @type String
			 */
			type: {
				value: FILE,
				validator: isString
			},

			/**
			 * Last selected TreeNode.
			 *
			 * @attribute lastSelected
			 * @default null
			 * @type TreeNode
			 */
			lastSelected: {
				value: null,
				validator: isTreeNode
			},

			/**
			 * IO metadata for loading the children using ajax.
			 *
			 * @attribute io
			 * @default null
			 * @type Object
			 */
			io: {
				value: null
			},

			paginator: {
				value: null
			}
		},

		EXTENDS: A.TreeData,

		prototype: {
			CONTENT_TEMPLATE: '<ul></ul>',

			/**
			 * Bind the events on the TreeView UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;

				instance._delegateDOM();
			},

			/**
			 * Create the DOM structure for the TreeView. Lifecycle.
			 *
			 * @method renderUI
			 * @protected
			 */
			renderUI: function() {
				var instance = this;

				instance._renderElements();
			},

			/**
			 * Sync the TreeView UI. Lifecycle.
			 *
			 * @method syncUI
			 * @protected
			 */
			syncUI: function() {
				var instance = this;

				instance.refreshIndex();
			},

			registerNode: function(node) {
				var instance = this;

				// when the node is appended to the TreeView set the OWNER_TREE
				node.set(OWNER_TREE, instance);

				TreeView.superclass.registerNode.apply(this, arguments);
			},

			/**
			 * Create TreeNode from HTML markup.
			 *
			 * @method _createFromHTMLMarkup
			 * @param {Node} container
			 * @protected
			 */
			_createFromHTMLMarkup: function(container) {
				var instance = this;

				container.all('> li').each(function(node) {
					// use firstChild as label
					var labelEl = node.one('> *').remove();
					var label = labelEl.outerHTML();

					// avoid memory leak
					docFrag = null;

					var treeNode = new A.TreeNode({
						boundingBox: node,
						label: label
					});

					var deepContainer = node.one('> ul');

					if (deepContainer) {
						// if has deepContainer it's not a leaf
						treeNode.set(LEAF, false);
						treeNode.set(CONTAINER, deepContainer);

						// render node before invoke the recursion
						treeNode.render();

						// propagating markup recursion
						instance._createFromHTMLMarkup(deepContainer);
					}
					else {
						treeNode.render();
					}

					// find the parent TreeNode...
					var parentNode = node.get(PARENT_NODE).get(PARENT_NODE);
					var parentTreeNode = A.Widget.getByNode(parentNode);

					// and simulate the appendChild.
					parentTreeNode.appendChild(treeNode);
				});
			},

			/**
			 * Render elements.
			 *
			 * @method _renderElements
			 * @protected
			 */
			_renderElements: function() {
				var instance = this;
				var contentBox = instance.get(CONTENT_BOX);
				var children = instance.get(CHILDREN);
				var type = instance.get(TYPE);
				var CSS_TREE_TYPE = getCN(TREE, type);

				contentBox.addClass(CSS_TREE_VIEW_CONTENT);

				instance.set(CONTAINER, contentBox);

				contentBox.addClass(
					concat(CSS_TREE_TYPE, CSS_TREE_ROOT_CONTAINER)
				);

				if (children.length) {
					// if has children appendChild them
					instance.eachChildren(function(node) {
						instance.appendChild(node, true);
					});
				}
				else {
					// if children not specified try to create from markup
					instance._createFromHTMLMarkup(contentBox);
				}
			},

			/**
			 * Delegate events.
			 *
			 * @method _delegateDOM
			 * @protected
			 */
			_delegateDOM: function() {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);

				// expand/collapse delegations
				boundingBox.delegate('click', A.bind(instance._onClickHitArea, instance), DOT+CSS_TREE_HITAREA);
				boundingBox.delegate('dblclick', A.bind(instance._onClickHitArea, instance), DOT+CSS_TREE_ICON);
				boundingBox.delegate('dblclick', A.bind(instance._onClickHitArea, instance), DOT+CSS_TREE_LABEL);
				// other delegations
				boundingBox.delegate('mouseenter', A.bind(instance._onMouseEnterNodeEl, instance), DOT+CSS_TREE_NODE_CONTENT);
				boundingBox.delegate('mouseleave', A.bind(instance._onMouseLeaveNodeEl, instance), DOT+CSS_TREE_NODE_CONTENT);
				boundingBox.delegate('click', A.bind(instance._onClickNodeEl, instance), DOT+CSS_TREE_NODE_CONTENT);
			},

			/**
			 * Fires on click the TreeView (i.e. set the select/unselect state).
			 *
			 * @method _onClickNodeEl
			 * @param {EventFacade} event
			 * @protected
			 */
			_onClickNodeEl: function(event) {
				var instance = this;
				var treeNode = instance.getNodeByChild( event.currentTarget );

				if (treeNode && !treeNode.isSelected()) {
					var lastSelected = instance.get(LAST_SELECTED);

					// select drag node
					if (lastSelected) {
						lastSelected.unselect();
					}

					treeNode.select();
				}
			},

			/**
			 * Fires on <code>mouseeneter</code> the TreeNode.
			 *
			 * @method _onMouseEnterNodeEl
			 * @param {EventFacade} event
			 * @protected
			 */
			_onMouseEnterNodeEl: function(event) {
				var instance = this;
				var treeNode = instance.getNodeByChild( event.currentTarget );

				if (treeNode) {
					treeNode.over();
				}
			},

			/**
			 * Fires on <code>mouseleave</code> the TreeNode.
			 *
			 * @method _onMouseLeaveNodeEl
			 * @param {EventFacade} event
			 * @protected
			 */
			_onMouseLeaveNodeEl: function(event) {
				var instance = this;
				var treeNode = instance.getNodeByChild( event.currentTarget );

				if (treeNode) {
					treeNode.out();
				}
			},

			/**
			 * Fires on <code>click</code> the TreeNode hitarea.
			 *
			 * @method _onClickHitArea
			 * @param {EventFacade} event
			 * @protected
			 */
			_onClickHitArea: function(event) {
				var instance = this;
				var treeNode = instance.getNodeByChild( event.currentTarget );

				if (treeNode) {
					treeNode.toggle();
				}
			}
		}
	}
);

A.TreeView = TreeView;

/*
* TreeViewDD - Drag & Drop
*/
var isNumber = L.isNumber,

	ABOVE = 'above',
	APPEND = 'append',
	BELOW = 'below',
	BLOCK = 'block',
	BODY = 'body',
	CLEARFIX = 'clearfix',
	DEFAULT = 'default',
	DISPLAY = 'display',
	DOWN = 'down',
	DRAG = 'drag',
	DRAGGABLE = 'draggable',
	DRAG_CURSOR = 'dragCursor',
	DRAG_NODE = 'dragNode',
	EXPANDED = 'expanded',
	HELPER = 'helper',
	INSERT = 'insert',
	OFFSET_HEIGHT = 'offsetHeight',
	PARENT_NODE = 'parentNode',
	SCROLL_DELAY = 'scrollDelay',
	STATE = 'state',
	TREE_DRAG_DROP = 'tree-drag-drop',
	UP = 'up',

	DDM = A.DD.DDM,

	CSS_HELPER_CLEARFIX = getCN(HELPER, CLEARFIX),
	CSS_ICON = getCN(ICON),
	CSS_TREE_DRAG_HELPER = getCN(TREE, DRAG, HELPER),
	CSS_TREE_DRAG_HELPER_CONTENT = getCN(TREE, DRAG, HELPER, CONTENT),
	CSS_TREE_DRAG_HELPER_LABEL = getCN(TREE, DRAG, HELPER, LABEL),
	CSS_TREE_DRAG_INSERT_ABOVE = getCN(TREE, DRAG, INSERT, ABOVE),
	CSS_TREE_DRAG_INSERT_APPEND = getCN(TREE, DRAG, INSERT, APPEND),
	CSS_TREE_DRAG_INSERT_BELOW = getCN(TREE, DRAG, INSERT, BELOW),
	CSS_TREE_DRAG_STATE_APPEND = getCN(TREE, DRAG, STATE, APPEND),
	CSS_TREE_DRAG_STATE_INSERT_ABOVE = getCN(TREE, DRAG, STATE, INSERT, ABOVE),
	CSS_TREE_DRAG_STATE_INSERT_BELOW = getCN(TREE, DRAG, STATE, INSERT, BELOW),

	HELPER_TPL = '<div class="'+CSS_TREE_DRAG_HELPER+'">'+
					'<div class="'+[CSS_TREE_DRAG_HELPER_CONTENT, CSS_HELPER_CLEARFIX].join(SPACE)+'">'+
						'<span class="'+CSS_ICON+'"></span>'+
						'<span class="'+CSS_TREE_DRAG_HELPER_LABEL+'"></span>'+
					'</div>'+
				 '</div>';

/**
 * A base class for TreeViewDD, providing:
 * <ul>
 *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *    <li>DragDrop support for the TreeNodes</li>
 * </ul>
 *
 * Quick Example:<br/>
 *
 * Check the list of <a href="TreeViewDD.html#configattributes">Configuration Attributes</a> available for
 * TreeViewDD.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class TreeViewDD
 * @constructor
 * @extends TreeView
 */
var TreeViewDD = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property TreeViewDD.NAME
		 * @type String
		 * @static
		 */
		NAME: TREE_DRAG_DROP,

		/**
		 * Static property used to define the default attribute
		 * configuration for the TreeViewDD.
		 *
		 * @property TreeViewDD.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * Dragdrop helper element.
			 *
			 * @attribute helper
			 * @default null
			 * @type Node | String
			 */
			helper: {
				value: null
			},

			/**
			 * Delay of the scroll while dragging the TreeNodes.
			 *
			 * @attribute scrollDelay
			 * @default 100
			 * @type Number
			 */
			scrollDelay: {
				value: 100,
				validator: isNumber
			}
		},

		EXTENDS: A.TreeView,

		prototype: {
			/**
			 * Direction of the drag (i.e. could be 'up' or 'down').
			 *
			 * @property direction
			 * @type String
			 * @protected
			 */
			direction: BELOW,

			/**
			 * Drop action (i.e. could be 'append', 'below' or 'above').
			 *
			 * @attribute dropAction
			 * @default null
			 * @type String
			 */
			dropAction: null,

			/**
			 * Last Y.
			 *
			 * @attribute lastY
			 * @default 0
			 * @type Number
			 */
			lastY: 0,

			node: null,

			/**
			 * Reference for the current drop node.
			 *
			 * @attribute nodeContent
			 * @default null
			 * @type Node
			 */
			nodeContent: null,

			/**
			 * Bind the events on the TreeViewDD UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;

				TreeViewDD.superclass.bindUI.apply(this, arguments);

				instance._bindDragDrop();
			},

			/**
			 * Create the DOM structure for the TreeViewDD. Lifecycle.
			 *
			 * @method renderUI
			 * @protected
			 */
			renderUI: function() {
				var instance = this;

				TreeViewDD.superclass.renderUI.apply(this, arguments);

				// creating drag helper and hiding it
				var helper = A.Node.create(HELPER_TPL).hide();

				// append helper to the body
				A.one(BODY).append(helper);

				instance.set(HELPER, helper);

				// set DRAG_CURSOR to the default arrow
				DDM.set(DRAG_CURSOR, DEFAULT);
			},

			/**
			 * Setup DragDrop on the TreeNodes.
			 *
			 * @method _createDrag
			 * @param {Node} node
			 * @protected
			 */
			_createDrag: function(node) {
				var instance = this;

				if (!instance.dragTimers) {
					instance.dragTimers = [];
				}

				if (!DDM.getDrag(node)) {
					var dragTimers = instance.dragTimers;
					// dragDelay is a incremental delay for create the drag instances
					var dragDelay = 50 * dragTimers.length;

					// wrapping the _createDrag on a setTimeout for performance reasons
					var timer = setTimeout(
						function() {
							if (!DDM.getDrag(node)) {
								// creating delayed drag instance
								var drag = new A.DD.Drag({
									bubbleTargets: instance,
									node: node,
									target: true
								})
								.plug(A.Plugin.DDProxy, {
									moveOnEnd: false,
									positionProxy: false,
									borderStyle: null
								})
								.plug(A.Plugin.DDNodeScroll, {
									scrollDelay: instance.get(SCROLL_DELAY),
									node: instance.get(BOUNDING_BOX)
								});
							}

							A.Array.removeItem(dragTimers, timer);
						},
						dragDelay
					);

					dragTimers.push(timer);
				}
			},

			/**
			 * Bind DragDrop events.
			 *
			 * @method _bindDragDrop
			 * @protected
			 */
			_bindDragDrop: function() {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);

				instance._createDragInitHandler = A.bind(
					function() {
						// set init elements as draggable
						instance.eachChildren(function(child) {
							if (child.get(DRAGGABLE)) {
								instance._createDrag( child.get(CONTENT_BOX) );
							}
						}, true);

						boundingBox.detach('mouseover', instance._createDragInitHandler);
					},
					instance
				);

				// only create the drag on the init elements if the user mouseover the boundingBox for init performance reasons
				boundingBox.on('mouseover', instance._createDragInitHandler);

				// when append new nodes, make them draggable
				instance.after('insert', A.bind(instance._afterAppend, instance));
				instance.after('append', A.bind(instance._afterAppend, instance));

				// drag & drop listeners
				instance.on('drag:align', instance._onDragAlign);
				instance.on('drag:start', instance._onDragStart);
				instance.on('drop:exit', instance._onDropExit);
				instance.on('drop:hit', instance._onDropHit);
				instance.on('drop:over', instance._onDropOver);
			},

			/**
			 * Set the append CSS state on the passed <code>nodeContent</code>.
			 *
			 * @method _appendState
			 * @param {Node} nodeContent
			 * @protected
			 */
			_appendState: function(nodeContent) {
				var instance = this;

				instance.dropAction = APPEND;

				instance.get(HELPER).addClass(CSS_TREE_DRAG_STATE_APPEND);

				nodeContent.addClass(CSS_TREE_DRAG_INSERT_APPEND);
			},

			/**
			 * Set the going down CSS state on the passed <code>nodeContent</code>.
			 *
			 * @method _goingDownState
			 * @param {Node} nodeContent
			 * @protected
			 */
			_goingDownState: function(nodeContent) {
				var instance = this;

				instance.dropAction = BELOW;

				instance.get(HELPER).addClass(CSS_TREE_DRAG_STATE_INSERT_BELOW);

				nodeContent.addClass(CSS_TREE_DRAG_INSERT_BELOW);
			},

			/**
			 * Set the going up CSS state on the passed <code>nodeContent</code>.
			 *
			 * @method _goingUpState
			 * @param {Node} nodeContent
			 * @protected
			 */
			_goingUpState: function(nodeContent) {
				var instance = this;

				instance.dropAction = ABOVE;

				instance.get(HELPER).addClass(CSS_TREE_DRAG_STATE_INSERT_ABOVE);

				nodeContent.addClass(CSS_TREE_DRAG_INSERT_ABOVE);
			},

			/**
			 * Set the reset CSS state on the passed <code>nodeContent</code>.
			 *
			 * @method _resetState
			 * @param {Node} nodeContent
			 * @protected
			 */
			_resetState: function(nodeContent) {
				var instance = this;
				var helper = instance.get(HELPER);

				helper.removeClass(CSS_TREE_DRAG_STATE_APPEND);
				helper.removeClass(CSS_TREE_DRAG_STATE_INSERT_ABOVE);
				helper.removeClass(CSS_TREE_DRAG_STATE_INSERT_BELOW);

				if (nodeContent) {
					nodeContent.removeClass(CSS_TREE_DRAG_INSERT_ABOVE);
					nodeContent.removeClass(CSS_TREE_DRAG_INSERT_APPEND);
					nodeContent.removeClass(CSS_TREE_DRAG_INSERT_BELOW);
				}
			},

			/**
			 * Update the CSS node state (i.e. going down, going up, append etc).
			 *
			 * @method _updateNodeState
			 * @param {EventFacade} event
			 * @protected
			 */
			_updateNodeState: function(event) {
				var instance = this;
				var drag = event.drag;
				var drop = event.drop;
				var nodeContent = drop.get(NODE);
				var dropNode = nodeContent.get(PARENT_NODE);
				var dragNode = drag.get(NODE).get(PARENT_NODE);
				var dropTreeNode = A.Widget.getByNode(dropNode);

				// reset the classNames from the last nodeContent
				instance._resetState(instance.nodeContent);

				// cannot drop the dragged element into any of its children
				// using DOM contains method for performance reason
				if ( !dragNode.contains(dropNode) ) {
					// nArea splits the height in 3 areas top/center/bottom
					// these areas are responsible for defining the state when the mouse is over any of them
					var nArea = nodeContent.get(OFFSET_HEIGHT) / 3;
					var yTop = nodeContent.getY();
					var yCenter = yTop + nArea*1;
					var yBottom = yTop + nArea*2;
					var mouseY = drag.mouseXY[1];

					// UP: mouse on the top area of the node
					if ((mouseY > yTop) && (mouseY < yCenter)) {
						instance._goingUpState(nodeContent);
					}
					// DOWN: mouse on the bottom area of the node
					else if (mouseY > yBottom) {
						instance._goingDownState(nodeContent);
					}
					// APPEND: mouse on the center area of the node
					else if ((mouseY > yCenter) && (mouseY < yBottom)) {
						// if it's a folder set the state to append
						if (dropTreeNode && !dropTreeNode.isLeaf()) {
							instance._appendState(nodeContent);
						}
						// if it's a leaf we need to set the ABOVE or BELOW state instead of append
						else {
							if (instance.direction == UP) {
								instance._goingUpState(nodeContent);
							}
							else {
								instance._goingDownState(nodeContent);
							}
						}
					}
				}

				instance.nodeContent = nodeContent;
			},

			/**
			 * Fires after the append event.
			 *
			 * @method _handleEvent
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_afterAppend: function(event) {
				var instance = this;
				var treeNode = event.tree.node;

				if (treeNode.get(DRAGGABLE)) {
					instance._createDrag( treeNode.get(CONTENT_BOX) );
				}
			},

			/**
			 * Fires on drag align event.
			 *
			 * @method _onDragAlign
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_onDragAlign: function(event) {
				var instance = this;
				var lastY = instance.lastY;
				var y = event.target.lastXY[1];

				// if the y change
				if (y != lastY) {
					// set the drag direction
					instance.direction = (y < lastY) ? UP : DOWN;
				}

				instance.lastY = y;
			},

			/**
			 * Fires on drag start event.
			 *
			 * @method _onDragStart
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_onDragStart: function(event) {
				var instance = this;
				var drag = event.target;
				var dragNode = drag.get(NODE).get(PARENT_NODE);
				var dragTreeNode = A.Widget.getByNode(dragNode);
				var lastSelected = instance.get(LAST_SELECTED);

				// select drag node
				if (lastSelected) {
					lastSelected.unselect();
				}

				dragTreeNode.select();

				// initialize drag helper
				var helper = instance.get(HELPER);
				var helperLabel = helper.one(DOT+CSS_TREE_DRAG_HELPER_LABEL);

				// show helper, we need display block here, yui dd hide it with display none
				helper.setStyle(DISPLAY, BLOCK).show();

				// set the CSS_TREE_DRAG_HELPER_LABEL html with the label of the dragged node
				helperLabel.html( dragTreeNode.get(LABEL) );

				// update the DRAG_NODE with the new helper
				drag.set(DRAG_NODE, helper);
			},

			/**
			 * Fires on drop over event.
			 *
			 * @method _onDropOver
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_onDropOver: function(event) {
				var instance = this;

				instance._updateNodeState(event);
			},

			/**
			 * Fires on drop hit event.
			 *
			 * @method _onDropHit
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_onDropHit: function(event) {
				var instance = this;
				var dropAction = instance.dropAction;
				var dragNode = event.drag.get(NODE).get(PARENT_NODE);
				var dropNode = event.drop.get(NODE).get(PARENT_NODE);

				var dropTreeNode = A.Widget.getByNode(dropNode);
				var dragTreeNode = A.Widget.getByNode(dragNode);

				var output = instance.getEventOutputMap(instance);

				output.tree.dropNode = dropTreeNode;
				output.tree.dragNode = dragTreeNode;

				if (dropAction == ABOVE) {
					dropTreeNode.insertBefore(dragTreeNode);

					instance.bubbleEvent('dropInsert', output);
				}
				else if (dropAction == BELOW) {
					dropTreeNode.insertAfter(dragTreeNode);

					instance.bubbleEvent('dropInsert', output);
				}
				else if (dropAction == APPEND) {
					if (dropTreeNode && !dropTreeNode.isLeaf()) {
						dropTreeNode.appendChild(dragTreeNode);

						if (!dropTreeNode.get(EXPANDED)) {
							// expand node when drop a child on it
							dropTreeNode.expand();
						}

						instance.bubbleEvent('dropAppend', output);
					}
				}

				instance._resetState(instance.nodeContent);

				// bubbling drop event
				instance.bubbleEvent('drop', output);

				instance.dropAction = null;
			},

			/**
			 * Fires on drop exit event.
			 *
			 * @method _onDropExit
			 * @param {EventFacade} event append event facade
			 * @protected
			 */
			_onDropExit: function() {
				var instance = this;

				instance.dropAction = null;

				instance._resetState(instance.nodeContent);
			}
		}
	}
);

A.TreeViewDD = TreeViewDD;

}, '@VERSION@' ,{skinnable:true, requires:['aui-tree-node','dd-drag','dd-drop','dd-proxy']});


AUI.add('aui-tree', function(A){}, '@VERSION@' ,{use:['aui-tree-data', 'aui-tree-node', 'aui-tree-view'], skinnable:true});