/********** Miscellaneous DOM utilities **********/

// Include the namespace tests until we've ensured that all pages including this script also
// include /codelib/js/ilr/ilr.js and /codelib/js/ilr/util/scriptLoader.js
// RY 9/17/07 Script loading strategy completely bombs in IE6
/*
if (typeof ILR != "undefined") {
	var requiredScripts = [
		ILR.js.ilr + "util/string.js"
	];
	
	if (typeof ILR.util != "undefined" && typeof ILR.util.scriptLoader != "undefined") {
		ILR.util.scriptLoader.loadScriptsOnce(requiredScripts);
	}
}
*/

/** @description Applies the designated function to an element or array of elements. Modelled
 * on YAHOO.util.Dom.batch(). Allows a function call to abstract away from whether it is
 * passed a single id/element or array of ids/elements.
 * @method batch
 * @param {String|HTMLElement|Array} el The id/element/array of elements and ids to apply the function to
 * @param {Function} fcn The function to apply
 * @param {Object} args (optional) The arguments to fcn
 * @return {Array} An array of function return values
 */
// RY Not deployed/tested yet.
var batch = function(el, fcn, args) {
	
	var i, 
		e,
		results = [];
	
	el = toArray(el);
	for (i = 0; i < el.length; i++) {
		results[results.length] = fcn(getElement(el[i]), args);
	}
	
	return results;

};


/******************** GET VALUES ********************************************/

// If el is a string, return the element with id el; else return el itself.
function getElement(el) {

	if (ILR.util.string.isString(el)) {
		el =  document.getElementById(el);
	}
	return el;
}


/**
 * @description Get the nearest ancestor of an element with the specified tagName and/or className. 
 * For example, get the form that a given input element belongs to. Arguments are passed in in 
 * an args orbject because there are multiple optional arguments; otherwise, we couldn't specify
 * just el and classname without tagname, for example.
 * @method getAncestor
 * @param {Object|String} args.el An element reference or element id
 * @param {String} args.tagname (optional) The tag name of the element to return
 * @param {String} args.classname (optional) The class name of the element to return
 * @param {Boolean} args.self (optional) Indicates whether el itself can be returned. Default to false.
 * @return {Element} The ancestor element
 */
function getAncestor(args) {

	var ancestor, 
		el = args.el ? getElement(args.el) : "",
		self = args.self || false,
		classname = args.classname || "",
		tagname = args.tagname ? args.tagname.toUpperCase() : "";
	
	if (el && (tagname || classname)) {
		ancestor = self ? el : el.parentNode;

		while ( ancestor !=  null &&
				( (tagname && ancestor.tagName != tagname) ||
				  (classname && !hasClass(ancestor, classname)) ) ) {
			ancestor = ancestor.parentNode;	
		}
		return ancestor;
	}
}


/**
 * @description Get the nearest ancestor of an element with the specified tagName. For example, get the 
 * form that a given input element belongs to.
 * @method getAncestor
 * @param {Object|String} el An element reference or element id
 * @param {String} tag The tag name of the element to return
 * @param {Boolean} self (optional) Indicates whether el itself can be returned. Default to false.
 * @return {Element} The ancestor element
 */
/*
function getAncestor(el, tag, self) {

	var ancestor; 
	self = self || false;
	el = getElement(el);
	tag = tag.toUpperCase();
	
	ancestor = self ? el : el.parentNode;
	
	while (ancestor != null && ancestor.tagName != tag) {
		ancestor = ancestor.parentNode;
	}
	return ancestor;
}
*/


/** 
 * @description Return an array of element attributes. 
 * @param {Array} els An array of elements
 * @param {String} attr The attribute to get
 * @return {Array} array of attribute values 
 */
function getAttributes(els, attr) {
	
	var attrArray = [];
	
	for (i = 0; i < els.length; i++) {
		attrArray.push(els[i][attr]);
	}
	
	return attrArray;
}

/** 
 * @description Return an array of element attributes. Example:
 * getAttributesByTagName(body, "script", "src") returns an array of src values of all script
 * tags in the document
 * @param {HTMLElement|String} el
 * @param {String} tag
 * @param {String} attr
 * @return {Array} array of attribute values 
 */
function getAttributesByTagName(tag, attr, node) {
	
	var tags;
	
	node = getElement(node) || document;	
	tags = node.getElementsByTagName(tag);
	
	return getAttributes(tags, attr);

}

/******************** SET VALUES ********************************************/

// Set the property property to value value for each item in element array elArray
function setProperty(elArray, property, value) {
	
	for (var i = 0; i < elArray.length; i++) {
		elArray[i].property = value;
	}
}


/********** DOM functions for class name manipulation **********/

// hasClass(), addClass(), removeClass() adapted from YUI
function hasClass(el, className) {

	var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
	return re.test(getElement(el).className);
}

function addClass(el, className) {

	el = toArray(el);
	for (var i = 0; i < el.length; i++) {
		var e = getElement(el[i]);
		if (!hasClass(e, className)) {
			e.className = [e.className, className].join(' ');
		}
	}
} 

function removeClass(el, className) {

	var re = new RegExp('(?:^|\\s+)' + className + '|' + className + '(?:\\s+|$)', 'g');
	el = toArray(el);

	for (var i = 0; i < el.length; i++) {
		var e = getElement(el[i]);
		e.className = e.className.replace(re, '');
	}	
}

function getElementsByClassName(className, node, tagName) {
	
	var classElements = [];
	
	// Defaults for optional parameters
	if ( tagName == null ) {
		tagName = '*';
	}
	if ( node == null ) {
		node = document;
	}

	// console.log(node.id + " " + className + " " + tagName);
  	var els = node.getElementsByTagName(tagName);

  	// don't use \\b around searchClass because class names can be hyphenated
  	var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");

  	for (i = 0; i < els.length; i++) {
    	if ( pattern.test(els[i].className) ) {
      		classElements[classElements.length] = els[i];
    	}
  	}
	return classElements;
}

/* IE6 and Opera don't support extension of the Element object (in fact in these browsers there
is no Element object). RY 2/27/07 
Element.prototype.getElementsByClassName = function(className, tagName) {

	var classElements = [];
	
	// Defaults for optional parameters
	if ( tagName == null ) {
		tagName = '*';
	}

	// console.log(this.id + " " + className + " " + tagName);
  	var els = this.getElementsByTagName(tagName);

  	// don't use \\b around searchClass because class names can be hyphenated
  	var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");

  	for (i = 0, j = 0; i < els.length; i++) {
    	if ( pattern.test(els[i].className) ) {
      		classElements[j] = els[i];
      		j++;
    	}
  	}
	return classElements;
};
*/


/********** DOM insertion utilities **********/

// Insert newElement as the next sibling of targetElement
// Javascript provides a built-in insertBefore function, but not insertAfter
function insertAfter(newElement, targetElement) {

	var parent = targetElement.parentNode;

	if ( parent.lastChild == targetElement ) {
		parent.appendChild(newElement);
	}
	else {	
		parent.insertBefore(newElement, targetElement.nextSibling);
	}
		
}

// Insert an empty div styled with "clear both" as the next sibling of element el
function insertClearingDiv(el) {

	var div = document.createElement("div");
	div.style.clear = "both";
	insertAfter(div, el);
	
}

/**
 * @description If oldFirst immediately precedes newFirst, move newFirst above oldFirst. Assumes they share a parent.
 * @method reorder
 * @param {HTMLElement|String} newFirst The element to move ahead
 * @param {HTMLElement|String} oldFirst The element to move behind
 */
var reorder = function(newFirst, oldFirst) {
	
	newFirst = getElement(newFirst);
	oldFirst = getElement(oldFirst);
	
	if (oldFirst.nextSibling == newFirst) {
		newFirst.parent.insertBefore(newFirst, oldFirst);
	}
	
};

