// JAVASCRIPT LIBRARY

// constants
var XHTML_NS_URI = 'http://www.w3.org/1999/xhtml';

// global variable to store reference to popup window, using the window's name is useless in IE/Win
var popup = '';

/**
 * 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;
    };
}

/**
 * Implements trim method for the String object.
 * <p>
 * Usage:
 * <p>
 * <code>trimmedString = rawString.trim()</code>
 *
 * @author klaus.hartl (24.06.2005)
 */
String.prototype.trim = function() {
    var s = this;
    s = s.replace(/^\s*(.*)/, "$1");
    s = s.replace(/(.*?)\s*$/, "$1");
    return s;
}

/**
 * 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;
    }
}

/**
 * Removes the given element from an array.
 *
 * @author klaus.hartl (13.02.2006)
 */
Array.prototype.remove = function(item) {
    var temp = new Array();
    for (var j = 0; j < this.length ; j++) {
        if (this[j] !== item) {
            temp = temp.concat(this[j]);
        }
    }
    this.length = 0;
    for (j = 0; j < temp.length; j++) {
        this[this.length] = temp[j]
    }
    return this;
}

/**
 * Converts HTMLCollection object to array object.
 *
 * @param  HTMLCollection object.
 * @return array object
 * @author klaus.hartl (24.06.2005)
 */
function collectionToArray(collection) {
	var a = new Array();
	for (var i = 0; i < collection.length; i++) {
        a[a.length] = collection[i];
    }
	return a;
}

/**
 * Checks, if an object is an array.
 *
 * @return <code>true</code>, if the given object <code>obj</code>
 *         is an array, otherwise <code>false</code> is returned.
 *
 * @author klaus.hartl (24.03.2005)
 */
function isArray(obj) {
    var isA = false;
    if (typeof obj == "object" && obj != null) { // null is an object
        if (obj.constructor && obj.constructor.toString().indexOf("Array") != -1) {
            isA = true;
        }
    }
    return isA;
}

/**
 * Retrieves the actual document language. Therefore it analyzes the
 * <code>xml:lang</code> and <code>lang</code> attribute of the
 * <code>html</code> element. The <code>xml:lang</code> attribute takes
 * precedence.
 *
 * @return a string identifying the document's language. If no language
 *         is specified, an empty string is returned.
 *
 * @author klaus.hartl (29.03.2005)
 */
function getDocumentLanguage() {
    var language = '';
    if (document.body.parentNode) {
        var htmlElem = document.body.parentNode;
        if (htmlElem.getAttribute('xml:lang')) {
            language = htmlElem.getAttribute('xml:lang');
        } else if (typeof htmlElem.lang == 'string') {
            language = htmlElem.lang;
        }
    }
    return language;
}

/**
 * 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;
}

/*	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;
			};
		}
	};
}();

// destroy events on unload to prevent memory leakage in IE-Win
addEventHandler(window, 'unload', EventCache.flush);

/**
 * 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;
}

/**
 * Firefox sometimes requires new rendering after changing the DOM structure.
 *
 * @param elem reference to the block level element, that should be rendered anew
 *             (for example document.body)
 * @author     klaus.hartl (18.08.2005)
 */
function redraw(elem) {
    elem.style.display = 'none';
    elem.style.display = 'block';
}

/**
 * Inserts a node as first child node.
 */
function prependChild(parent, node) {
    parent.insertBefore(node, parent.firstChild);
}

function toggleValue(evt) {
    evt = (evt) ? evt : ((event) ? event : null);
    if (evt) {
        var elem = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
        if (elem) {
            // store default value
            if (!elem.defaultValue) {
                elem.defaultValue = elem.value;
            }
            if (elem.value == elem.defaultValue) {
                elem.value = '';
            } else if (elem.value == '') {
                elem.value = elem.defaultValue;
            }
        }
    }
}

// Helper method to open a new window
function openNewWindow(url, target, windowFeatures) {
    var win = window.open(url, target, windowFeatures);
    win.focus();
    return win;
}

// To change dynamically the click event and title attribute for links, which
// should open in a new window.
// Compliant to XHTML 1.0 Strict and WCAG/BITV.
// Note: the click event also includes the keypress event in modern browsers.
//
// Insert this JS-file to change your external links. If JS is enabled, all links
// with the class attribute "external" (multiple classes supported), will open in
// a new window

// BEGIN configuration

// Globals

// Default language
var defaultLanguage = 'de';

// The features of the new window
var WINDOW_DEFAULT_FEATURES = 'directories,location,menubar,resizable,scrollbars,status,toolbar';

var titlePrefix = new Array();
// Use iso language codes as array keys as we retrieve the value of the xml:lang attribute as key for the current document language
// english
titlePrefix['en'] = 'New window: ';
// german
titlePrefix['de'] = 'Neues Fenster: ';
// french
titlePrefix['fr'] = 'Nouvelle fen' + String.fromCharCode(234) + 'tre: ';
// spanish
titlePrefix['es'] = 'Ventana nueva: ';
// polish
titlePrefix['pl'] = 'Nowe okno: ';
// define more if needed

// END configuration

// Helper method to get the title prefix in the correct document language
function getTitlePrefix(language) {
    var s = titlePrefix[defaultLanguage];
    if (typeof titlePrefix[language] == 'string') {
        s = titlePrefix[language];
    }
    return s;
}

// Helper method to retrieve only the child text node values of an element node
function getTextNodeValues(elementNode) {
    var text = '';
    if (elementNode.nodeType && elementNode.nodeType == 1 && elementNode.childNodes) {
        var children = elementNode.childNodes;
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            if (child.nodeType == 3) {
                // text node
                text += child.nodeValue;
            } else if (child.nodeType == 1) {
                // element node
                text += getTextNodeValues(child);
            }
        }
    }
    return text;
}

// create history.back() link
function renderHistoryBackLink(refId, text) {
    var refNode = document.getElementById(refId);
    if (refNode && typeof text == 'string') {
        var p = (document.createElementNS) ? document.createElementNS(XHTML_NS_URI, 'p') : document.createElement('p');
        p.className = 'links';
        var a = (document.createElementNS) ? document.createElementNS(XHTML_NS_URI, 'a') : document.createElement('a');
        a.className = 'back';
        a.setAttribute('href', '#');
        a.onclick = function() {
            history.back();
            return false;
        };
        var textNode = document.createTextNode(text);
        a.appendChild(textNode);
        p.appendChild(a);
        refNode.parentNode.insertBefore(p, refNode);
    }
}

// AJAX
var POPUP_DEFAULT_FEATURES = 'width=300,height=350,top=100,left=100,resizable,scrollbars,status';
var req;

if (window.XMLHttpRequest) {
    // branch for native XMLHttpRequest object
    req = new XMLHttpRequest();
    // Opera 8.0 does not support method setRequestHeader, fixed in Opera 8.01...
    // We only need this check, if we want to make POST requests.
    // We need this check here, because in IE/Win "typeof req.setRequestHeader"
    // returns "unknown"
    if (typeof req.setRequestHeader != 'function') {
        req = null;
    }
} else if (window.ActiveXObject) {
    // branch for IE/Windows ActiveX version
    req = new ActiveXObject('Microsoft.XMLHTTP');
}

function loadXMLDoc(url, postData) {
    postData = (typeof postData == 'string') ? postData : null;
    var requestMethod = (typeof postData == 'string') ? 'POST' : 'GET';
    if (req) {
        req.abort(); // abort a former request and set req.readyState back to 0
        req.onreadystatechange = processReqChange;
        req.open(requestMethod, url, true);
        if (requestMethod == 'POST') {
            req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
        }
        req.send(postData);
    }
}

function getTextNode(parentNode) {
    var value = '';
    if (parentNode.firstChild && parentNode.firstChild.nodeType == 3) {
        value = parentNode.firstChild.data;
    }
    return value;
}

function processReqChange() {
    // only if req shows "complete"
    if (req.readyState == 4) {
        // only if "OK"
        if (req.status == 200) {
            // processing...
            var response = req.responseXML.documentElement;
            var method = getTextNode(response.getElementsByTagName('method')[0]).trim();
            var result = getTextNode(response.getElementsByTagName('result')[0]).trim();
            eval(method + '(\'\', \'\', result)');
        } else {
            alert('There was a problem retrieving the XML data:\n' + req.statusText);
        }
    }
}

function handleDownload(url, input, response) {
    if (typeof response == 'string') {
        // Response mode...
        if (response.length > 0) {
            if (!popup.closed && popup.location) {
                popup.location.href = response;
            } else {
                popup = openNewWindow(response, 'shopping_cart', POPUP_DEFAULT_FEATURES);
            }
            if (window.focus) {
                popup.focus();
            }
        } else {
            alert('Das Dokument konnte nicht gespeichert werden.\nBitte versuchen Sie es erneut.');
        }
    } else {
        // Input mode...
        loadXMLDoc(url, input);
    }
}
