/*______________
|       ______  |   U I Z E     J A V A S C R I P T     A P I
|     /      /  |   -----------------------------------------
|    /    O /   |    MODULE : Uize.Widget.Tree Class (version 1.1.0)
|   /    / /    |    AUTHOR : Chris van Rensburg (http://www.tomkidding.com)
|  /    / /  /| |    ONLINE : http://www.tomkidding.com/uize/uize-js-api
| /____/ /__/_| | COPYRIGHT : (c)2003-2008 UIZE
|          /___ |   LICENSE : Distributed under the terms of the GNU General Public License
|_______________|             http://www.gnu.org/licenses/gpl.txt
*/

/*
	DESCRIPTION
		Implements a base class for collapsible/expandable tree menus of various flavors

	REQUIRES
		- Uize.Widget.js (base class)
		- Uize.Node.js
*/

/*ScruncherSettings Mappings="=c" LineCompacting="TRUE"*/

Uize.module ({
	name:'Uize.Widget.Tree',
	required:'Uize.Node',
	builder:function (_superclass) {
		var
			_undefined,
			_false = false,
			_Uize_Node = Uize.Node
		;

		/*** Object Constructor ***/
			var
				_class = _superclass.subclass (),
				_classPrototype = _class.prototype
			;

		/*** Public Instance-static Methods ***/
			_classPrototype.getTreeFromList = _class.getTreeFromList = function (_node) {
				function _getText (_node) {
					var _text = '';
					if (_node) {
						if (_node.nodeType == 3) _text = _node.data;
						if (_node.childNodes) {
							var _childNodes = _node.childNodes;
							for (var _childNodeNo = 0; _childNodeNo < _childNodes.length; _childNodeNo++) {
								var _childNode = _childNodes [_childNodeNo];
								if (_childNode.tagName == 'UL' || _childNode.tagName == 'OL') {
									break;
								} else {
									_text += _getText (_childNode);
								}
							}
						}
					}
					return _text;
				}
				function _getItemFromListItem (_node) {
					var _item = null;
					if (_node) {
						_item = {
							title:_getText (_node),
							link:'',
							items:[]
						};
						var _childNodes = _node.childNodes;
						for (_nodeNo = 0; _nodeNo < _childNodes.length; _nodeNo++) {
							_node = _childNodes [_nodeNo];
							if (_node.tagName == 'UL' || _node.tagName == 'OL') {
								_item.expanded = _node.style.display != 'none';
								var _childNodes = _node.childNodes;
								for (var _childNodeNo = 0; _childNodeNo < _childNodes.length; _childNodeNo++) {
									var _childNode = _childNodes [_childNodeNo];
									if (_childNode.tagName == 'LI')
										_item.items.push (_getItemFromListItem (_childNode))
									;
								}
							} else if (_node.tagName == 'A') {
								_item.link = _node.getAttribute ('href');
							}
						}
					}
					return _item;
				}
				var _item = _getItemFromListItem (_Uize_Node.getById (_node));
				return _item.title.search (/\S/) > -1 ? [_item] : _item.items;
			};

			_classPrototype.getTreeFromPage = _class.getTreeFromPage = function  (_levelClasses,_initialExpandedDepth) {
				var
					_nodes = document.all || document.getElementsByTagName ('*'),
					_nodesLength = _nodes.length,
					_levelClassMap = {},
					_levelClassRegExp,
					_levelClassRegExpChunks = [],
					_tree = {
						title:'Contents',
						link:''
					},
					_currentLevelNo = 0,
					_treeLevels = [_tree],
					_itemSpecifier = [],
					_currentLevel = _tree,
					_linkPrefix = this._objectName ? this._objectName : 'Uize_Widget_TreeMenu',
					_anchor,
					_documentUrl = location.href,
					_urlAnchorPos = _documentUrl.lastIndexOf ('#')
				;
				if (_urlAnchorPos > -1) _documentUrl = _documentUrl.substring (0,_urlAnchorPos);
				if (typeof _initialExpandedDepth != 'number') _initialExpandedDepth = 1;
				for (var _levelClassNo = 0; _levelClassNo < _levelClasses.length; _levelClassNo++) {
					var _levelClass = _levelClasses [_levelClassNo];
					_levelClassMap [_levelClass] = _levelClassNo;
					_levelClassRegExpChunks.push ('\\b' + _levelClass + '\\b');
				}
				_levelClassRegExp = new RegExp (_levelClassRegExpChunks.join ('|'));
				for (var _nodeNo = 0; _nodeNo < _nodesLength; _nodeNo++) {
					var
						_node = _nodes [_nodeNo],
						_nodeClassName = _node.className
					;
					if (_nodeClassName) {
						var _levelClassMatch = _nodeClassName.match (_levelClassRegExp);
						if (_levelClassMatch) {
							var _newLevelNo = _levelClassMap [_levelClassMatch [0]];
							if (_newLevelNo > _currentLevelNo) {
								_treeLevels [_newLevelNo] = _currentLevel.items [_currentLevel.items.length - 1];
								_currentLevelNo = _newLevelNo;
								_currentLevel = _treeLevels [_currentLevelNo];
							} else if (_newLevelNo < _currentLevelNo) {
								_currentLevelNo = _newLevelNo;
								_currentLevel = _treeLevels [_currentLevelNo];
							}
							_itemSpecifier.length = _currentLevelNo;
							if (!_currentLevel.items) {
								_currentLevel.items = [];
								_currentLevel.expanded = _currentLevelNo < _initialExpandedDepth;
							}
							_itemSpecifier.push (_currentLevel.items.length);
							_anchor = _linkPrefix + '_' + _itemSpecifier.join ('_');
							_currentLevel.items.push ({
								title:_Uize_Node.getText (_node),
								link:_documentUrl + '#' + _anchor
							});
							_Uize_Node.injectHtml (_node,'<a name="' + _anchor + '"></a>','outer top');
							/* NOTE: offset to compensate for added anchor node */
								_nodesLength++;
								_nodeNo++;
						}
					}
				}
				return [_tree];
			};

		/*** Public Instance Methods ***/
			_classPrototype.getItemFromSpecifier = function (_itemSpecifier) {
				var
					_this = this,
					_item,
					_items = _this._items,
					_itemSpecifierLevels = _itemSpecifier.split (_this._itemDelimiter),
					_itemSpecifierLevelsLength = _itemSpecifierLevels.length
				;
				for (var _levelNo = 0; _levelNo < _itemSpecifierLevelsLength; _levelNo++) {
					_item = _items [_itemSpecifierLevels [_levelNo]];
					_items = _item.items;
				}
				return _item;
			};

			_classPrototype.setExpandedDepth = function (_expandedDepth,_itemSpecifier) {
				var _this = this;
				_this.traverseTree ({
					itemHandler:
						function (_item,_itemSpecifier,_depth) {
							_this.setItemExpanded (_itemSpecifier,_depth < _expandedDepth);
						},
					itemSpecifier:_itemSpecifier
				});
			};

			_classPrototype.setItemExpanded = function (_itemSpecifier,_expanded) {
				/* NOTE:
					- override the implementation of this method in a subclass
				*/
			};

			_classPrototype.collapseAllBut = function (_expandedItemSpecifier) {
				var
					_this = this,
					_itemDelimiter = _this._itemDelimiter
				;
				_this.traverseTree ({
					itemHandler:
						function (_item,_itemSpecifier) {
							_this.setItemExpanded (
								_itemSpecifier,
								(_expandedItemSpecifier + _itemDelimiter).indexOf (_itemSpecifier + _itemDelimiter) == 0
							);
						}
				});
			};

			_classPrototype.traverseTree = function (_params) {
				var
					_this = this,
					_itemDelimiter = _this._itemDelimiter,
					_itemSpecifier = _params.itemSpecifier,
					_doNothing = function () {},
					_itemHandler = _params.itemHandler || _doNothing,
					_beforeSubItemsHandler = _params.beforeSubItemsHandler || _doNothing,
					_afterSubItemsHandler = _params.afterSubItemsHandler || _doNothing
				;
				function _traverseItem (_item,_itemSpecifier,_depth) {
					_itemHandler (_item,_itemSpecifier,_depth);
					var _itemItems = _item.items;
					if (_itemItems && _itemItems.length) {
						_beforeSubItemsHandler (_item,_itemSpecifier,_depth);
						_traverseItems (_itemItems,_itemSpecifier + _itemDelimiter,_depth + 1);
						_afterSubItemsHandler (_item,_itemSpecifier,_depth);
					}
				}
				function _traverseItems (_items,_itemSpecifierPrefix,_depth) {
					var _itemsLength = _items.length;
					for (var _itemNo = 0; _itemNo < _itemsLength; _itemNo++)
						_traverseItem (_items [_itemNo],_itemSpecifierPrefix + _itemNo,_depth)
					;
				}
				_itemSpecifier
					? _traverseItem (_this.getItemFromSpecifier (_itemSpecifier),_itemSpecifier,0)
					: _traverseItems (_this._items,'',0)
				;
			};

		/*** Setup Properties ***/
			_class.registerProperties ({
				_itemDelimiter:{
					name:'itemDelimiter',
					value:'x'
				},
				_items:{
					name:'items',
					value:[]
				},
				_value:{
					name:'value',
					value:[]
				}
			});

		return _class;
	}
});

