
/*
 * DOM Traversal functions
 */
function FindParentByTag(source, tagName)
{
	return ApplyGenericBoolIterator(source, 'parentNode', 'parentNode',
		function(parent) { return (parent.tagName && parent.tagName.toLowerCase() == tagName.toLowerCase()); });
}

function FindChildByTag(source, tagName)
{
	return ApplyGenericBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}
function FindPreviousSiblingByTag(source, tagName)
{
	return ApplyGenericBoolIterator(source, 'previousSibling', 'previousSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}
function FindNextSiblingByTag(source, tagName)
{
	return ApplyGenericBoolIterator(source, 'nextSibling', 'nextSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}

function FindChildrenByTag(source, tagName)
{
	return ApplyGenericBoolIteratorArray(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}

function FindChildByName(source, name)
{
	return ApplyGenericBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.name && (elem.name.toLowerCase() == name.toLowerCase())); });
}

function FindChildByAttribute(source, attributeKey, attributeValue)
{
	return ApplyGenericBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem) { return ((elem.getAttribute && elem.getAttribute(attributeKey) == attributeValue)) || (elem[attributeKey] == attributeValue); });
}
function FindFirstTextChild(source)
{
	return FindChildByAttribute(source, 'nodeType', '3');
}

function FindDescendantByAttribute(source, attributeKey, attributeValue)
{
	return RecursiveBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.getAttribute && elem.getAttribute(attributeKey) == attributeValue); });
}
function FindDescendantByAttributes(source, attributeKeyValueHash)
{
	return RecursiveBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem)
		{
			if(!elem.getAttribute) return false;
			for(var attributeKey in attributeKeyValueHash)
			{
				if (!ElementAttributeEquals(elem, attributeKey, attributeKeyValueHash[attributeKey])) return false;
			}
			return true;
		});
}
function FindDescendantsByAttribute(source, attributeKey, attributeValue)
{
	return ApplyRecursiveBoolIteratorArray(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.getAttribute && elem.getAttribute(attributeKey) == attributeValue); });
}

function FindDescendantsByAttributes(source, attributeKeyValueHash)
{
	return ApplyRecursiveBoolIteratorArray(source, 'firstChild', 'nextSibling',
		function(elem)
		{
			if(!elem.getAttribute) return false;
			for(var attributeKey in attributeKeyValueHash)
			{
				if (!ElementAttributeEquals(elem, attributeKey, attributeKeyValueHash[attributeKey]))
				{
					return false;
				}
			}
			return true;
		});
}

function FindDescendantByTag(source, tagName)
{
	return RecursiveBoolIterator(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}
function FindDescendantsByTag(source, tagName)
{
	return ApplyRecursiveBoolIteratorArray(source, 'firstChild', 'nextSibling',
		function(elem) { return (elem.tagName && elem.tagName.toLowerCase() == tagName.toLowerCase()); });
}

function FindDescendantByFunction(source, funcOneParam)
{
	return RecursiveBoolIterator(source, 'firstChild', 'nextSibling', funcOneParam);
}

/**
 * For all children of source with a given tag, set the attribute to the given value
 */
function UpdateSelectedChildren(source, childrenTagName, attribute, value)
{
	ApplyToSelectedChildren(source, childrenTagName, new Function("elem", 'elem.' + attribute + ' = ' + value + ';'));
}

/// case insensitive string match
function ElementAttributeEquals(elem, attributeKey, attributeValue)
{
	var reValue = new RegExp("^" + attributeValue + "$", "i");
	if(reValue.test(elem[attributeKey])) return true;

	if(!elem.getAttribute) return false;
	if(reValue.test(elem.getAttribute(attributeKey))) return true;
	return false;
}

function GetAttribute(elem, attributeKey)
{
	if(elem[attributeKey]) return elem[attributeKey];
	return elem.getAttribute(attributeKey);
}


/*
 * DOM measurement functions
 */
function TotalOffsetLeft(source)
{
	return ApplyGenericSumIterator(source, "offsetParent", function(node) { return node.offsetLeft; });
}
function TotalOffsetTop(source)
{
	return ApplyGenericSumIterator(source, "offsetParent", function(node) { return node.offsetTop; });
}
function DiffOffsetTop(source, stopAtParent)
{
	return ApplyGenericSumLimitedIterator(source, stopAtParent, "offsetParent", function(node) { return node.offsetTop; });
}
function DiffOffsetLeft(source, stopAtParent)
{
	return ApplyGenericSumLimitedIterator(source, stopAtParent, "offsetParent", function(node) { return node.offsetLeft; });
}

/*
 * DOM Iteration functions. Use these functions to iteratively apply a user-defined function
 * on many elements in the dom tree
 */

/* iterate over a single list. return the first match */
function ApplyGenericBoolIterator(source, firstItemName, iteratorItemName, boolFunction)
{
	if(source == null) return null;
	var derivedElement = source[firstItemName];
	while(derivedElement != null)
	{
		if(boolFunction(derivedElement)) return derivedElement;
		derivedElement = derivedElement[iteratorItemName];
	}
	return null;
}

/* iterate recursively over a list and sublists. return the first match */
function RecursiveBoolIterator(source, firstItemName, iteratorItemName, boolFunction)
{
	if(source == null) return null;
	var derivedElement = source[firstItemName];
	while(derivedElement != null)
	{
		if(boolFunction(derivedElement)) return derivedElement;

		// recurse
		var recursed = RecursiveBoolIterator(derivedElement, firstItemName, iteratorItemName, boolFunction);
		if(recursed != null) return recursed;
		
		derivedElement = derivedElement[iteratorItemName];
	}
	return null;
}

/* iterate over a single list. return all matches */
function ApplyGenericBoolIteratorArray(source, firstItemName, iteratorItemName, boolFunction)
{
	var arr = new Array();
	if(source == null) return arr;
	var derivedElement = source[firstItemName];
	while(derivedElement != null)
	{
		if(boolFunction(derivedElement)) arr.push(derivedElement);
		derivedElement = derivedElement[iteratorItemName];
	}
	return arr;
}

/* iterate over a list and sublists. return all matches. does not search children of a matched element */
function ApplyRecursiveBoolIteratorArray(source, firstItemName, iteratorItemName, boolFunction)
{
	var arr = new Array();
	if(source == null) return arr;
	var derivedElement = source[firstItemName];
	while(derivedElement != null)
	{
		if(boolFunction(derivedElement))
		{
			arr.push(derivedElement);
		}
		else
		{
			arr = concat(arr, ApplyRecursiveBoolIteratorArray(derivedElement, firstItemName, iteratorItemName, boolFunction));
		}
		derivedElement = derivedElement[iteratorItemName];
	}
	return arr;
}

function ApplyGenericSumIterator(source, iteratorItemName, sumFunction)
{
	if(source == null) return null;
	var derivedElement = source;
	var sum = 0;
	while(derivedElement != null)
	{
		var x = sumFunction(derivedElement);
		sum += (x) ? x : 0; // fix undefined values etc
		derivedElement = derivedElement[iteratorItemName];
	}
	return sum;
}

function ApplyGenericSumLimitedIterator(source, stopAtElement, iteratorItemName, sumFunction)
{
	if(source == null) return null;
	var derivedElement = source;
	var sum = 0;
	while(derivedElement != null && derivedElement != stopAtElement)
	{
		var x = sumFunction(derivedElement);
		sum += (x) ? x : 0; // fix undefined values etc
//		alert(sum + ' ' + x + ' -- '  + derivedElement.id + " " + derivedElement.tagName);
		derivedElement = derivedElement[iteratorItemName];
	}
	return sum;
}

function ApplyToSelectedChildren(source, filterTagname, userFunction)
{
	if(source == null) return;
	var elements = source.getElementsByTagName(filterTagname);
	for(var i=0; i<elements.length; i++)
	{
		userFunction(elements[i]);
	}
}


/*****************************************************************************/
/*
 * Layout stuff
 */

/* works except we need to fix IE's circle reference bug */

function ClearChildren(source)
{
	if(source == null) return;
	while(source.childNodes.length > 0)
	{
		source.removeChild(source.childNodes[0]);
	}
}

/// Clears and unlinkes source recursively
/*
function ClearUnlinkEvents(source)
{
	ClearChildrenUnlinkEvents(source);
	source.parentNode.removeChild(source);
}
*/

/// Clears the children and unlinks events (for IE's mem leak)
/// but leaves the parent in place
function ClearChildrenUnlinkEvents(source)
{
	if(source == null) return;

	while(source.childNodes.length > 0)
	{
		var firstNode = source.childNodes[0];
		if(firstNode.childNodes)
		{
			ClearChildrenUnlinkEvents(firstNode);
		}
		if(firstNode.nodeType == 1) UnlinkEvents(firstNode);
		source.removeChild(firstNode);
	}
}

/// Clears and unlinkes source recursively
function UnlinkEvents(source)
{
	ChildrenUnlinkEvents(source);
	source.parentNode.removeChild(source);
}

function ChildrenUnlinkEvents(source)
{
	if(source == null) return;
	
	var node = source.firstChild;
	while(node != null)
	{
		if(node.childNodes) ChildrenUnlinkEvents(node);
		UnlinkEvents(node);
		node = node.nextSibling;
	}
}


function UnlinkEvents(elem)
{
	//if(!document.ignoreme && elem.className == "MedewerkerDropdown" ) document.ignoreme = confirm(elem.className);
	if(elem == null || elem.nodeType == 3) return;
	elem.onclick = null;
	elem.onkeypress = null;
	elem.onkeyup = null;
	elem.onkeydown = null;
	elem.onchange = null;
	elem.onfocus = null;
	elem.onblur = null;
	elem.onmouseover = null;
	elem.onmouseout = null;
	elem.onpaste = null;
	
	elem.model = null; // todo remove later
	elem.Model = null; // todo remove later
}
function SetElementByIdDisplay(elementId, display)
{
	document.getElementById(elementId).style.display = display;
}

/// Copies a row in a table and inserts it immediately below
function DuplicateRow(sourceRow)
{
   var newRow = sourceRow.cloneNode(true);
   sourceRow.parentNode.insertBefore(newRow, sourceRow.nextSibling);
   return newRow;
}


/*
function FlagHasBit(flag, mask)
{
	return (flag & mask) != 0;
}

function FlipBitInFlag(flag, mask)
{
	return flag ^ mask;
}
*/
function StringFlagHasBit(stringFlag, ID)
{
	return (stringFlag.charAt(ID) == "1");
}
function FlipBitInStringFlag(stringFlag, ID)
{
	charFlipped = (StringFlagHasBit(stringFlag, ID)) ? "0" : "1";
	return stringFlag.substr(0, ID) + charFlipped + stringFlag.substr(ID + 1);
}


// Some browsers don't like expressions such as foo.concat(arguments)
// or arguments.concat(foo) --- presumably because argument objects are
// not really considered to be arrays --- so we'll just concatenate
// pseudoarrays manually.  Thanks to Chih-Chao Lam for pointing this out.
function concat() {
    var result = [];

    for (var i = 0; i < arguments.length; i++)
        for (var j = 0; j < arguments[i].length; j++)
            result.push(arguments[i][j]);

    return result;
}

function withoutFirst(sequence) {
    result = [];

    for (var i = 1; i < sequence.length; i++)
        result.push(sequence[i]);

    return result;
}

function cons(element, sequence) {
    return concat([element], sequence);
}

//
// returns a function pointer that is bound to the given object
// usage: someitem.someeventhandler = somemethod.bind2(somemethod's object)
// this will ensure that you have access to somemethod's object when somemethod
// is called by the eventhandler
//
//
// Thanks to http://www.deepwood.net/writing/method-references.html.utf8
// for (what I'm guessing is) applied lisp knowledge
//
// Usage: someOtherObject.onSomeEvent = CreateMethodReference(this, this.someEventHandlerMethod);
//
function CreateMethodReference(object, method) {
    if (!(method instanceof Function))
        method = object[method];

    return function () {
        method.apply(object, arguments);
    };
};

Function.prototype.bind2 = function (object) {
    var method = this;
    var preappliedArguments = withoutFirst(arguments);
    return function () {
        return method.apply(object, concat(preappliedArguments, arguments));
    };
}

Function.prototype.bindAsEventListener2 = function (object) {
    var method = this;
    var preappliedArguments = withoutFirst(arguments);

    return function (event) {
        return method.apply(object, concat([event || window.event], preappliedArguments));
    };
}

//
// implement JS inheritance (fifth try in this application)
// in a clean way, see
// http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/
//
function copyPrototype(descendant, parent) {
    var sConstructor = parent.toString();
    var aMatch = sConstructor.match( /\s*function (.*)\(/ );
    if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
    for (var m in parent.prototype) {
        descendant.prototype[m] = parent.prototype[m];
    }
};



/*****************************************************************************/
/*
 * borrowed from xmlhttp.js - it works for some bizare reason on the DOM as well
 */
function IsNodeTypeElement(xmlNode)
{
	// xmlNode.nodeType == 1 -> nodeType ELEMENT
	return (xmlNode.nodeType == 1);
}
function IsNodeTypeText(xmlNode)
{
	// xmlNode.nodeType == 3 -> nodeType TEXT
	return (xmlNode.nodeType == 3);
}


/*****************************************************************************/
/*
 * methods to cope with stylesheet declarations
 */

/** Finds the first stylesheet in the active document which is located on the partialPath */
function findStyleSheet(partialPath)
{
	if(!document.styleSheets) return null;
	
	var partialPathMatch = new RegExp(partialPath, 'i');

	for(var i=0; i<document.styleSheets.length; i++)
	{
		if(!document.styleSheets[i].href) continue;
		if(partialPathMatch.test(document.styleSheets[i].href)) return document.styleSheets[i];
	}

	return null;
}

/** Finds the first cssRules whose selector (the part before the '{' ) contains the given search string */
function findCssRule(styleSheet, cssSelector)
{
	if(!styleSheet) return null;
	var cssRules = getCssRulesArray(styleSheet);
	
	var cssMatch = new RegExp(cssSelector, "i");
	for(var i=0; i<cssRules.length; i++)
	{
		if(cssMatch.test(cssRules[i].selectorText)) return cssRules[i];
	}
	return null;
}


/** Browser-agnostically returns the array of CssRules in a stylesheet */
function getCssRulesArray(styleSheet)
{
	if (styleSheet.cssRules) return styleSheet.cssRules
	else if (styleSheet.rules) return styleSheet.rules;
	return null;
}


/** generate a long string (2^power) */
function DbgAttach(s, power)
{
	for(var i=0; i<power; i++)
	{
		s += s;
	}
	return s;
}

/** trigger the garbage collector by flooding the heap */
function DbgForceGC()
{
	for(var i=0; i<10000; i++)
	{
		new Object();
	}
}

/*****************************************************************************/

/*
 * Compatibility stuff
 */

// insertAdjacentHTML(), insertAdjacentText() and insertAdjacentElement()
// for Netscape 6/Mozilla by Thor Larholm thor@jscript.dk
// Usage: include this code segment at the beginning of your document
// before any other Javascript contents.

if(typeof HTMLElement!="undefined" && !HTMLElement.prototype.insertAdjacentElement)
{
	HTMLElement.prototype.insertAdjacentElement = function(where,parsedNode) {
		switch (where)
		{
			case 'beforeBegin':
				this.parentNode.insertBefore(parsedNode,this)
				break;
			case 'afterBegin':
				this.insertBefore(parsedNode,this.firstChild);
				break;
			case 'beforeEnd':
				this.appendChild(parsedNode);
				break;
			case 'afterEnd':
				if (this.nextSibling) 
					this.parentNode.insertBefore(parsedNode,this.nextSibling);
				else this.parentNode.appendChild(parsedNode);
				break;
		}
	}

	HTMLElement.prototype.insertAdjacentHTML = function(where,htmlStr) {
		var r = this.ownerDocument.createRange();
		r.setStartBefore(this);
		var parsedHTML = r.createContextualFragment(htmlStr);
		this.insertAdjacentElement(where,parsedHTML)
	}


	HTMLElement.prototype.insertAdjacentText = function(where,txtStr) {
		var parsedText = document.createTextNode(txtStr)
		this.insertAdjacentElement(where,parsedText)
	}
}
