/**

* @version		$Id: tree.js 81 2009-06-05 16:31:31Z happynoodleboy $

* @package      JCE

* @copyright    Copyright (C) 2005 - 2009 Ryan Demmer. All rights reserved.

* @author		Ryan Demmer

* @license      GNU/GPL

* JCE is free software. This version may have been modified pursuant

* to the GNU General Public License, and as distributed it includes or

* is derivative of works licensed under the GNU General Public License or

* other free or open source software licenses.

*/



/*

Class: Tree



Note:

	The Sortables require an XHTML doctype.



Arguments:

	container 	- the container element

	options 	- an Object, see options below.



Options:

	rootName		- the root name. Defaults to 'Root'

	rootClass		- the root node class. Defaults to 'root'

	collapseTree	- collapse all other tree nodes on the same level when one is opened. Defaults to false.

	useLink			- Use the link href

Events:

	onNodeToggle 	- function executed when a node is toggled

	onNodeClick 	- function executed when a node is clicked

	onNodeLoad 		- function executed when a node is loaded

	onNodeCreate 	- function executed when a node is created

*/

var Tree = new Class({

	getOptions : function(){

		return {

			rootName: 'Root',

			rootClass: 'root',

			loaderClass: 'load',

			collapseTree: false,

			charLength: false,

			onInit: Class.empty,

			onNodeClick: Class.empty,

			onNodeLoad: Class.empty,

			onNodeCreate: Class.empty

		};

	},	

	initialize : function(container, options){

		this.setOptions(this.getOptions(), options);

		this.container = $(container);



		this.fireEvent('onInit', function(){

			this.nodeEvents();		

		}.bind(this));

		

		return this;

	},

	nodeEvents : function(parent){

		if(!parent){

			parent = this.container;	

		}

		

		$ES('div.tree-row', parent).each(function(el){

			el.addEvent('mouseover', function(){

				el.addClass('hover');							  

			})

			el.addEvent('mouseout', function(){

				el.removeClass('hover');							  

			})							 

		});

		

		$ES('div.tree-image', parent).each(function(el){

			var p = this.findParent(el);

			el.addEvent('click', function(e){

				this.toggleNode(e, p);							  

			}.bind(this))

			if(this.getNode(p)){

				el.addClass('open');	

			}

		}.bind(this));

		

		$ES('span', this.container).each(function(el){

			if(window.ie){

				el.onselectstart = function(){

					return false;

				}

			}

			if(window.gecko){

				el.setStyle('moz-user-select', 'none');

			}

			if(this.getNode(el.getParent())){

				el.addClass('open');	

			}

		}.bind(this));

		

		$ES('a', this.container).each(function(el){

			var p = this.findParent(el);

			el.addEvent('click', function(e){

				this.fireEvent('onNodeClick', [e, p]);

			}.bind(this))

		}.bind(this));

	},

	/**

	 * Does a parent (ul) have childnodes

	 * @param {String} The parent

	 * @return {Boolean}.

	*/

	hasNodes : function(parent){

		if($type(parent) == 'string'){

			parent = this.findParent(parent);	

		}

		var c = parent.childNodes;

		return c.length > 1 || (c.length == 1 && c[0].className != 'spacer');

	},

	/**

	 * Does the node exist?

	 * @param {String} The node title

	 * @param {String or Element} The parent node

	 * @return {Boolean}.

	*/

	isNode : function(id, parent){

		return this.findNode(id, parent) ? true : false;

	},

	/**

	 * Does a parent have subnodes?

	 * @param {String or Element} The parent node

	 * @return {Boolean}.

	*/

	getNode : function(parent){

		if($type(parent) == 'string'){

			parent = this.findParent(parent);	

		}

		return $E('ul.tree-node', parent);

	},

	/**

	 * Reset all nodes. Set to closed

	*/

	resetNodes : function(){

		$ES('span', this.container).each(function(el){

			el.removeClass('open');	

		});

		$ES('div.tree-image', this.container).each(function(el){

			el.removeClass('open');										  

		});

	},

	/**

	 * Rename a node

	 * @param {String} The node title

	 * @param {String} The new title

	*/

	renameNode : function(id, name){

		var parent 	= string.dirname(id);

		var node 	= this.findNode(id, parent);

		// Rename the node

		node.setProperty('id', name);

		// Rename the span

		$E('a', node).setHTML(string.basename(name));

		// Rename each of the child nodes

		$ES('li[id^='+ this._escape(encodeURI(id)) +']', node).each(function(n){

			var nt = n.getProperty('id');

			n.setProperty('id', nt.replace(id, name));

		});

	},

	/**

	 * Remove a node

	 * @param {String} The node title

	*/

	removeNode : function(id){

		var parent 	= string.dirname(id);

		var node 	= this.findNode(id, parent);

		var ul		= node.getParent();

		// Remove the node

		node.remove();

		// Remove it if it is now empty

		if(ul && !this.hasNodes(ul)){

			ul.remove();	

		}

	},

	/**

	 * Create a node <ul></ul>

	 * @param {String or Element} The parent node

	 * @return {Array} An array of nodes to create.

	*/

	createNode : function(nodes, parent){				

		var e, p, h, l, np, i;

		// If parent is not an element, find the parent element

		if(!parent){

			parent = string.dirname(nodes[0].id);	

		}

		if($type(parent) == 'string'){

			parent = this.findParent(parent);	

		}

		/* Create the nodes from the array

		 * <li><div class="tree-row"><div class="tree-image"></div><span><a>node</a></span><div></li>

		*/

		if(nodes && nodes.length){

			// Get parent ul

			var ul = $E('ul.tree-node', parent);

			// Create it if it doesn't exist

			if(!ul){

				ul = new Element('ul').addClass('tree-node').adopt(

					new Element('li').addClass('spacer')

				).injectInside(parent);	

			}

			

			// Iterate through nodes array

			nodes.each(function(node){

				if(!this.isNode(node.id, parent)){

					// Set default

					if(!node['class']){

						node['class'] = 'folder';

					}

					// title and link html

					t = node.name || node.id;

					// decode

					t = string.decode(t);

					h = t;

					l = this.options.charLength;

					// shorten

					if(l){

						if(h.length > l){

							h = h.substring(0, l) + '...';	

						}

					}

					ul.adopt(

						new Element('li', {

							'id': this._escape(encodeURI(node.id))

						}).adopt(

							new Element('div', {

								'class': 'tree-row',

								events: {

									mouseover: function(){

										this.addClass('hover');

									},

									mouseout: function(){

										this.removeClass('hover');

									}

								}			

							}).adopt(

								new Element('div', {

									'class': node['class'].contains('folder') ? 'tree-image' : 'tree-noimage',

									events: {

										'click': function(e){

											e = new Event(e);

											p = this.findParent(e.target);

											this.toggleNode(e, p);

										}.bind(this)

									}

								})

							).adopt(

								new Element('span', {

									'class': node['class']		

								}).adopt(

									new Element('a', {

										'href': !node.url ? 'javascript:;' : node.url,

										'tabindex': '1',

										'title': t,

										events : {

											'click': function(e){

												e = new Event(e);

												p = this.findParent(e.target);

												this.fireEvent('onNodeClick', [e, p]);

												e.stop();

											}.bind(this)

										}

									}).setHTML(h)

								)

							)

						)

					)

					this.toggleNodeState(parent, 1);

					this.fireEvent('onNodeCreate');

				}else{

					// Node exists, set as open

					this.toggleNodeState(parent, 1);	

				}

			}.bind(this))

		}else{

			// No new nodes, set as open

			this.toggleNodeState(parent, 1);	

		}

	},

	/**

	 * Find the parent node

	 * @param {String} The child node id

	 * @return {Element} The parent node.

	*/

	findParent : function(el){

		if($type(el) == 'element'){

			n = el.parentNode;

			while (n) {

				if(n.nodeName == 'LI'){

					return n;

				}

				n = n.parentNode;

			}

		}else{

			return $E('li[id='+ this._escape(encodeURI(el)) +']', this.container);

		}

	},

	/**

	 * Find a node by id

	 * @param {String} The child node title

	 * @param {String / Element} The parent node

	 * @return {Element} The node.

	*/

	findNode : function(id, parent){

		if(!parent || parent == '/'){

			parent = this.container;

		}

		if($type(parent) == 'string'){

			parent = this.findParent(parent);	

		}

		return $E('li[id='+ this._escape(encodeURI(id)) +']', parent) || false;

	},

	/**

	 * Toggle the loader class on the node span element

	 * @param {Element} The target node

	*/

	toggleLoader : function(node){

		var span = $E('span', node), cls = this.options.loaderClass;

				

		if(!span.hasClass(cls)){

			span.$tmp 		= span.className || '';

			span.className 	= cls;

		}else{

			span.removeClass(cls).addClass(span.$tmp);

			span.$tmp = null;

		}

	},

	/**

	 * Collapse all tree nodes except one excluded

	 * @param {Element} The excluded node

	*/

	collapseNodes : function(ex){

		if(!ex) this.resetNodes();		

		var parent = ex.getParent();



		$ES('li', parent).each(function(el){

			if(el != ex){

				if(el.getParent() == parent){

					this.toggleNodeState(el, 0);

					var child = this.getNode(el);

					if(child){

						child.addClass('hide');	

					}

				}

			}

		}.bind(this));

	},

	/**

	 * Toggle a node's state, open or closed

	 * @param {Element} The node

	*/

	toggleNodeState : function(node, state){

		var span 	= $E('span', node);

		var div 	= $E('div.tree-image', node);

		

		[span, div].each(function(el){

			if(el){

				if(state == 1){

					el.addClass('open');

				}else if(state == 0){

					el.removeClass('open');

				}else{

					el.toggleClass('open');

				}	

			}

		});

		if (state == 1) {

			if(node.id == '/'){

				return;

			}

			var c = $E('ul.tree-node', node);

			if (c) {

				if (span.hasClass('open')) {

					c.removeClass('hide');

				}

				else {

					c.addClass('hide');

				}

			}	

		}

	},

	/**

	 * Toggle a node

	 * @param {Element} The node

	*/

	toggleNode : function(e, node){		

		e = new Event(e);

		// Force reload

		if(e.shift){

			return this.fireEvent('onNodeLoad', node);	

		}

		var child = this.getNode(node);

		// No children load or close

		if(!child){

			if($E('div.tree-image', node).hasClass('open')){

				this.toggleNodeState(node);

			}else{

				this.fireEvent('onNodeLoad', node);

			}

		// Hide children, toggle node

		}else{

			child.toggleClass('hide');

			this.toggleNodeState(node);	

		}

		// Collpase the all other tree nodes

		if(this.options.collapseTree){

			this.collapseNodes(node);

		}

	},

	/**

	 * Private function

	 * Escape a string

	 * @param {String} The string

	 * @return {String} The escaped string

	*/

	_escape : function(s){

		return s.replace(/'/, '%27');

	}

});

Tree.implement(new Events, new Options);