
/**
 * @namespace Publicis.Components
 * @class Publicis.Components.List
 * @extends Publicis.Observable
 * Makes a list with a lot events ,controlls and configuration options
 * <br><br>
 * Dependency on Prototype.js Version 1.6.x
 * <br>
 * <br>
 * TODO: Add CSS Styles to /resources/Publicis.css<br><br>
 * There is a known bug in prototype 1.6.1 about stopObserving and IE while removing dom nodes
 * <br>
 * see: https://prototype.lighthouseapp.com/projects/8886/tickets/789-observestopobserving-cause-memory-leaks-in-ie
 * <br>
 * There is a known bug in IE while removeChild
 * <br>
 * see: http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/c76967f0-dcf8-47d0-8984-8fe1282a94f5
 *  
 * @constructor
 * Creates a new List
 * @param {Object} cfg Configuration options
 * @author Alexis Dorn
 */
			
Publicis.Components.List = function (cfg){
	/**
	 * @private
	 */
	this.cfg = Object.clone(cfg);
	/**
	 * @cfg {String|DOMElement} renderTo Container Element of list
	 */
	this.el = this.node = $(this.cfg.renderTo);
	/**
	* @cfg {String} id Unique ID for the component
	*/ 
	if (typeof this.cfg.id == "undefined"){
		this.cfg.id = Publicis.uniqueId();
	}
	/**
	 * @cfg {Object[]} data Data Container. Array of data objects (@see config.valueProp and config.textProp)
	 */
	this.data = [];
	this.dataById = {};
	this.setData(this.cfg.data);
	
	
	/**
	 * @cfg {String} valueProp Key of data objects, which will contain the value (@see config.data)
	 */  
	 /**
	 * @cfg {String} textProp Key of data objects, which will contain the text (@see config.data)
	 */
	/**
	 * @cfg {String} bodyCls CSS Class of the List Container. Default to pubList
	 */  
	 if (typeof this.cfg.bodyCls == "undefined"){
		this.cfg.bodyCls = "";
	 }
	/**
	* @cfg {Boolean} dhtmlScroll If the items of the box will change dynamically, make sure not to enable dhtmlScroll
	* as there is a bug in the slider
	*/  
	if (typeof this.cfg.dhtmlScroll != "boolean"){
		this.cfg.dhtmlScroll = false;
	}
	 
	this.ul = $(document.createElement("ul"));
	this.ul.style.width = "100%";
	
	Publicis.Components.List.superclass.constructor.call(this, cfg);
	
	this.registerEvent([
		/**
		 * @event render
		 * Fires when component is rendered
		 * @param {Publicis.Components.List} this
		 * @param {Prototype.Element} this.el
		 */
		"render",
		/**
		 * @event itemclick
		 * Fires when a item has been clicked
		 * @param {Publicis.Components.List} this
		 * @param {Prototype.Element} el Element of the list item
		 * @param {Mixed} value Value of the list item
		 * @param {String} text Text of the list item
		 * @param {Integer} index Index of data
		 */
		"itemclick",
		/**
		 * @event update
		 * Fires when component is updated with data
		 * @param {Publicis.Components.List} this
		 * @param {Prototype.Element} this.el
		 * @param {Array} this.data
		 */
		"update",
		/**
		 * @event beforeupdate
		 * Fires when before component is updated with data. Return false to stop updating
		 * @param {Publicis.Components.List} this
		 * @param {Prototype.Element} this.el
		 * @param {Array} this.data
		 */
		"beforeupdate"
	]);
	
	this.hasScrollbar = typeof GuiScrollbar == "function" && this.cfg.dhtmlScroll;
	this.scrollbar = false;
	this.listTemplate = false;
	
	this.rendered = false;
	
	this.render();
	
};

Publicis.extend(Publicis.Components.List, Publicis.Observable, {
	/**
	 * Return the List Template
	 * @private
	 */
	getListTemplate: function (){
		if (this.listTemplate instanceof Template){
			return this.listTemplate;
		}
		this.listTemplate = new Template([
			"<li style='cursor:pointer; #{sep};width:100%;'><span class='value'>tst</span>",
				"<a href='javascript:void(null)' compValue='#{value}' compIndex='#{index}' style='display:block;width:100%;' title='#{text}'>",
				"#{text}",
				"</a>",
			"</li>"].join("")
		); 
		return this.listTemplate;
	},
	getScrollTemplate: function (){
		return new Template([
			'<div class="arrow-up"></div>',
			'<div id="slider-#{id}" class="slider">',
				'<div id="handle-#{id}" class="handle"></div>',
			'</div>',
			'<div class="arrow-down"></div>'
			].join("")
		);
	},
	hasDhtmlScrollbar: function (){
		return this.hasScrollbar;
	},
	renderScrollbar: function (){
		
		var innerHTML = this.getScrollTemplate().evaluate({
			id: this.cfg.id
		});
		this.scrollContainer = $(document.createElement("div"));
		this.el.appendChild(this.scrollContainer);
		
		this.scrollContainer.addClassName("slider-wrapper");
		this.scrollContainer.innerHTML = innerHTML;
		
		this.scrollbar = new GuiScrollbar("handle-" + this.cfg.id, "slider-" + this.cfg.id, this);
	},
	/**
	 * Builds the list
	 * @private
	 */
	render: function (){
		this.el.addClassName(this.cfg.bodyCls);
		if (!this.hasDhtmlScrollbar()){
			this.el.style.overflowY = "scroll";
		}
		this.el.appendChild (this.ul);
		
		//Setting Data
		this.updateList();
		
		//Render Scrollbar
		if (this.hasDhtmlScrollbar()) {
			this.renderScrollbar();
		}
		
		this.fireEvent("render", this, this.el);
	},
	/**
	 * Gets the basic conatiner
	 */
	getEl: function (){
		if (this.rendered){
			return this.el;
		}
		return false;
	},
	/**
	 * Sets the internal datastructure data into this.data
	 * @param {Array} data
	 * @private
	 */
	setData: function (data){
		var cpObj, i;
		this.data = [];
		this.dataById = {};
		if (this.cfg.data instanceof Array){
			for (i = 0; i < data.length; i++){
				cpObj = {
					index: i,
					text: data[i][this.cfg.textProp],
					value: data[i][this.cfg.valueProp]
				};
				this.data.push(cpObj);
				this.dataById[data[i][this.cfg.valueProp]] = cpObj;
			}
		}
		
	},
	/**
	 * Returns List Tags as array of DOMElement
	 * @return DOMElement[]
	 * @private
	 */
	getListItems : function (){
		return this.el.getElementsByTagName("a"); 
	},
	/**
	 * Updates the view with this.data
	 * @private
	 */
	updateList: function (){
		var innerHTML = [], parent, tmplateData, as, klasse, i = 0;
		var template = this.getListTemplate();
		
		if (this.fireEvent("beforeupdate", this, this.el, this.data) === false) {
			return false;
		}
		 
		for (i = 0; i < this.data.length; i++){
			tmplateData = {
				index: this.data[i].index,
				text: this.data[i].text,
				value: this.data[i].value/*,
				sep: (i < (this.data.length - 1)) ? "border-bottom:1px solid #dddddd;": ""*/
			};
			innerHTML.push(template.evaluate(tmplateData)); 
		}
		
		this.ul.innerHTML = innerHTML.join("");
		
		if (Prototype.Browser.IE){
			klasse = this;
			this.clickCallback = function (ev){
				klasse.onItemClick(ev);
			}; 
		}else{
			this.clickCallback = this.onItemClick.bindAsEventListener(this);
		}
		
		
		//Binding Observers
		as = this.getListItems(); 
		
		//Maybe should be done with Prototype.Enumerable and iterator
		for (i = 0; i < as.length; i++) {
			var el = $(as[i]);
			parent = $(el.parentNode);
			if (Prototype.Browser.IE){
				el.attachEvent("onclick", this.clickCallback);
				parent.attachEvent("onclick", this.clickCallback);
			}else{
				el.observe("click", this.clickCallback);
				parent.observe("click", this.clickCallback);
			}
		}
		this.rendered = true;
		this.fireEvent("update", this, this.el, this.data);
	},
	/**
	 * Observs the item elements by the click event 
	 * @param {Object} e
	 * @private
	 */
	onItemClick: function (e){
		
		var el = Event.element(e), a, as, value;
		Event.stop(e);
		value = el.readAttribute("compValue");
		if(el.nodeName.toLowerCase() == "li"){
			as = el.getElementsByTagName("a");
			a = $(as[0]);
		}else{
			a = el;
		}
		var text = a.innerHTML;
		this.fireEvent("itemclick", this, a, a.readAttribute("compValue"), text, a.readAttribute("compIndex"));
	},
	/**
	 * Updates the component with data, must be in defined structure (@see config.valueProp and config.textProp) 
	 * @param {Object} data
	 */
	update : function (data){
		this.setData(data);
		if (this.rendered) {
			this.removeNode(this.ul);
			this.ul = $(document.createElement("ul"));
			this.ul.style.width = "100%";
			if (this.hasDhtmlScrollbar()){
				this.el.insert(this.ul, {before : this.scrollContainer});
			}else{
				this.el.appendChild(this.ul);
			}
			
			this.updateList();
		}
	},
	/**
	 * Removes all child nodes
	 * @private
	 * @param {Object} node
	 */
	removeNode: function (node){
		var i, nd;
		node = $(node);
		
		while (node.firstChild) {
			this.removeNode(node.firstChild);
		}
		
		if (node.nodeType == 1){
			if (Prototype.Browser.IE) {
				node.detachEvent("onclick", this.clickCallback);
			}else{
				node.stopObserving("click", this.clickCallback);
			}
		}
		this.discardElement(node);
	},
	/**
	 * Helper for destroying ie nodes
	 * @private
	 * @param {Object} element
	 */
	discardElement : function (element) {
		if (!document.all){
			nd = element.parentNode.removeChild(element);
			nd = null;
			delete nd;
			return;
		}
		//see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/c76967f0-dcf8-47d0-8984-8fe1282a94f5
	     var garbageBin = document.getElementById('IELeakGarbageBin');
	     if (!garbageBin) {
	             garbageBin = document.createElement('DIV');
	             garbageBin.id = 'IELeakGarbageBin';
	             garbageBin.style.display = 'none';
	             document.body.appendChild(garbageBin);
	     }
	     // move the element to the garbage bin
	     garbageBin.appendChild(element);
	     garbageBin.innerHTML = '';
	},
	/**
	 * needed by scrollbar
	 * @private
	 * @param {Object} offsetTop
	 */
	scrollTo: function (offsetTop){
		this.ul.style.top = offsetTop + "px";
	},
	/**
	 * Gets date by index 
	 * @param {Integer} index
	 * @return Boolean|Object false if index does not exists or copy of the dataobject
	 */
	getData: function (index){
		if (index < this.data.length && typeof this.data[index] != "undefined"){
			return Object.clone(this.data[index]);
		}
		return false;
	},
	/**
	 * Gets data by the value (id)
	 * @param {String} id
	 * @return Boolean|Object false if id does not exists or copy of the dataobject
	 */
	getDataById: function (id){
		if (typeof this.dataById[id] != "undefined"){
			return Object.clone(this.dataById[id]);
		}
		return false;
	}
	 
});
