/**
 * Attach an event to an object, cross-browser compatible. Note that the event
 * parameter should not start with 'on', as it's not required by W3C-compatible
 * browsers. For Internet Explorer, the 'on' prefix is added automatically.
 */
function addEvent(object, event, handler) {
    // Stel de callback samen
    if (object.addEventListener && top.app) {
        var oldHandler = handler;
        handler = function() {
            try {
                oldHandler.apply(null, arguments);
            } catch (e) {
                top.app.catchException(e);
            }
        }
    }

    // Zorg ervoor dat de callback gekoppeld wordt
    if (object.attachEvent) {
        object.attachEvent('on' + event, handler);
    } else {
        object.addEventListener(event, handler, false);
    }
    return handler;
}

/**
 * Schakelt de mogelijkheid om tekst te selecteren uit op een bepaald element.
 * Dit is slechts cosmetisch en op geen enkele manier een beveiliging.
 */
function disableTextSelection(element) {
    if (typeof element.onselectstart != 'undefined') {                  // IE en Safari
        element.onselectstart = function(){return false;};
    } else if (typeof element.style.MozUserSelect != 'undefined') {     // Firefox / NS-familie
        element.style.MozUserSelect = 'none';
    } else if (typeof element.style.WebkitUserSelect != 'undefined') {  // Google Chrome / overige webkit-familie
        element.style.WebkitUserSelect = 'none';
    } else if (typeof element.onmousedown != 'undefined') {             // Opera
        element.onmousedown = function(){return false;};
    }

    // Standaard cursor
    element.style.cursor = 'default';
}

/**
 * Sets the text selection in an element to a specific text.
 */
document.setSelectionToText = function(elm, strText) {
    if (document.selection) {
        document.selection.createRange().text = strText;
    } else {
        var value = elm.value;
        elm.value = value.substring(0, elm.selectionStart) + strText + value.substring(elm.selectionEnd, value.length);
    }
}

/**
 * Provides an object that handles an element's events.
 */
function elementBehaviour(elementId) {
    var me = this;
    this.element = elm(elementId);
    this.className = this.element.className;
    this.element.onmouseover = function() { return me.mouseover(); }
    this.element.onmousedown = function() { return me.mousedown(); }
    this.element.onmouseout  = function() { return me.mouseout(); }
    this.element.onmouseup   = function() { return me.mouseup(); }
}
elementBehaviour.prototype.mouseover = function() { this.element.className = this.className + ' ' + this.className + '_hover'; }
elementBehaviour.prototype.mousedown = function() { this.element.className = this.className + ' ' + this.className + '_down'; }
elementBehaviour.prototype.mouseout  = function() { this.element.className = this.className; }
elementBehaviour.prototype.mouseup   = function() { this.element.className = this.className + ' ' + this.className + '_hover'; }

/**
 * Shorter way of calling document.getElementById().
 */
function elm(id) {
    return document.getElementById(id);
}

/**
 * Extends the Function object with an inheritance method.
 */
Function.prototype.extendsFrom = function(parentClass) {
    this.prototype = new parentClass;
    this.prototype.constructor = this;
}

/**
 * Retourneert de absolute positie van een element op de pagina.
 */
function getAbsolutePosition(element) {
    var r = {x: element.offsetLeft, y: element.offsetTop};
    if (element.offsetParent) {
        var parentPos = getAbsolutePosition(element.offsetParent);
        r.x += parentPos.x;
        r.y += parentPos.y;
    }
    return r;
}

/**
 * Returns the calculated CSS styles for an element.
 */
function getCalculatedStyle(targetElm) {
    if (document.defaultView) {
        return document.defaultView.getComputedStyle(targetElm, '');
    } else if (targetElm.currentStyle) {
        return targetElm.currentStyle;
    }
}

/**
 * Finds and returns the value of a cookie.
 */
function getCookie(name) {
    var dc = document.cookie;
    var prefix = name + '=';
    var begin = dc.indexOf('; ' + prefix);
    if (begin == -1) {
        begin = dc.indexOf(prefix);
        if (begin != 0) {
            return null;
        }
    } else {
        begin += 2;
    }
    var end = document.cookie.indexOf(';', begin);
    if (end == -1) {
        end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
}

/**
 * Retourneert de tekstuele data binnen een XML element.
 */
function getElementData(XMLElement) {
    if (!XMLElement) {
        return '';
    }
    var numberOfChildren = XMLElement.childNodes.length;
    var data = '';
    for (var i = 0; i < numberOfChildren; i++) {
        data += XMLElement.childNodes[i].nodeValue;
    }
    return data;
}

/**
 * The Ultimate getElementsByClassName
 * Authors:
 *   Written by Jonathan Snook, http://www.snook.ca/jonathan
 *   Add-ons by Robert Nyman, http://www.robertnyman.com (source)
 */
function getElementsByClassName(oElm, strTagName, strClassName) {
    if (!oElm) {
        return Array();
    }
    var arrElements = ((strTagName == '*') && oElm.all) ? oElm.all : oElm.getElementsByTagName(strTagName);
    var arrReturnElements = new Array();
    strClassName = strClassName.replace(/\-/g, "\\-");
    var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
    var oElement;
    for (var i = 0; i < arrElements.length; i++) {
        oElement = arrElements[i];
        if (oRegExp.test(oElement.className)) {
            arrReturnElements.push(oElement);
        }
    }
    return (arrReturnElements);
}

/**
 * Retourneert de JSON-notatie van een Javascript object of primitive.
 * @returns string Het object in JSON-formaat
 */
function getJSON(jsObject) {
    if (jsObject === null) {
        return 'null';
    }
    var objType = (typeof jsObject).toLowerCase();
    switch (objType) {
        case 'number':
            return String(jsObject);
        case 'string':
            return '"' + jsObject.replace(/"/g, '\\"') + '"';
        case 'boolean':
            return (jsObject ? 'true' : 'false');
        case 'object':
            var value = '', first = true;
            if (jsObject instanceof Array) {
                value += '[';
                var i, len = jsObject.length;
                for (i = 0; i < len; i++) {
                    if (!first) {
                        value += ',';
                    }
                    value += getJSON(jsObject[i]);
                    first = false;
                }
                value += ']';
            } else {
                value += '{';
                for (idx in jsObject) {
                    if (!first) {
                        value += ',';
                    }
                    value += getJSON(idx) + ':' + getJSON(jsObject[idx]);
                    first = false;
                }
                value += '}';
            }
            return value;
    }
}

/**
 * Retourneert de muispositie op het scherm op basis van een muisevent.
 */
function getMousePositionFromEvent(e) {
    var r = {x: 0, y: 0};
    if (e.pageX && e.pageY) {
        r.x = e.pageX;
        r.y = e.pageY;
    } else if (e.clientX && e.clientY) {
        r.x = e.clientX;
        r.y = e.clientY;
    }
    return r;
}

/**
 * Returns the height of the window.
 */
function getWindowHeight() {
    var windowHeight = 0;
    if (typeof(window.innerHeight) == 'number') {
        windowHeight = window.innerHeight;
    } else {
        if (document.documentElement && document.documentElement.clientHeight) {
            windowHeight = document.documentElement.clientHeight;
        } else {
            if (document.body && document.body.clientHeight) {
                windowHeight = document.body.clientHeight;
            }
        }
    }
    return windowHeight;
}

/**
 * Retourneert de breedte van het browserscherm.
 */
function getWindowWidth() {
    var windowWidth = 0;
    if (typeof(window.innerWidth) == 'number') {
        windowWidth = window.innerWidth;
    } else if (document.documentElement && document.documentElement.clientWidth) {
        windowWidth = document.documentElement.clientWidth;
    } else if (document.body && document.body.clientWidth) {
        windowWidth = document.body.clientWidth;
    }
    return windowWidth;
}

/**
 * Verbergt een element op basis van de naam.
 */
function hide(objName) {
    setVisibility(elm(objName), 'none');
}

/**
 * Retourneert de vertaling voor een term.
 */
function i18nGet(term) {
    if (typeof i18nTranslations == 'undefined') {
        throw new Error('Translations not available');
    }
    if (!term in i18nTranslations) {
        throw new Error('Term \'' + term + '\' not translated');
    }
    return i18nTranslations[term];
}

/**
 * Voegt een element toe na een bepaald ander element.
 */
function insertAfter(elmNew, elmNode) {
    if (!elmNode.parentNode) {
        return;
    }
    if (elmNode.nextSibling) {
        elmNode.parentNode.insertBefore(elmNew, elmNode.nextSibling);
    } else {
        elmNode.parentNode.appendChild(elmNew);
    }
}

/**
 * Formats numbers like the PHP function number_format().
 * @source http://www.fobit.com/index.php?article=JavaScript%3A%20number_format
 */
Number.prototype.format = function(decimals, dec_point, thousands_sep) {
    var exponent = '';
    var number = this;
    var numberstr = number.toString();
    var eindex = numberstr.indexOf('e');
    if (eindex > -1) {
        exponent = numberstr.substring(eindex);
        number = parseFloat(numberstr.substring(0, eindex));
    }
    if (decimals != null) {
        var temp = Math.pow(10, decimals);
        number = Math.round(number * temp) / temp;
    }
    var sign = number < 0 ? '-' : '';
    var integer = (number > 0 ?
        Math.floor(number) : Math.abs(Math.ceil(number))).toString();
    var fractional = number.toString().substring(integer.length + sign.length);
    dec_point = dec_point != null ? dec_point : '.';
    fractional = decimals != null && decimals > 0 || fractional.length > 1 ?
        (dec_point + fractional.substring(1)) : '';
    if (decimals != null && decimals > 0) {
        for (i = fractional.length - 1, z = decimals; i < z; ++i) {
            fractional += '0';
        }
    }
    thousands_sep = (thousands_sep != dec_point || fractional.length == 0) ?
        thousands_sep : null;
    if (thousands_sep != null && thousands_sep != '') {
        for (i = integer.length - 3; i > 0; i -= 3) {
            integer = integer.substring(0, i) + thousands_sep + integer.substring(i);
        }
    }
    return sign + integer + fractional + exponent;
}

/**
 * Laadt een afbeelding in zonder deze weer te geven. Essentiëel als een afbeelding
 * middels Javascript wordt weergegeven en geen laadtijd mag hebben.
 */
function preloadImage(imageSource) {
    var img = new Image();
    img.src = imageSource;
}

/**
 * Schakelt de standaard actie van een event uit.
 */
function preventEventDefault(event) {
    if (event.preventDefault) {
        event.preventDefault();
    } else {
        event.returnValue = false;
    }
}

/**
 * Voorkomt dat een event gepropageerd (of gebubblet) wordt naar andere elementen.
 */
function preventEventPropagation(event) {
    if (event.stopPropagation) {
        event.stopPropagation();
    } else {
        event.cancelBubble = true;
    }
}

/**
 * Verwijdert alle child elements uit een element middels DOM manipulation.
 */
function removeChildren(element) {
    if (!element) {
        throw new Error('removeChildren(): invalid element as argument');
    }
    while (element.hasChildNodes()) {
        element.removeChild(element.firstChild);
    }
}

/**
 * Verwijdert een event uit een object. Bij de event string moet de 'on' op het begin van
 * de naam van de event weggelaten worden.
 */
function removeEvent(object, event, handler) {
    if (object.detachEvent) {
        object.detachEvent('on' + event, handler);
    } else {
        object.removeEventListener(event, handler, false);
    }
}

/**
 * Sets a cookie using a name, value, duration in seconds and path information.
 */
function setCookie(name, value, duration, path, domain, secure) {
    var expires = new Date();
    var today = new Date();
    expires.setTime(today.getTime() + duration * 1000);
    document.cookie = name + '=' + escape(value)
        + (expires ? '; expires=' + expires.toGMTString() : '')
        + (path ? '; path=' + path : '')
        + (domain ? '; domain=' + domain : '')
        + (secure ? '; secure' : '');
}

/**
 * Stelt de hoogte van een element in op de maximale hoogte mogelijk in het scherm op basis
 * van het element zelf en een array met namen van elementen waarvan de hoogte van de
 * hoogte van het scherm wordt afgetrokken.
 */
function setMaxHeight(elementId, arrIds, extraMargin) {
    // Bepaal het element
    var elem = elm(elementId);
    if (!elem) {
        alert('Element \'' + elementId + '\' kon niet gevonden worden.');
        return;
    }

    // Verkrijg de paginahoogte
    var height = getWindowHeight();

    // Haal de extra marge van de hoogte af
    if (!extraMargin) {
        extraMargin = 0;
    }
    height -= extraMargin;

    // Verwijder diverse marges van het element
    var props = getCalculatedStyle(elem);
    if (props) {
        var checkStyles = ['borderTopWidth', 'borderBottomWidth', 'paddingBottom', 'paddingTop'];
        for (idx in checkStyles) {
            var propValue = parseInt(props[checkStyles[idx]], 10);
            if (propValue > 0) {
                height -= propValue;
            }
        }
    }

    // Haal de hoogte van de array met elementen eraf
    for (index in arrIds) {
        var subtractElement = elm(arrIds[index]);
        if (!subtractElement) {
            continue;
        }
        height -= subtractElement.offsetHeight;
    }

    // Zorg ervoor dat het element nooit een negatieve hoogte krijgt
    height = Math.max(height, 0);

    // Stel de hoogte in
    var JSelementObject = eval('if(typeof ' + elementId + ' == \'object\')(' + elementId + ');else(null)');
    if (JSelementObject && JSelementObject.setHeight) {
        JSelementObject.setHeight(height);
    } else {
        elem.style.height = height + 'px';
    }
}

/**
 * Stelt de opacity van een element in (op basis van 0..1).
 * @param HTMLElement Het element waarvan de opacity ingesteld moet worden
 * @param double De opacity van 0..1
 */
function setOpacity(element, opacity) {
    if ((typeof element).toLowerCase() == 'string') {
        element = elm(element);
    }
    if (element.filters) {
        if (opacity == 1) {
            element.style.filter = '';
        } else {
            element.style.filter = 'alpha(opacity=' + opacity * 100 + ')';
        }
    } else {
        element.style.opacity = opacity;
    }
}

/**
 * Verandert de zichtbaarheid van een element van display:none naar display:block (standaard) en andersom.
 * @param obj Het element dat van zichtbaarheid veranderd dient te worden
 * @param displayStyle Het type display ('block' of 'inline')
 * @param toggle True als het object getoggled dient te worden qua zichtbaarheid
 */
function setVisibility(obj, displayStyle, toggle) {
    if (!displayStyle) {
        displayStyle = 'block';
    }
    if (toggle && (obj.style.display == displayStyle)) {
        obj.style.display = 'none';
    } else {
        obj.style.display = displayStyle;
    }
}

/**
 * Toont een element op basis van de naam.
 */
function show(objName) {
    setVisibility(elm(objName), 'block');
}

/**
 * Toont een popup aan de hand van een URI en diverse instellingen.
 * @param string De URI
 * @param integer De breedte van de popup
 * @param integer De hoogte van de poup
 * @param boolean Of de popup modal is
 * @param boolean Of de popup resizable moet zijn
 * @param boolean Of de popup scrollbars moet tonen indien nodig
 */
function showPopup(uri, width, height, isModal, isResizable, hasScrollbars) {
    var newWindow, options;
    if (isModal) {
        options = 'dialogheight:' + height + 'px;dialogwidth:' + width + 'px;resizable=' + (isResizable ? '1' : '0') + ';scroll' + (hasScrollbars ? '1' : '0');
        newWindow = window.showModalDialog(uri, null, options);
    } else {
        options = 'height=' + height + ',width=' + width + ',resizable=' + (isResizable ? '1' : '0') + ',scrollbars=' + (hasScrollbars ? '1' : '0');
        newWindow = window.open(uri, '', options);
    }
    return newWindow;
}

/**
 * Counts the number of occurrences of a specific character or substring
 * and returns this number.
 */
String.prototype.countOccurrences = function(str) {
    var matches = this.match(new RegExp(str.escapeForRegex(), 'g'));
    if (!matches) {
        return 0;
    }
    return matches.length;
}

/**
 * Escapes a string so it can be used within a regular expression.
 */
String.prototype.escapeForRegex = function() {
    var escapeChars = '.\\()[]^$+{}?!*';
    var chr, result = '';
    var len = this.length;
    for (var i = 0; i < len; i++) {
        chr = this.charAt(i);
        if (escapeChars.indexOf(chr) >= 0) {
            result += '\\';
        }
        result += chr;
    }
    return result;
}

/**
 * Removes all whitespace at the beginning and end of a string.
 */
String.prototype.trim = function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
}

/**
 * Togglet de zichtbaarheid van een object.
 */
function toggle(objName) {
    setVisibility(elm(objName), 'block', true);
}