// JAVASCRIPT LIBRARY

// constants
var XHTML_NS_URI = 'http://www.w3.org/1999/xhtml';

/*	EventCache Version 1.0
	Copyright 2005 Mark Wubben

	Provides a way for automagically removing events from nodes and thus preventing memory leakage.
	See <http://novemberborn.net/javascript/event-cache> for more information.

	This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
*/

/*	Event Cache uses an anonymous function to create a hidden scope chain.
	This is to prevent scoping issues. */
var EventCache = function(){
	var listEvents = [];

	return {
		listEvents : listEvents,

		add : function(node, sEventName, fHandler, bCapture){
			listEvents.push(arguments);
		},

		flush : function(){
			var i, item;
			for(i = listEvents.length - 1; i >= 0; i = i - 1){
				item = listEvents[i];

				if(item[0].removeEventListener){
					item[0].removeEventListener(item[1], item[2], item[3]);
				};

				/* From this point on we need the event names to be prefixed with 'on" */
				if(item[1].substring(0, 2) != "on"){
					item[1] = "on" + item[1];
				};

				if(item[0].detachEvent){
					item[0].detachEvent(item[1], item[2]);
				};

				item[0][item[1]] = null;
			};
		}
	};
}();

/**
 * Implements <code>getElementById<code> method for W3C DOM-incapable browsers.
 * For these browsers it returns always <code>null</code>. You do not have
 * to detect the <code>getElementById<code> elsewhere in a script anymore.
 * <p>
 * You should always check, if the returned value is not <code>null</code> to
 * prevent script errors; <code>getElementById</code> returns null in
 * W3C DOM-capable browsers as well, if there is no match for the given id in
 * the document tree.
 * <p>
 * Usage:
 * <p>
 * <code>var elem = document.getElementById(id);<br>
 * if (elem) {<br>
 *     // process elem<br>
 * }</code>
 *
 * @author klaus.hartl (30.03.2005)
 */
if (!document.getElementById) {
    document.getElementById = function() {
        return null;
    };
}

/**
 * Implement <code>Array.push</code> for browsers which don't support it natively.
 *
 * @author klaus.hartl (30.03.2005)
 */
if (!Array.prototype.push) {
    Array.prototype.push = function() {
        for(var i = 0; i < arguments.length; i++) {
            this[this.length] = arguments[i];
        }
        return this.length;
    }
}

/**
 * Returns an array of element objects from the current document
 * matching the CSS selector. Selectors can contain element names,
 * class names and ids and can be nested. For example:
 * <p>
 * <code>elements = document.getElementsBySelect('div#main p a.external')</code>
 * <p>
 * Will return an array of all '<code>a</code>' elements with
 * '<code>external</code>' in their <code>class</code> attribute
 * that are contained inside '<code>p</code>' elements that are
 * contained inside the '<code>div</code>' element which has
 * <code>id="main"</code>.
 * <p>
 * Supports CSS2 and CSS3 attribute selectors.
 * <p>
 * Based on:
 * <a href="http://simon.incutio.com/archive/2003/03/25/getElementsBySelector">getElementsBySelector()</a>
 * <p>
 * Fails in Safari: '.external', because the special case "*" is not
 * supported for the getElementsByTagName() function. Use element
 * selectors (like 'a.external') whenever possible instead.
 */
function getAllChildren(e) {
    // Returns all children of element. Workaround required for IE5/Windows. Ugh.
    return e.all ? e.all : e.getElementsByTagName('*');
}

document.getElementsBySelector = function(selector) {
    // Required variables
    var bits, tagName, elements, found, foundCount, currentContextIndex;
    // Attempt to fail gracefully in lesser browsers
    if (!document.getElementsByTagName) {
        return new Array();
    }
    // Split selector in to tokens
    var tokens = selector.split(' ');
    var currentContext = new Array(document);
    for (var i = 0; i < tokens.length; i++) {
        var token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');
        if (token.indexOf('#') > -1) {
            // Token is an Id selector
            bits = token.split('#');
            tagName = bits[0];
            var id = bits[1];
            var element = document.getElementById(id);
            if (!element) {
                // Id not found, return false
                return new Array();
            }
            if (tagName && element.nodeName.toLowerCase() != tagName) {
                // Tag with that Id not found, return false
                return new Array();
            }
            // Set currentContext to contain just this element
            currentContext = new Array(element);
            continue; // Skip to next token
        }
        if (token.indexOf('.') > -1) {
            // Token contains a class selector
            bits = token.split('.');
            tagName = bits[0];
            var className = bits[1];
            if (!tagName) {
                tagName = '*';
            }
            // Get elements matching tag, filter them for class selector
            found = new Array;
            foundCount = 0;
            for (var j = 0; j < currentContext.length; j++) {
                if (tagName == '*') {
                    elements = getAllChildren(currentContext[j]);
                } else {
                    elements = currentContext[j].getElementsByTagName(tagName);
                }
                for (var k = 0; k < elements.length; k++) {
                    found[foundCount++] = elements[k];
                }
            }
            currentContext = new Array;
            currentContextIndex = 0;
            for (var m = 0; m < found.length; m++) {
                var classAttr = found[m].className ? found[m].className : found[m].getAttribute('class');
                if (classAttr) {
                    var classNames = classAttr.split(' ');
                    for (var n = 0; n < classNames.length; n++) {
                        if (className == classNames[n]) {
                            currentContext[currentContextIndex++] = found[m];
                            break;
                        }
                    }
                }
                /* Opera 6 does not support \b
                if (classAttr && classAttr.match(new RegExp('\\b'+className+'\\b'))) {
                  currentContext[currentContextIndex++] = found[k];
                } */
            }
            continue; // Skip to next token
        }
        // Code to deal with attribute selectors
        if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
            tagName = RegExp.$1;
            var attrName = RegExp.$2;
            var attrOperator = RegExp.$3;
            var attrValue = RegExp.$4;
            if (!tagName) {
                tagName = '*';
            }
            // Grab all of the tagName elements within current context
            found = new Array;
            foundCount = 0;
            for (var x = 0; x < currentContext.length; x++) {
                if (tagName == '*') {
                    elements = getAllChildren(currentContext[x]);
                } else {
                    elements = currentContext[x].getElementsByTagName(tagName);
                }
                for (var y = 0; y < elements.length; y++) {
                    found[foundCount++] = elements[y];
                }
            }
            currentContext = new Array;
            currentContextIndex = 0;
            var checkFunction; // This function will be used to filter the elements
            switch (attrOperator) {
                case '=': // Equality
                    checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
                    break;
                case '~': // Match one of space seperated words
                    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
                    break;
                case '|': // Match start with value followed by optional hyphen
                    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
                    break;
                case '^': // Match starts with value
                    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
                    break;
                case '$': // Match ends with value - fails with "Warning" in Opera 7
                    checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
                    break;
                case '*': // Match ends with value
                    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
                    break;
                default :
                    // Just test for existence of attribute
                    checkFunction = function(e) { return e.getAttribute(attrName); };
            }
            currentContext = new Array;
            currentContextIndex = 0;
            for (var z = 0; z < found.length; z++) {
                if (checkFunction(found[z])) {
                    currentContext[currentContextIndex++] = found[z];
                }
            }
            // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
            continue; // Skip to next token
        }
        // If we get here, token is just an element (not a class or Id selector)
        tagName = token;
        found = new Array;
        foundCount = 0;
        for (var a = 0; a < currentContext.length; a++) {
            elements = currentContext[a].getElementsByTagName(tagName);
            for (var b = 0; b < elements.length; b++) {
                found[foundCount++] = elements[b];
            }
        }
        currentContext = found;
    }
    return currentContext;
}

/**
 * Binds an event handler function to a given node, so that the function executes
 * when an event of the particular, given type arrives at the node either as event
 * target or during event propagation. Existing events, either defined as a tag
 * attribute or binded dynamically, are not affected.
 * <p>
 * Note: If this has to work in IE/Mac with existing events as tag attributes
 * the call to bind a new event has to follow the target node in the source
 * code.
 *
 * @param target           the node to which the event will be binded
 * @param eventType        a string of one event type (without the "on" prefix)
 *                         known to the browser's object model
 * @param listenerFunction a reference to the function to execute, when the node
 *                         hears the event type
 * @param useCapture       a Boolean value. If <code>true</code>, the node listens
 *                         for the event type only while the event propagates
 *                         toward the target node. If <code>false</code>, the
 *                         node listens only when the event bubbles outward from
 *                         the event target. The typical setting of this parameter
 *                         is <code>false</code> and if omitted, it falls back to
 *                         that value.
 * @author                 klaus.hartl (24.03.2005)
 */
function addEventHandler(target, eventType, listenerFunction, useCapture) {
    var result;
    if (target.addEventListener) {
        // W3C DOM approach
        useCapture = (typeof useCapture == 'boolean') ? useCapture : false;
        target.addEventListener(eventType, listenerFunction, useCapture);
        result = true;
    } else if (target.attachEvent) {
        // IE/Win DOM approach
        var r = target.attachEvent('on' + eventType, listenerFunction);
        result = r;
    } else {
        // fallback approach (IE/Mac and anything else that gets this far)
        var onEv = 'on' + eventType;
	    // if there's an existing event handler function
        if(typeof target[onEv] == 'function') {
            // store it
            var existing = target[onEv];
            // attach new onload handler
            target[onEv] = function() {
                // call existing function
                existing();
                // call given function
                listenerFunction();
            };
        } else {
            target[onEv] = listenerFunction;
        }
        return true; // do not use EventCash
    }
    EventCache.add(target, eventType, listenerFunction, useCapture);
    return result;
}

/**
 *
 */
function toggleValue(evt) {
    evt = (evt) ? evt : ((event) ? event : null);
    if (evt) {
        var elem = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
        if (elem) {
            if (!elem.defaultValue) {
                elem.defaultValue = elem.value;
            }
            if (elem.value == elem.defaultValue) {
                elem.value = '';
            } else if (elem.value == '') {
                elem.value = elem.defaultValue;
            }
        }
    }
}

// destroy events on unload to prevent memory leakage in IE-Win
addEventHandler(window, 'unload', EventCache.flush);