adobe.link("lib/animator.js");
/*-------------------------------------------------------------------------------

	Class: Tree
	A list view where a branch, ie. a nested list, can be shown or hidden by the control above it. Any number of branches may be shown without effecting the state of other branches. A Tree does not manage the overall dimensions of itself like an <Accordion> would.
	
	Author:
	btapley
	
	Notes: 
	Incomplete keystroke support.
	
-------------------------------------------------------------------------------*/

adobe.Tree = Class.create({
/*-------------------------------------------------------------------------------

	Method: initialize
	
	Parameters:
	division - node
	options - hash
	labels - hash
	
	Options:
	crossfadeButtonStyle - boolean, default detects browser support
	crossfadePanelStyle - boolean, default detects browser support
	select_button - string, default is "dt"
	select_panel - string, stub for a custom getBranch call
	style_button_opened - string, default is "tree-button-opened"
	style_button_closed - string, default is "tree-button-closed"
	style_panel_opened - string, default is "tree-panel-opened"
	style_panel_closed - string, default is "tree-panel-closed"
	style_uninitialized - string
	style_initialized - string
	transition - transition class, default is Animator.tx.easeIn
	duration - integer, default is 400
	getBranch - function, default returns adjacent elements to select_button
	show - array containing integers
	
	Returned Value:
	Class instance

	Example:
>	new adobe.Tree($("foo"))

-------------------------------------------------------------------------------*/
	initialize: function(division, options, labels) {
		/* Each branch object is identified by index */
		
		this.branch_total = 0; 
		
		/* States passed to Animator */
		
		this.states = {
			OPENED: 1,
			CLOSED: 0
		};
		
		/* Set options */
		
		this.options = Object.extend({
			transition: Animator.tx.easeIn,
			duration: 400,
			getBranch: (function (element) {
				var result=[];
				while(!!(element = element.next()) && 
					!element.match(this.options.select_button)) { 
					 result.push(element);	
				}
				return result;
			}).bind(this),
			show: [],
			select_button: "dt",
			select_panel: "",
			// Opera is very slow to load Animator CSS Subjects
			crossfadeButtonStyle: !window.opera,
			crossfadePanelStyle: !window.opera,
			style_button_opened: "tree-button-opened",
			style_button_closed: "tree-button-closed",
			style_panel:"tree-panel",			
			style_panel_opened: "tree-panel-opened",
			style_panel_closed: "tree-panel-closed",
			style_uninitialized: "dyn-treelist",
			style_initialized: "treelist",
			remember: true,
			auto_open : false,
			fade_fx_content : false,
			vspace: 0
		}, options);
		
		/* Set labels */
		
		this.labels = Object.extend({
			toggle_all: "Alt+click to show/hide all"
		}, labels);
		
		/* Set branch nodes */
		this.branches = [];
		
		
		var selector = this.options.select_button,
		controls = division.childElements().grep(new Selector(selector)),
		contents = controls.collect(this.options.getBranch);
		
		controls.zip(contents).collect(this.createBranch.bind(this));
		
		this.branches.invoke("setLabel", this.labels.toggle_all);
		
		/* Define container to fire custom events */
		
		this.container = $(division);
		
		if(this.options.auto_open) {
			var branchopen = this.openHereBranch.bind(this);
			var delayedopen = branchopen.delay(0.5);
		}
		
		/* Optional open branches */
		
		this.options.show.collect(this.openBranch.bind(this));
		
		/* Optional ready state styles */
		
		if(this.options.style_initialized) {
			this.container.addClassName(this.options.style_initialized);
		}
			
		if(this.options.style_uninitialized && 
		   this.container.hasClassName(this.options.style_uninitialized)) {
			this.container.removeClassName(this.options.style_uninitialized);
		}
	},
/*-------------------------------------------------------------------------------

	Method: openBranch
	
	Parmeters:
	oid - integer
	deep (not implemented) - boolean
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	openBranch: function(oid, deep) {
		if(!this.branches[oid]) return;	// added for Bug 107064
		this.branches[oid].setView(this.states.OPENED);
	},	
	
	openHereBranch: function() {
		if(this.container.hasClassName('treenav')) {			
			$$("."+this.options.style_panel).each(function(panel,index){
				if(!$$("."+this.options.style_panel)[0].hasClassName("tree-panel-closed")) {
					index = index -1;	// if first panel doesn't have option to open or close
				}
				panel.select("a").find(function(link){
					if(link.hasClassName('here') || link.href == window.location) {
						this.openBranch(index);
					}
				}.bind(this))
			}.bind(this));
		}
	},
/*-------------------------------------------------------------------------------

	Method: closeBranch
	
	Parmeters:
	deep (not implemented) - boolean
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	closeBranch: function(deep) {
		this.branches[oid].setView(this.states.CLOSED);
	},
/*-------------------------------------------------------------------------------

	Method: toggleAllBranches
	
	Parmeters:
	deep (not implemented) - boolean
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	toggleAllBranches: function(deep) {
		if(this.branches.invoke("isOpened").any()) {
			this.closeAllBranches(deep);
		} else {
			this.openAllBranches(deep);
		}
	},
/*-------------------------------------------------------------------------------

	Method: closeAllBranches
	
	Parmeters:
	deep (not implemented) - boolean
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	closeAllBranches: function(deep) {
		this.branches.invoke("setView", this.states.CLOSED);
		this.fireCloseAllEvent.bind(this).delay(0.5);
			
	},
	fireCloseAllEvent : function() {
		this.container.fire("tree:closeAllBranches");
	},
/*-------------------------------------------------------------------------------

	Method: openAllBranches
	
	Parmeters:
	deep (not implemented) - boolean
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	openAllBranches: function(deep) {
		this.branches.invoke("setView", this.states.OPENED);
		this.fireOpenAllEvent.bind(this).delay(0.5);
			
	},
	fireOpenAllEvent : function() {
		this.container.fire("tree:openAllBranches");
	},
/*-------------------------------------------------------------------------------

	Method: createBranch
	
	Parmeters:
	data - array
	
	Returned Value:
	<TreeBranch> instance
	
-------------------------------------------------------------------------------*/
	createBranch: function(data) {
		var button = data[0];
		var panel = data[1];
		
		var button_styles = [this.options.style_button_closed, this.options.style_button_opened];
		
		var panel_styles = [this.options.style_panel_closed, this.options.style_panel_opened];
		
		var branch = new adobe.TreeBranch(this.branch_total++, 
					button, 
					panel, 
					{
						opened: button.hasClassName(button_styles[this.states.OPENED]),
						button_styles: button_styles,
						panel_styles: panel_styles,
						fade_fx_content : this.options.fade_fx_content,
						duration: this.options.duration,
						transition: this.options.transition
					});
		

		var handleBoundClick = handleButtonEvent.bindAsEventListener(this);
		
		branch.enableKeyAccess();
		
		branch.observe("click", handleBoundClick);
		branch.observe("keydown", handleKeyEvent);
		
		this.branches.push(branch);
		
		
		function handleButtonEvent(event) {
			s_pageName = document.URL.toLowerCase();
			if(event.altKey) {
				this.toggleAllBranches();
				if ((!this.branches.invoke("isOpened").any()) && (button.parentNode.hasClassName("omnitrack"))) {
					s_pageName = s_pageName + ": " + button.parentNode.id + " toggled";
					setTimeout("sendAnalyticsEvent();",500);
				}
			} else {
				branch.toggleView.call(branch);
				if ((!branch.opened) && (button.parentNode.hasClassName("omnitrack"))) {
					s_pageName = s_pageName + ": " + button.id;
					setTimeout("sendAnalyticsEvent();",500);
				}
			}
		}
		
		function handleKeyEvent(event) {
			if(event.keyCode == Event.KEY_RETURN) {
				handleBoundClick(event);
			}
		}
		
	}
});

/*-------------------------------------------------------------------------------

	Class: TreeBranch
	A wrapper for nested lists with a <Tree>
	
	Author:
	btapley
	
-------------------------------------------------------------------------------*/
adobe.TreeBranch = Class.create({
/*-------------------------------------------------------------------------------

	Method: initialize
	
	Parameters:
	oid - number
	control - node
	content - node
	options - object
	
	Returned Value:
	Class instance
	
	Author:
	btapley
	
-------------------------------------------------------------------------------*/
	initialize: function(oid, control, content, options) {
		this.control = control;
		this.contents = content;
		
		this.options = Object.extend({
			opened: false,
			button_styles: ["tree-button-opened", "tree-button-closed"],
			panel_styles: ["tree-panel-opened", "tree-panel-closed"],
			crossfadeButtonStyle: true,
			crossfadePanelStyle: true,
			fade_fx_content: false
		}, options);
		
		this.branch_total = oid;
		
		/* init states */
		this.states = {
			OPENED: 1,
			CLOSED: 0
		};
		this.opened = this.options.opened;
		this.currentstate = (this.opened) ? this.states.OPENED : this.states.CLOSED;
		this.nextstate = (!this.opened) ? this.states.OPENED : this.states.CLOSED;
		
		this.an = new Animator({
			duration: this.options.duration,
			transition: this.options.transition,
			onComplete: (function() {
				this.control
					.removeClassName(this.options.button_styles[this.currentstate])
					.addClassName(this.options.button_styles[this.nextstate]);
				
				this.contents
					.invoke("removeClassName", this.options.panel_styles[this.currentstate])
					.invoke("addClassName", this.options.panel_styles[this.nextstate]);
				
				this.currentstate = this.nextstate;
				
				this.opened = (this.currentstate == this.states.OPENED);
				
				if(this.opened) {
					this.control.fire("Tree:BranchOpened");
					this.contents.invoke("setStyle", {height:""});
				}
				
			}).bind(this)
		});
		
		this.updateView();
	},
	enableKeyAccess: function() {
		this.control.setAttribute("tabindex", "0");	
	},
/*-------------------------------------------------------------------------------

	Method: isOpened
	
	Returned Value:
	Boolean
	
-------------------------------------------------------------------------------*/
	isOpened: function() {
		return this.opened;
	},
/*-------------------------------------------------------------------------------

	Method: observe
	
	Parameters:
	eventname - string
	func - function
	
	Returned Value:
	None
	
-------------------------------------------------------------------------------*/
	observe: function(eventname, func) {
		this.control.observe(eventname, func);
	},
/*-------------------------------------------------------------------------------

	Method: toggleView
	
	Returned Value:
	Animator instance
	
-------------------------------------------------------------------------------*/
	toggleView: function() {
		this.setView((this.currentstate == this.states.CLOSED) ? this.states.OPENED : this.states.CLOSED);	
	},
/*-------------------------------------------------------------------------------

	Method: setLabel
	
	Parmeters:
	label - string
	
	Returned Value:
	Animator instance
	
-------------------------------------------------------------------------------*/
	setLabel: function(label) {
		this.control.setAttribute("title", label);
	},
/*-------------------------------------------------------------------------------

	Method: setView
	
	Parmeters:
	state - integer, 0  or 1
	
	Returned Value:
	Animator instance
	
-------------------------------------------------------------------------------*/
	setView: function(state) {
		this.nextstate = state;
		this.an.seekTo(this.nextstate);
	},
/*-------------------------------------------------------------------------------

	Method: updateView
	
	Returned Value:
	Animator instance
	
-------------------------------------------------------------------------------*/
	updateView: function() {
		this.control
			.removeClassName(this.options.button_styles[this.nextstate])
			.addClassName(this.options.button_styles[this.currentstate]);
		
		this.contents
			.invoke("removeClassName", this.options.panel_styles[this.nextstate])
			.invoke("addClassName", this.options.panel_styles[this.currentstate]);
			
		this.an.clearSubjects();
		
		if(this.options.crossfadeButtonStyle) {
			this.an.addSubject(new CSSStyleSubject(this.control, 
							       this.options.button_styles[this.states.CLOSED], 
							       this.options.button_styles[this.states.OPENED]));
		}
		
		
		this.contents.each((function(content) {
			this.an.addSubject(new NumericalStyleSubject(content, 
								     "height", 
								     0, 
								     content.scrollHeight));
			
			if(this.options.fade_fx_content && adobe.SCRIPT_ENGINE!="JScript") {
				this.an.addSubject(new NumericalStyleSubject(content, 
										 "opacity", 
										 0, 
										 1));
			}
			if(this.options.crossfadePanelStyle) {
				this.an.addSubject(new CSSStyleSubject(content, 
							       this.options.panel_styles[this.states.CLOSED], 
							       this.options.panel_styles[this.states.OPENED]));	
			}
			
		}).bind(this));
		
		this.an.jumpTo(this.currentstate);
	}
});