/*
Element.3.js
A library to handle DOM Elements

Documentation at http://barney.wallst.com/wsodwiki/index.php/Element.2.js_:Dev

Most functions take an element handle or an element's id as a string as the first parameter
"set" functions can also take an array of elements or ids.

Element.get – returns a handle on an element
Element.create – extension of document.createElement. 2nd parameter is properties, 3rd parameter is children.
Element.addChild - appends an element to an element
Element.remove – remove an element from the DOM
Element.setDisplay – sets display: property of element(s)
Element.setVisibility – sets visibility: property of element(s)
Element.remove – removes element(s) from DOM
Element.setHTML – set innerHTML value of element(s).  Optional 3rd parameter to append content
Element.parseSelector – returns list of elements that match CSS selector string
Element.setStyle – an inline way to set multiple style properties of an element – example: Element.setStyle(el,"border:2px solid red;width:400px;background:#888;");
Element.removeChildNodes - remove all nodes within an element
Element.cloneNode - extension of el.cloneNode that strips all DOM events
Element.insertBefore - keeps the Element syntax in your code
Element.insertAfter - because the DOM only offers insertBefore
Element.nextElement - returns next sibling of indicated tagname
Element.getParent - returns the first parent element of a certain tag name
Element.getXY - returns an {x:,y:} object of an element's position relative to the html body
Element.setXY - set an element(s) position
Element.getSize - returns an {width:,height:} object of an element's total size
Element.getSizeXY  - returns an {x:,y:} object of an element's total size
Element.setSize - set the size of an element(s)
Element.setWidth - set the width of an element(s)
Element.setHeight - set the height of an element(s)
Element.getBorderSize - returns the current border size 

Element.addClass  - adds a class attribute
Element.removeClass - removes a class attribute
Element.toggleClass - reverses a class attribute
Element.switchClass - takes a 3rd parameter boolean to turn on/off a class attribute
Element.hasClass - returns if an element is part of a class
Element.getStyle

//////////////////////////
Element Extras (separate file)
Element.isInsideOf 
Element.isInsideOfNS 
Element.isInsideOfEW 
Element.debug
Element.setOpacity 
Element.setDisabled
Element.setAttribute - same functionality as el.setAttribute but accepts an array of elements
Element.setProperty - sets a JavaScript property to a single or arary of elements


*/


var Element_class = function() {

}

// -------------------------------------------------------
// 	DOM Helpers
// -------------------------------------------------------


Element_class.prototype.get = function(el) {
	if (typeof el == "string") el = document.getElementById(el);
	return el;
};


Element_class.prototype.create = function (tag, attributes, children, parent, ElementObjectInstance) {

	var element = document.createElement(tag);

	// mapping for IE specific attributes
	var attributeMap = {
		"for":["htmlFor"],
		"colspan":["colSpan"]
	}

	for (var i in attributes)
	{
		if (i == "className" || i == "class")
		{
			element.className = attributes[i];
		}
		else if (document.all && attributeMap[i])
		{
			for (var j = 0; j <attributeMap[i].length; j++) {
				element.setAttribute(attributeMap[i][j], attributes[i]);
			}
			element.setAttribute(i, attributes[i]); // retain original
		}
		else if (i == "style")
		{
			this.setStyle(element,attributes[i]);
		}
		else if (i == "Events")
		{
			if (typeof Events != "undefined") {
				var elEvents = attributes[i];
				if (!this.isArray(elEvents)) {
					elEvents = [elEvents]
				}
	
				for (var j = 0; j < elEvents.length; j++) {
					elEvents[j].element = element;
					Events.add(elEvents[j]);
				}
			}
			else {
				alert(":: DEV ERROR :: \n Location: Element.3.js -- Element_class.prototype.create \n Type: Dependency \n Message: Expecting Events Lib for use of Events in Element.create")
			}
		}
		else
		{
			element.setAttribute(i, attributes[i]);
		};
	};

	if (arguments.length > 2 && children)
	{
		if (typeof children == "object" && children.constructor == Array)
		{
			for (var i = 0; i < children.length; i++)
			{
				this.addChild(element, children[i]);
			};
		}
		else
		{
			this.addChild(element, children);
		};
	};

	if (parent) {	
		this.addChild(parent,element);
	}

	return (ElementObjectInstance) ? new ElementObject(element) : element;
};

Element_class.prototype.addChild = function (el, child) {
	if (typeof child == "object")
	{
		el.appendChild(child);
	}
	else if (typeof child == "string" || typeof child == "number")
	{
		// element.appendChild(document.createTextNode(children));
		el.innerHTML += child;
	};
};




Element_class.prototype.remove = function(el) {
	el = this.get(el);

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		el[i].parentNode.removeChild(el[i])
	}
};

Element_class.prototype.setDisplay = function(el,d) {

	el = this.get(el);

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		el[i].style.display = d;
	}
};

Element_class.prototype.setVisibility = function(el,d) {
	el = this.get(el);

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		el[i].style.visibility = d;
	}
};



Element_class.prototype.removeChildNodes = function(el) {

	el = this.get(el);

	while (el.childNodes.length) {
		el.removeChild(el.firstChild);
	}
	return el;

};

Element_class.prototype.cloneNode = function(el, cloneChildren) {
	//prevents IE from cloning events
	var cloneChildNodes = (cloneChildren) ? cloneChildren : false;

	if (document.all) {

		var node = el.outerHTML;
		
		if (cloneChildNodes && el.innerHTML) {		
			node.innerHTML = el.innerHTML;
		}
		
		var container = document.createElement("DIV");
		container.innerHTML = node;
	
		node = container.firstChild;
		
	} else {
		var node = el.cloneNode(cloneChildNodes);
	}
	
	return node;

};


Element_class.prototype.getParent = function(el, tag, includeSelf) {
	var el = Element.get(el);


	if (!tag) { tag = el.tagName; }

	if (!includeSelf && el.parentNode) { // grab immediate parent
		el = el.parentNode;
	}

	if (el.tagName && el.tagName.match(/body/i) && !tag.match(/body/i)) {
		return null;
	}

	if (el.nodeType == 1 && el.tagName.toLowerCase() == tag.toLowerCase()) {
		return el;
	}
	else {
		return this.getParent(el.parentNode, tag, true);
	}
}




Element_class.prototype.setHTML = function(el,v,appendV) {
	el = this.get(el);

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
			el[i].innerHTML = (appendV) ? (el[i].innerHTML+v) : v;
	}
};

Element_class.prototype.parseSelector = (function() {
	var SEPERATOR = /\s*,\s*/;
	
	function parseSelector(selector, node, num) {

		node = node || document.documentElement;
		node = Element.get(node);
		var argSelectors = selector.split(SEPERATOR);
		var result = [];
		
		for(var i = 0; i < argSelectors.length; i++) {
			var nodes = [node];
			var stream = toStream(argSelectors[i]);
			for(var j = 0;j < stream.length;) {
				var token = stream[j++];
				var filter = stream[j++];
				var args = '';
				if(stream[j] == '(') {
					while(stream[j++] != ')' && j < stream.length) args += stream[j];
					args = args.slice(0, -1);
				}
				if(stream[j] == '[') { // Attribute Selector
					while(stream[j++] != ']' && j < stream.length) args += stream[j];
					args = args.slice(0, -1);
					token = "[";
				}

				nodes = select(nodes, token, filter, args);
			}
			result = result.concat(nodes);
		}
		
		// simple selection of an individual node
		  if (num != undefined) {
			if (result.length && num == "first") {
				return result[0]
			}
			else if (result.length && num == "last") {
				return result[result.length-1]
			}
			else if (result.length && !isNaN(num) && result.length >= num) {
				return result[num]
			}
			else {
				return null; // num passed, but no matches
			}
		
		  }

		return result;
	}

	var WHITESPACE = /\s*([\s>+~(),]|^|$)\s*/g;
	var IMPLIED_ALL = /([\s>+~,]|[^(]\+|^)([#.:@])/g;
	var STANDARD_SELECT = /^[^\s>+~]/;
	var STREAM = /[\s#.:>+~[\]()@]|[^\s#.:>+~[\]()@]+/g;
		
	function toStream(selector) {
		var stream = selector.replace(WHITESPACE, '$1')
												 .replace(IMPLIED_ALL, '$1*$2');
		if(STANDARD_SELECT.test(stream)) stream = ' ' + stream;
    return stream.match(STREAM) || [];
	}
	
	function select(nodes, token, filter, args) {
		return (selectors[token]) ? selectors[token](nodes, filter, args) : [];
	}
	
	var util = {
		toArray: function(enumerable) {
			var a = [];
			for(var i = 0; i < enumerable.length; i++) util.push(a, enumerable[i]);
			return a;
		},
		
		push: function(arr) {
      for(var i = 1; i < arguments.length; i++) arr[arr.length] = arguments[i];
      return arr.length;
    }
	};
	
	var dom = {
		isTag: function(node, tag) {
			return (tag == '*') || (
				tag.toLowerCase() == node.nodeName.toLowerCase().replace(':html', '')
			);
		},
	
		previousSiblingElement: function(node) {
			do node = node.previousSibling; while(node && node.nodeType != 1);
			return node;
		},
	
		nextSiblingElement: function(node) {
			do node = node.nextSibling; while(node && node.nodeType != 1);
			return node;
		},
	
		hasClass: function(name, node) {
			return (node.className || '').match('(^|\\s)'+name+'(\\s|$)');
		},
	
		getByTag: function(tag, node) {
			/*	IE5.x does not support document.getElementsByTagName("*")
				therefore we're falling back to element.all */
			if(tag == '*') {
			  var nodes = node.getElementsByTagName(tag);
			  if(nodes.length == 0 && node.all != null) return node.all
			  return nodes;
		  }
			return node.getElementsByTagName(tag);
		}
	};

	var selectors = {
		'#': function(nodes, filter) {
			for(var i = 0; i < nodes.length; i++) {
				if(nodes[i].getAttribute('id') == filter) return [nodes[i]];
			}
			return [];
		},

		' ': function(nodes, filter) {
			var result = [];
			for(var i = 0; i < nodes.length; i++) {
				result = result.concat(util.toArray(dom.getByTag(filter, nodes[i])));
			}
			return result;
		},
		
		'>': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				for(var j = 0, child; j < node.childNodes.length; j++) {
					child = node.childNodes[j];
					if(child.nodeType == 1 && dom.isTag(child, filter)) {
						util.push(result, child);
					}
				}
			}
			return result;
		},

		'.': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				if(dom.hasClass([filter], node)) util.push(result, node);
			}
			return result;
		}, 
				
		':': function(nodes, filter, args) {
			//alert(parseSelector.pseudoClasses[filter])
			//alert(pseudoClasses[filter](nodes, args))
			return (pseudoClasses[filter]) ? pseudoClasses[filter](nodes, args) : [];
		},


		'+': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				var sibling = parseSelector.dom.nextSiblingElement(node);
				if(sibling && parseSelector.dom.isTag(sibling, filter)) {
					util.push(result, sibling);
				}
			}
			return result;
		},
		'~': function(nodes, filter) {
			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				var sibling = parseSelector.dom.previousSiblingElement(node);
				if(parseSelector.dom.isTag(sibling, filter)) util.push(result, sibling);
			}


			return result;
		},
		'[': function(nodes, filter, args) { // CSS Attribute

			args = args.replace(/'/g,'"'); // allow single quotes

			var attributeProps = [];
		
			if (!/=/.test(args)) { // has attribute test
				attributeProps = ["",args,"",""];
			}
			else {
				var params = args.match(/([^^$*]*)(\*=|\$=|\^=|=)"(.*)"/);
				if (params) { attributeProps = params; }
			}	
		
			attributeProps = {name:attributeProps[1],operator:attributeProps[2],value:attributeProps[3]};

			var result = [];
			for(var i = 0, node; i < nodes.length; i++) {
				node = nodes[i];
				var children = parseSelector.dom.getByTag(filter,node);
				// loop through children and see if match
				for (var j=0; j < children.length; j++) {
					var att = children[j].getAttribute(attributeProps.name);
					if (!att) continue;

					if (!attributeProps.operator) {
						util.push(result,children[j])
					}
					else {
						switch(attributeProps.operator){
							case '*=': ;if (att.match(attributeProps.value)) util.push(result,children[j]); break;
							case '=': if (att == attributeProps.value) util.push(result,children[j]); break;
							case '^=': if (att.match('^'+attributeProps.value)) util.push(result,children[j]); break;
							case '$=': if (att.match(attributeProps.value+'$')) util.push(result,children[j]);
						}	
					}
				}


			}
			return result;

		}
		
	};

	parseSelector.selectors			= selectors;
	var pseudoClasses = { };
	parseSelector.pseudoClasses 			= pseudoClasses;
	parseSelector.util 				  = util;
	parseSelector.dom 				  = dom;

	return parseSelector;
})();


Element_class.prototype.insertBefore = function (el, sibling) {

	el = this.get(el);	if (!el) return;

	sibling = this.get(sibling);

	if (!el || !sibling || !sibling.parentNode)
	{
		return null;
	};

	sibling.parentNode.insertBefore(el, sibling);
};


Element_class.prototype.insertAfter = function (el, sibling) {

	el = this.get(el);	if (!el) return;

	sibling = this.get(sibling);

	if (!el || !sibling || !sibling.parentNode)
	{
		return null;
	};

	return sibling.nextSibling ? sibling.parentNode.insertBefore(el, sibling.nextSibling) : sibling.parentNode.appendChild(el);
};


	
// 	Returns next sibling of indicated tagname
Element_class.prototype.nextElement = function(el, tagName) {
	tagName = tagName || "";
	tagName = tagName.toLowerCase();
	var sibling = this.get(el);
	while(sibling = sibling.nextSibling) {
		if (sibling.nodeType == 1 && ( tagName == undefined || sibling.tagName.toLowerCase() == tagName ) ) {
			return sibling;
		}
	}
	return null;
}
	


Element_class.prototype.getXY = function(el) {

	el = this.get(el);	if (!el) return;

	var x = 0, y = 0;

	while (el.offsetParent) {
		x += el.offsetLeft;
		y += el.offsetTop;
		el = el.offsetParent;
	}

	return { x: x, y: y };
};


Element_class.prototype.setXY = function(el, x, y) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		if (x !== null) el[i].style.left = x + "px";
		if (y !== null) el[i].style.top = y + "px";
	}
};

Element_class.prototype.getSize = function(el) {

	el = this.get(el);	if (!el) return;

	var height = el.offsetHeight;
	var width = el.offsetWidth;
    
	return { height: height, width: width };
};


// more useful if in same units as pos
Element_class.prototype.getSizeXY = function(el) {
	var size = this.getSize(el);
	return {x: size.width, y: size.height};
};


Element_class.prototype.setSize = function(el, width, height) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		this.setWidth(el[i], width);
		this.setHeight(el[i], height);
	}
};

Element_class.prototype.setWidth = function(el, width) {
	
	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		el[i].style.width = width + "px";
	}
};

Element_class.prototype.setHeight = function(el, height) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		el[i].style.height = height + "px";
	}
};


Element_class.prototype.getBorderSize = function(el) {

	el = this.get(el);

	var height = el.offsetHeight - el.clientHeight;
	var width = el.offsetWidth - el.clientWidth;

	return { height: height, width: width };
}


// -------------------------------------------------------
//  className modifications
// -------------------------------------------------------

// takes a boolean as 3rd parameter to turn on or off class
Element_class.prototype.switchClass = function(el, classname, b) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}

	if (b) {
		this.addClass(el, classname);
	}
	else {
		this.removeClass(el, classname);
	}

	if (el.length) {
		return el[0].className;
	}

};

Element_class.prototype.replaceClass = function(el, oClassname, nClassname) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}

	this.removeClass(el, oClassname);
	this.addClass(el, nClassname);

	if (el.length) {
		return el[0].className;
	}

};

Element_class.prototype.addClass = function(el, classname) {

	el = this.get(el);	if (!el) return;

	if (!this.isArray(el)) {
		el = [el]
	}
	for (var i=0; i<el.length; i++) {
		if (!this.hasClass(el[i], classname)) {
    			el[i].className += (el[i].className?" ":"") + classname;
		}
	}

	if (el.length) {
		return el[0].className;
	}
};

Element_class.prototype.removeClass = function(el, classname) {

	el = this.get(el);

	if (!this.isArray(el)) {
		el = [el]
	}

	var re = this._getClassnameRegEx(classname);

	for (var i=0; i<el.length; i++) {
			el[i].className = el[i].className.replace(re, "$1$3");
	}

	if (el.length) {
		return el[0].className;
	}
	
};

Element_class.prototype.toggleClass = function(el, classname) {
	el = this.get(el);
	//alert(el.id)

	if (!this.isArray(el)) {
		el = [el]
	}

	for (var i=0; i<el.length; i++) {
		if (this.hasClass(el[i], classname)) {
			this.removeClass(el[i], classname);
		} else {
			this.addClass(el[i], classname);
		}
	}

	if (el.length) {
		return el[0].className;
	}
};

Element_class.prototype.hasClass = function(el, classname) {

	el = this.get(el);

	return (el.className && el.className.match(this._getClassnameRegEx(classname)) != null);
};

Element_class.prototype._getClassnameRegEx = function(classname) {
	return new RegExp("(\\s|^)(" + classname + ")(\\s|$)", "g")
};

// -------------------------------------------------------
//  style gets and sets
// -------------------------------------------------------

Element_class.prototype.getStyle = function (el, styleProp) {

	el = this.get(el);	if (!el) return;

	if (window.getComputedStyle) {

		return window.getComputedStyle(el, null).getPropertyValue(styleProp);

	} else if (el.currentStyle) {

		styleProp = styleProp.replace(/\-(.)/g, function () {
		
			return arguments[1].toUpperCase();

		});
                
		return el.currentStyle[styleProp];

	}

	return null;

} 



Element_class.prototype.setStyle = function (el, styles) {

	el = this.get(el);	if (!el) return;

	var pairs = [];
	styles = styles.split(";");
	for (var i=0; i<styles.length; i++) {
		//var nv = styles[i].split(":");
		var nv = styles[i].replace(":","{:}").split("{:}");
		if (nv.length > 1) {
			nv[0] = nv[0].replace(/\-(.)/g, function() {
				return arguments[1].toUpperCase();
			}).replace(/\s/g, "");
			pairs.push({n:nv[0],v:nv[1].replace(/^\s*|\s*$/g, "")});	
		}
	}

	if (!this.isArray(el)) {
		el = [el]
	}

	var attributeMap = {
		"float":["cssFloat","styleFloat"]
	}

	for (var i=0; i<el.length; i++) {	
		for (var j=0; j<pairs.length; j++) {
			if (attributeMap[pairs[j].n]) {
				for (var k = 0; k <attributeMap[pairs[j].n].length; k++) {
					pairs.push({n:attributeMap[pairs[j].n][k],v:pairs[j].v});
				}
			}
			el[i].style[pairs[j].n] = pairs[j].v;
		}
	}

}



// -------------------------------------------------------
// 	Element Helpers
// -------------------------------------------------------


Element_class.prototype.isArray = function(o) {
	return (o instanceof Array);
};


Element = new Element_class();


