window.kgst = window.kgst || (function() {
    var jLog = null,
        consoleAvailable = (typeof console != 'undefined' && console.log),
        Stopwatch = function() {
            this.startTime = +new Date();
        }

    Stopwatch.prototype = {
        reset: function() {
            this.startTime = +new Date();
        },
        getTime: function(resetAfter) {
            var t = ((+new Date()) - this.startTime);
            if (resetAfter) this.reset();
            return t;
        }
    };

    return {
        DEFAULT_POPUP_SETTINGS: 'location=yes,menubar=no,resizable=yes,scrollbars=no,status=yes,height=500,width=1000',
        PRINT_URL: '/meinKGSt/adminVR?rmtparams=cm10YWM9YWRtaW5Ja29uJnJtdGF1PWh0dHAlM0ElMkYlMkZsb2NhbGhvc3QlM0E1Nzg4MCUyRmtn_c3Qtd2ViYXBwJTJGYWRtaW5VYmVyc2NocmlmdFp1b3JkbnVuZyUyRnByaW50',
        IS_LOCAL_DEV: !!top.location.href.match(/http:\/\/[^\/\d:]*:\d+\/kgst-webapp\//),
        DEBUG: false,
        LOGGER: {
            log: function(s) {
                if (!kgst.DEBUG) return;
                if (consoleAvailable) {
                    console.log.apply(this, arguments);
                    return;
                } else if (!jLog) {
                    jLog = $('<textarea ondblclick="this.value=\'\'" id="treeLogger" style="font-size:10px;font-family:tahoma;color:red;height:100px;width:180px"></textarea>');
                    $('#kgst-logo-subline').html('').append(jLog);
                }
                jLog.append('** ' + s + "\n<br/>");
            }
        },
        Stopwatch: Stopwatch
    };

})();

kgst.Tools = {
    clearInput: function(field) {
        if (field.defaultValue == field.value)
            field.value = "";
    },


    /*
     * Adds a nocache parameter to the given URL.
     * @param {string} url
     * @return {string} url with ?/&x=1234..
     */
    antiCacheURL: function(url) {
        var time = (+new Date),
            c = (url.indexOf('?') >= 0) ? '&' : '?',
            param = c + 'x=' + time.toString(16);

        return url + param;
    },

    /**
     * Jumps to the login page.
     */
    jumpToLogin: function() {
        top.location.href = '/login/anmelden.dot';
    },

    fillInput: function(field) {
        if (field.value == "")
            field.value = field.defaultValue;
    },

    updateProvinces: function(province) {
        var provinceSelect = province;
        var country = document.getElementById("country").value;
        /* Delete old options */
        provinceSelect.length = 0;
        /* Add new options */
        if (country != "") {
            var newProvinceID = "provinces-for-" + country;
            var newLabelID = "province-label-" + country;
            var newSelectOptions = document.getElementById(newProvinceID).options;
            for (i = 0; i < newSelectOptions.length; i++) {
                provinceSelect.options[provinceSelect.options.length] = new Option(newSelectOptions[i].text, newSelectOptions[i].value, newSelectOptions[i].name);
            }
            /* Changing the Label */
            document.getElementById("province-name").innerHTML = document.getElementById(newLabelID).innerHTML;
        }
    },

    updateDistricts: function(province, district) {
        var districtSelect = district;
        var currentID = province.value;
        /** Delete old district options **/
        districtSelect.length = 0;
        if (currentID != "") {
            var newDistrictID = "districtId-for-" + currentID;
            var newSelectOptions = document.getElementById(newDistrictID).options;
            for (i = 0; i < newSelectOptions.length; i++) {
                districtSelect.options[districtSelect.options.length] = new Option(newSelectOptions[i].text, newSelectOptions[i].value, newSelectOptions[i].name);
            }

        }
        //alert(districtSelect + ":" + currentID);
    },

    clearDistricts: function(district) {
        var districtSelect = district;
        districtSelect.length = 0;
    },

    urlencode: function(str) {
        // http://kevin.vanzonneveld.net
        // +   original by: Philip Peterson
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: AJ
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: travc
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Lars Fischer
        // %          note 1: info on what encoding functions to use from: http://xkr.us/articles/javascript/encode-compare/
        // *     example 1: urlencode('Kevin van Zonneveld!');
        // *     returns 1: 'Kevin+van+Zonneveld%21'
        // *     example 2: urlencode('http://kevin.vanzonneveld.net/');
        // *     returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
        // *     example 3: urlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
        // *     returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'

        // The histogram is identical to the one in urldecode.
        // modified LeP: separated keys/vals to avoid false malware detection by McAfee GW-Edition
        var histoKeys = ["'",'(',')','*','~','!','%20','\u00DC','\u00FC','\u00C4','\u00E4','\u00D6','\u00F6','\u00DF','\u20AC','\u0081','\u201A','\u0192','\u201E','\u2026','\u2020','\u2021','\u02C6','\u2030','\u0160','\u2039','\u0152','\u008D','\u017D','\u008F','\u0090','\u2018','\u2019','\u201C','\u201D','\u2022','\u2013','\u2014','\u02DC','\u2122','\u0161','\u203A','\u0153','\u009D','\u017E','\u0178'],
            histoVals = ['%27','%28','%29','%2A','%7E','%21','+','%DC','%FC','%D4','%E4','%D6','%F6','%DF','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F'],
            histogram = {},
            unicodeStr = '',
            hexEscStr = '',
            ret = (str + '').toString(),
            replacer = function(search, replace, str) {
                var tmp_arr = [];
                tmp_arr = str.split(search);
                return tmp_arr.join(replace);
            };

        for (var i = 0; i < histoKeys.length; i++) {
            histogram[histoKeys[i]] = histoVals[i];
        }

        // Begin with encodeURIComponent, which most resembles PHP's encoding functions
        ret = encodeURIComponent(ret);

        for (unicodeStr in histogram) {
            hexEscStr = histogram[unicodeStr];
            ret = replacer(unicodeStr, hexEscStr, ret); // Custom replace. No regexing
        }

        // Uppercase for full PHP compatibility
        return ret.replace(/(\%([a-z0-9]{2}))/g, function(full, m1, m2) {
            return "%" + m2.toUpperCase();
        });
    },

    highlightInvalidElement: function(element) {
        element.style.border = "1px solid red";
        element.style.color = "red";
    },

    updateHitsPerPage: function(hitsPerPage) {
        var hpp_s = document.getElementById('hitsPerPage_s');
        if (hpp_s) {
            hpp_s.value = hitsPerPage;
        }
    },


    /**
     * Makes a table (its TRs) selectable (*** EXPERIMENTAL ***)
     * @param {String} tableSelector the jQuery selector of the table
     * @todo do sensible stuff, add parameter for hidden field(s) etc.
     */
    makeSelectable: function(tableSelector) {
        var console = (typeof console == 'undefined') ? {log:function() {
        }} : console;

        $(tableSelector).selectable({
            filter: 'tr',
            cancel: 'a',
            selecting: function(e, ui) {
                console.log('selecting %o', ui.selecting);
            },
            selected: function(e, ui) {
                var tr = ui.selected;
                console.log(e);
                console.log(ui);
                if (e.ctrlKey && $(tr).hasClass('previouslySelected')) {
                    $(tr).removeClass('ui-selected').removeClass('previouslySelected');
                } else {
                    $(tr).addClass('previouslySelected');
                }
            },
            unselected: function(e, ui) {
                var tr = ui.unselected;
                console.log('unselected %o', ui.unselected);
                $(tr).removeClass('previouslySelected');
            }
        });
    },


    form2action: function(f) {
        var formGetURL = $('a.formURL:first', f).attr('href') || 0,
            dataParams = $(f).serialize(),
            match;

        if (!( match = formGetURL.match(/^(.*rmtparams=)(.*)$/) )) return false;

        var baseURL = match[1],
            base64encodedParams = match[2],
            base64decodedParams = decodeBase64(base64encodedParams);

        if (!( match = base64decodedParams.match(/rmtau=([^&]*)/) )) return false;

        var encodedURL = match[1],
            decodedURL = decodeURIComponent(encodedURL),
            PARAM_CHAR = (decodedURL.indexOf('?') < 0) ? '?' : '&',
            newURL = decodedURL + PARAM_CHAR + dataParams,
            newURLEncoded = encodeURIComponent(newURL),
            newBase64DecodedParams = base64decodedParams.replace(/rmtau=[^&]*/, 'rmtau=' + newURLEncoded),
            newBase64EncodedParams = encodeBase64(newBase64DecodedParams),
            newActionURL = baseURL + newBase64EncodedParams;


        return newActionURL;
    },



    /**
     * Opens a popup with the given name.
     * @return true if successful, otherwise false
     */
    openWertePopup: function(name, f) {
        f.method = 'post';

        try {
            var url = kgst.Tools.form2action(f);
            if (!url) throw 'invalid url';
            var popup = window.open(url, name || 'werteErfassen', kgst.DEFAULT_POPUP_SETTINGS);
            popup.focus()
            return false;
        } catch (e) {
            alert(e);
            return true;
        }
    },

    openPopup: function(name, a) {
        try {
            if (!a || !a.href) throw 'invalid url';
            var popup = window.open(a.href, name || 'ikonPopup', kgst.DEFAULT_POPUP_SETTINGS);
            popup.focus()
            return false;
        } catch (e) {
            alert(e);
            if (a) {
                a.target = '_blank';
            }
            return true;
        }
    },

    /**
     * Translates a given dotCMS url into a desciptor object like {controller:'abc',action:'def',id:'123}.
     * @params {string} url
     * @return {object|null}
     */
    url2object: function(url) {
        if (!/rmtparams=(.*)/.exec(url)) return null;
        var decodedURL = decodeURIComponent(decodeBase64(RegExp.$1)) || '';
        var matches = decodedURL.match(/kgst-webapp\/([^\/]+)\/([^\/]+)\/(\d*)/);
        if (!matches || matches.length < 4) return null;
        return {
            controller: matches[1],
            action: matches[2],
            id: matches[3]
        };
    },

    /**
     * Deletes a Jobangebot's PDF attachment.
     * @param {String} DEL_PDF_URL the ajax url for the delete call
     * @return false (always)
     */
    deleteJobangebotPdfAnhang: function(DEL_PDF_URL) {
        if (!confirm('Möchten Sie die PDF-Datei wirklich löschen?')) return false;
        var jDiv = $('#delPdfDiv');
        jDiv.html('<img src="/kgst-webapp/images/spinner.gif" alt="Bitte warten" />');
        $.getJSON(DEL_PDF_URL, {}, function(data) {
            data = data || {'error':'Es ist ein Fehler aufgetreten.'}
            if (data.error) {
                jDiv.html(data.error).css('color', 'red');
            } else {
                jDiv.html(data.success).css('color', 'green');
                $('#pdfNotice').fadeOut(400);
            }
        });
        return false;
    },

    /**
     * Extends the (dotcms-generated) breadcrumb with LIs of a given list.
     * @param {String} listID the id of the items to add to the BC
     * @param {String} baseURL (optional) URL for the previously non-linked last breadcrumb item.
     */
    extendBreadcrumb: function(listID, baseURL) {
        var jBCDiv = $('#breadcrumb'),
            jBC = $('#breadcrumb ul:first'),
            jLastLI = (jBC.length) ? $('li:last', jBC) : 0,
            jMoreItems = $('#' + listID + ' li');

        if (!jLastLI || !jMoreItems.length) return;

        var sepImg = $('img:first', jBC).get(0) || $('<img alt="ct_img" src="/global/images/template/breadcrumb-seperator.gif"/>')[0],
            INIT_HEIGHT = jBCDiv.height();

        if (baseURL && !$('a', jLastLI).length) {
            var jA = $('<a>').attr('href', baseURL).html(jLastLI.html());
            jLastLI.html(jA);
        }

        var prevLI = jLastLI[0];
        jMoreItems.each(function(i, li) {
            prevLI.appendChild(sepImg.cloneNode(1));
            jBC.append(li);
            prevLI = li;
        });

        var fixRuns = 100,
            liIndex = -1,
            newHeight,
            REDUCE_STEP = 13,
            totalBytes = 0,
            orderedItems = [];

        jMoreItems.each(function(i, li) {
            var jLI = $(li),
                len = (jLI.text() || '').length;
            totalBytes += len;
            orderedItems[orderedItems.length] = {
                len: len,
                cycles: 0,
                jLI: jLI
            };
        });

        orderedItems = orderedItems.sort(function(a, b) {
            return (a.len < b.len) ? 1 : -1;	// rev-sort by length
        });

        $.each(orderedItems, function(i, o) {
            o.cycles = Math.round((o.len / totalBytes) * (totalBytes / REDUCE_STEP));
        });

        // console.log('orderedItems: %o', orderedItems);

        var itemIndex = -1,
            cyclesLeft = 0,
            elem = null,
            html = '';

        while (--fixRuns > 0 && (newHeight = jBCDiv.height()) > INIT_HEIGHT) {
            if (!cyclesLeft) {
                itemIndex = (++itemIndex) % orderedItems.length;
                var item = orderedItems[itemIndex];
                cyclesLeft = item.cycles;
                elem = item.jLI[0].getElementsByTagName('a')[0] || item.jLI[0];
            }
            cyclesLeft--;

            html = elem.innerHTML;

            // console.log('itemIndex: %n, cycles: %n, elem: %o', itemIndex, cyclesLeft, elem);

            if (html.length < REDUCE_STEP) continue;

            html = html.substring(0, html.length - REDUCE_STEP) + ' [...]';
            // console.log('html: %s', html);

            if (!elem.title) {
                elem.title = $(elem).text();
            }

            elem.innerHTML = html;
        }
    }
};

kgst.Erfassen = (function() {

    var LIMIT = 500,
        lastLength = 0,
        jRemainDiv = null,
        remaining = 0,
        jTextarea = null,
        jSaveButton = 0,
        jResetButton = 0,
        jButtonDiv = 0,
        OLDVAL_ATTRIB = 'oldValue',
        AJAX_SUCCESS_STRING = 'ajaxWerteSavedOK',
        jSpinner = null,
        loading = 0,
        TEXTS = {
            OK: 'Noch <b style="color:#000">%n</b> Zeichen.',
            BAD: 'Der Text ist <b style="color:red">%n</b> Zeichen zu lang.'
        },

        saveOldValue = function() {
            jTextarea.data(OLDVAL_ATTRIB, jTextarea.val());
        },

        loadOldValue = function() {
            jTextarea.val(jTextarea.data(OLDVAL_ATTRIB)).trigger('keyup');
        },

        setWait = function(on) {
            if (on) {
                loading = 1;
                jRemainDiv.html('').append(jSpinner, ' Bitte warten...');
                return;
            }
            // set off
            loading = 0;
            jRemainDiv.html('');
        },

        pendingAjaxWarning = function() {
            if (!loading) return false;
            alert('Ein Speichervorgang ist noch nicht abgeschlossen.');
            return true;
        },

        HANDLERS = {
            // When thickbox is opened
            THICKBOX_SHOW: function() {
                jTextarea = $('#TB_ajaxContent textarea')
                    .bind('keyup', HANDLERS.TEXTAREA_KEYUP)
                    .bind('mousemove', HANDLERS.TEXTAREA_KEYUP);

                if (typeof jTextarea.data(OLDVAL_ATTRIB) == 'undefined') {
                    saveOldValue();
                }
                loadOldValue();
                lastLength = -1;
                $('#TB_ajaxContent').append(jRemainDiv, jButtonDiv);
                lastLength = -1;
                jTextarea.trigger('keyup');
            },

            // on key events on textarea
            TEXTAREA_KEYUP: function() {
                var chars = ($(this).val() || '').length;
                if (chars == lastLength) {
                    return;
                }
                remaining = LIMIT - chars;
                var ok = (remaining >= 0),
                    text = (ok) ? TEXTS.OK : TEXTS.BAD,
                    valForText = (ok) ? remaining : remaining * -1;
                jRemainDiv.html(text.replace('%n', valForText));
                lastLength = chars;
            },

            AJAX_SUCCESS: function(data) {
                setWait(0);
                var success = ('' + data).indexOf(AJAX_SUCCESS_STRING) >= 0;
                if (success) {
                    saveOldValue();
                    updateErlauterungIcons();
                    jRemainDiv.html('<span style="color:green">Die Änderungen wurden gespeichert.</span>');
                    return;
                }
                HANDLERS.AJAX_ERROR();
            },

            AJAX_ERROR: function(e) {
                setWait(0);
                alert('Die Änderungen konnten nicht gespeichert werden.');
            },

            // on click on Speichern button
            SAVE_CLICK: function() {
                if (pendingAjaxWarning()) return false;

                if (remaining < 0) {
                    alert('Der eingegebene Text ist zu lang!');
                    return false;
                }

                var params = {isAjaxSaveRequest:1},
                    formURL = $('#ikonEditForm').attr('action');

                // populate params object to be sent as payload on ajax request
                params[jTextarea[0].name] = jTextarea.val();
                $('#ikonEditForm > input:hidden').each(function(i, inp) {
                    var pName = inp.name || '';
                    if (!pName || pName.indexOf('scroll') >= 0) return;
                    params[pName] = inp.value;
                });

                setWait(1);
                $.ajax({
                    url: formURL,
                    type: 'POST',
                    data: params,
                    cache: false,
                    success: HANDLERS.AJAX_SUCCESS,
                    error: HANDLERS.AJAX_ERROR
                });

                return false;
            },

            RESET_CLICK: function() {
                if (pendingAjaxWarning()) return false;
                if (!confirm('Alle nicht gespeicherten Änderungen gehen verloren. Fortfahren?')) return;
                loadOldValue();
                jTextarea.trigger('keyup');
                return false;
            }
        },

        updateErlauterungIcons = function() {
            var IMG_RED = '/kgst-webapp/images/skin/database_edit_red.gif',
                IMG_NORM = '/kgst-webapp/images/skin/database_edit.gif';

            $('td.erl').each(function(i, td) {
                var textarea = $('textarea:first', this)[0] || jTextarea[0],
                    hasValue = !!$(textarea).val();
                ($('a.thickbox img:first', this)[0] || {}).src = (hasValue) ? IMG_RED : IMG_NORM;
            });
        },

        initStaticGz = function() {
            var inputHandler = function(e) {
                $(this.parentNode).nextAll('td.static-disabled').find('input').val(this.value);
            },
                selectHandler = function(e) {
                    $(this.parentNode).nextAll('td.static-disabled').find('select').val(this.value);
                };

            $('td.static').each(function(i, td) {
                // Enable auto fill for "statische" Grundzahlen
                $('input', td).bind('keydown keyup keypress change select click focus', inputHandler);
                $('select', td).bind('change', selectHandler);
            });

            // Disable obsolete inputs for "statische" Grundzahlen
            $('.static-disabled').find('input,select').attr('disabled', 'disabled');
        },

        /**
         * Adjusts the width of the dropdowns of the ObjekteigenschaftenErfassen-Dialog.
         */
            initObjEigErfassen = function() {
            var SELECTOR = {
                SELECT: '#objEigTable td.value select',
                INPUT: '#objEigTable td.value input:text',
                VALUECELL: '#objEigTable td.value, #objEigTable th.value'
            },
                TXT_INPUT_PADDING = (kgst.isIE7OrBelow) ? 5 : 3,
                isChanged = 0,
                newWidth = 123;

            $(SELECTOR.SELECT).css('width', 'auto').each(function(i, sel) {
                var w = $(sel).width();
                if (w < newWidth) return;
                newWidth = w;
                isChanged = 1;
            });

            if (isChanged) {
                // extra padding right
                newWidth += 13;
            }

            $(SELECTOR.SELECT).css({
                width: newWidth + 'px'
            });

            $(SELECTOR.INPUT).css({
                width: (newWidth - TXT_INPUT_PADDING) + 'px',
                paddingLeft: TXT_INPUT_PADDING + 'px'
            });

            $(SELECTOR.VALUECELL).css({
                width: newWidth + 'px',
                paddingLeft: 0,
                paddingRight: 0,
                textAlign:'left'
            });
        },

        init = function() {
            var wrapStart = ($.browser.msie) ? '<span class="ikonButtonIE6" style="float:right">' : '',
                wrapEnd = ($.browser.msie) ? '</span>' : '',
                floatCSS = ($.browser.msie) ? '' : 'float:right"';
            jRemainDiv = $('<div id="remainingChars" style="width:50%">&nbsp;</div>');
            jResetButton = $(wrapStart + '<a href="#" class="ikonActionLink" style="color:#fff;' + floatCSS + '">Abbrechen</a>' + wrapEnd).bind('click', HANDLERS.RESET_CLICK);
            jSaveButton = $(wrapStart + '<a href="#" class="ikonActionLink" style="color:#fff;' + floatCSS + '">Speichern</a>' + wrapEnd).bind('click', HANDLERS.SAVE_CLICK);
            jButtonDiv = $('<div>').append(jSaveButton, jResetButton, '<div class="break"></div>');
            jSpinner = $('<img src="/kgst-webapp/images/trans.gif" alt="" class="spinner" style="vertical-align:middle" />');

            $('td.erl a.thickbox').bind('click', HANDLERS.THICKBOX_SHOW);
            updateErlauterungIcons();
        };

    return {
        init: init,
        initStaticGz: initStaticGz,
        initObjEigErfassen: initObjEigErfassen
    };

})();


/**
 * Singleton managing the splitter layout.
 * Use by calling prepare() BEFORE splitpane HTML and init() AFTER splitpane HTML.
 */
kgst.Layout = (function() {

    var COOKIE_NAME = 'ikonLayout',
        COMPACT_MODE_SUPPORTED = 1,
        COMPACT_COOKIE_NAME = 'ikonCompact',
        MIN_LEFT_WIDTH = 100,
        MAX_LEFT_WIDTH = 600,
        HANDLE_WIDTH = 3,    // width of the vertical split handle line
        TOTAL_WIDTH = 940,    // width of the entire splitPane
        logger = kgst.LOGGER,
        FOOTER_HEIGHT = 25,
        ADDITIONAL_MARGIN = 115,
        ADDITIONAL_MARGIN_COMPACT = 85,
        jSplitPane = null,
        jLeftPane = null,
        optiTimer = 0,
        OPTI_DELAY = 100,    // delay to wait after last resize event before adapting the change
        state = {
            leftWidth: 200,
            leftHeight:400
        },

        features = [],  // array of functions to be executed after init()

        /**
         * Adds a feature function to the {features} list
         */
            addFeature = function(fn) {
            var fType = typeof fn;
            if (fType != 'function') {
                alert('Feature must be a function but is: ' + typeof fType);
                return;
            }
            features[features.length] = fn;
        },

        /**
         * Reads the last state from the cookie.
         */
            restoreStateFromCookie = function() {
            var cookie = $.cookie(COOKIE_NAME),
                obj = null;

            if (!cookie) return null;
            try {
                obj = eval(cookie);
                $.extend(state, obj);
            } catch(ignore) {
                logger.log('invalid layout cookie');
            }
            return obj;
        },

        /**
         * Determines the current state of the splitpanes and saves it into the cookie.
         * The state object will be updated to actual current left pane dimensions.
         * @param {boolean} force (optional) set TRUE to force renewal of cookie even if no values have changed
         */
            saveStateToCookie = function(force) {
            var hasChanged = false,
                ready = !!(jLeftPane && jLeftPane.length),
                leftWidth = (ready) ? jLeftPane.width() : state.leftWidth,
                leftHeight = (ready) ? jLeftPane.height() : state.leftHeight,
                hasChanged = (leftWidth != state.leftWidth || leftHeight != state.leftHeight);

            logger.log('layout::saveStateToCookie -> hasChanged: ' + hasChanged + ', forced:' + !!force);

            if (hasChanged || force) {
                state.leftWidth = leftWidth;
                state.leftHeight = leftHeight;
                var cookieValue = '({leftWidth:' + leftWidth + ',leftHeight:' + leftHeight + '})';
                $.cookie(COOKIE_NAME, cookieValue, {path:'/'});
            }
        },

        /**
         * Adjusts the vertical size of the splitpane to fit perfectly into the viewport without vertical scrollbars.
         * Used also as event handler on window:resize events.
         */
            setOptimalHeight = function() {
            optiTimer = 0;
            var headerHeight = $('#header').height(),
                footerHeight = FOOTER_HEIGHT, // $('#footer').height()
                winHeight = $(window).height(),
                margin = (isInCompactMode()) ? ADDITIONAL_MARGIN_COMPACT : ADDITIONAL_MARGIN,
                newHeight = winHeight - headerHeight - footerHeight - margin;

            // logger.log('header:'+headerHeight+', footer:'+footerHeight+', win:'+winHeight+',newHeight:'+newHeight );
            $('#splitPane, #splitPane .vsplitbar:first, #leftPane').css({
                height: newHeight + 'px',
                minHeight: newHeight + 'px'
            });

            var rightTopH = $('#topPane > div:first').height();
            $('#bottomPane').css('overflow', 'auto').height(newHeight - rightTopH + 'px');
            jSplitPane.trigger('resize'); // otherwise weird height effects in IE+FF on resize of left column
        },

        scheduleSetOptimalHeight = function() {
            if (optiTimer) clearTimeout(optiTimer);
            optiTimer = setTimeout(setOptimalHeight, OPTI_DELAY);
        },

        /**
         * Event handler for resize events on splitPane.
         */
            handleResize = function(e) {
            logger.log('resized pane ' + e.target.id);
            if (e.target != jSplitPane[0] && e.target != jLeftPane[0]) return;
            logger.log('pane ' + e.target.id + ' resize %o', e);
            saveStateToCookie();
        },

        checkCompactMode = function() {
            if (!COMPACT_MODE_SUPPORTED) return;
            var isCompact = !!$.cookie(COMPACT_COOKIE_NAME);
            var jNormal = $('<a id="aNormalView" href="#">Normalansicht</a>').bind('click', function() {
                $(document.body).removeClass('compactMode');
                $(window).trigger('resize');
                $.cookie(COMPACT_COOKIE_NAME, null, {path:'/'});
                return false;
            }),
                jCompact = $('<a id="aCompactView" href="#">Kompaktansicht</a>').bind('click', function() {
                    $(document.body).addClass('compactMode');
                    $(window).trigger('resize');
                    $.cookie(COMPACT_COOKIE_NAME, '1', {path:'/'});
                    return false;
                }),
                jViewmodeSwitch = $('<div id="viewmode"></div>').append(jNormal, jCompact);

            if (typeof dotCmsHelpPopup != 'undefined' && dotCmsHelpPopup) {
                var jHelp = $('<a id="aHelpView" href="' + dotCmsHelpPopup + '" target="_blank">Hilfe</a>');
                jViewmodeSwitch.append(' | ', jHelp);
            }
            if (isCompact) {
                $(document.body).addClass('compactMode');
            }
            /*  SEE KGST-2674  */
            if (window.location.href.indexOf("/pad/") == -1) {
                $('#header').append(jViewmodeSwitch);
            }
        },

        isInCompactMode = function() {
            return $(document.body).hasClass('compactMode');
        },

        /**
         * Checks the cookie for initial size of the left pane.
         * Inserts cookie or default dimensions as CSS rules (Anti-Ruckel).
         * To be called inline BEFORE the pane is rendered.
         */
            prepare = function() {
            //this.stopper = new kgst.Stopwatch();
            restoreStateFromCookie();
            var rightPos = state.leftWidth + HANDLE_WIDTH,
                rightWidth = TOTAL_WIDTH - rightPos,
                jStyle = $('<style id="panePrepareCSS" type="text/css">' +
                    '/* splitPane CSS dynamically inserted by kgst.Layout.prepare() */' + "\n" +
                    '#splitPane {position:relative} ' +
                    '#rightPane {position:absolute; left:' + rightPos + 'px; top:0; height:' + state.leftHeight + 'px; width:' + rightWidth + 'px; overflow:hidden !important}' +
                    '#leftPane {width: ' + state.leftWidth + 'px; height: ' + state.leftHeight + 'px;overflow:hidden !important}' +
                    '#tree {display:none !important}' +
                    '</style>');
            document.getElementsByTagName('head')[0].appendChild(jStyle[0]);
            //logger.log('Layout.prepare took ' + this.stopper.getTime() + 'ms');

            checkCompactMode();
        },

        /**
         * Initializes the Splitter layout.
         * To be called AFTER the pane is rendered (yet BEFORE domready).
         */
            init = function() {
            jLeftPane = $('#leftPane');

            var frag, tree, treeContainer;
            if ($.browser.msie) {
                /**
                 * IE rendering performance boost.
                 * Take tree off the DOM while initing splitpane (otherwise takes seconds in IE!).
                 */
                frag = document.createDocumentFragment();
                tree = document.getElementById('tree');
                treeContainer = tree.parentNode;
                frag.appendChild(tree);
            }

            jSplitPane = $("#splitPane").css('overflow', 'visible').splitter({
                splitVertical: true,
                outline: true,
                sizeLeft: state.leftWidth,
                minLeft: MIN_LEFT_WIDTH,
                maxLeft: MAX_LEFT_WIDTH
            }).bind('resize', handleResize);

            if (!jSplitPane.length || !jLeftPane.length) {
                alert('Whoops, invalid splitpane HTML!?');
                return;
            }

            // set optimal height "near" domload
            $(window).bind('resize', scheduleSetOptimalHeight);
            setOptimalHeight();

            $('#panePrepareCSS').remove();

            saveStateToCookie(true);

            if ($.browser.msie) {
                setTimeout(function() {
                    treeContainer.appendChild(frag);
                    kgst.Tree2.init();
                }, 1);
            } else {
                kgst.Tree2.init();
            }

            $('#ikonEditForm input[type=text]:first').each(function() {
                $(this).trigger('focus')[0].focus();
            });

            for (var i = 0; i < features.length; i++) {
                features[i]();
            }
        };

    // --------------------------
    return {
        prepare: prepare,
        init: init,
        setOptimalHeight: setOptimalHeight,
        addFeature: addFeature
    };
})();


kgst.PopupLayout = (function() {

    var ADDITIONAL_V_MARGIN = ($.browser.msie) ? 50 : 50,
        ADDITIONAL_H_MARGIN = 20,
        LOGGER = kgst.LOGGER,
        resizeHandlers = [],
        cssDiv = 0,
        resizeTimer = 0,

        prepare = function() {
            $('head:first').append($('<style type="text/css">html,body{overflow:hidden}</style>'));
            $('#ikonPageMenu').remove();
            $(document.body).css({
                backgroundImage:'url(/kgst-webapp/images/spinnerBig.gif)',
                backgroundPosition:'center center',
                backgroundRepeat:'no-repeat'
            });
            $(function() {
                $('#header,#breadcrumb').remove();
                document.body.style.backgroundImage = 'none';
                $('#bottomPane, #ikonButtonPane').show();
                setOptimalSize(true);
                updateTooltips();
            });
        },

        init = function() {
            $(window).bind('resize', setOptimalSize);
            $('form.werteErfassen td a').attr('tabindex', -1);
            if (document.all) {
                /*
                 $('.ikonWerte table thead th').each(function(){
                 this.innerHTML = (this.innerHTML||'').replace(/([^\s]+)\s+([^\s]+)/g,"$1&nbsp;$2");
                 });
                 */
                $('#o2gAssignTable').slideDown(20);
            }
        },

        doResize = function() {
            setOptimalSize(true);
        },

        setOptimalSize = function(now) {
            if (now !== true) {
                if (resizeTimer) {
                    clearTimeout(resizeTimer);
                    resizeTimer = 0;
                }
                resizeTimer = setTimeout(doResize, 60);
                return;
            }

            var topHeight = $('#topPane > div:first').height() || 0,
                winH = $(window).height() || 0,
                winW = $(window).width() || 0,
                buttonsH = $('#ikonButtonPane').height() || 0,
                bottomHeight = winH - topHeight - buttonsH - ADDITIONAL_V_MARGIN,
                w = winW - ADDITIONAL_H_MARGIN,
                css = [];

            if (!cssDiv) {
                cssDiv = $('<div style="display:none"></div>')[0];
                document.body.appendChild(cssDiv);
            }

            css[css.length] = '#page,#column-right {width:' + (w - ADDITIONAL_H_MARGIN) + 'px !important; padding:0 !important}';
            css[css.length] = '#bottomPane {width:' + w + 'px;height:' + bottomHeight + 'px;overflow:auto;}';
            css[css.length] = '#topPane {width:' + w + 'px}';

            cssDiv.innerHTML = '';
            cssDiv.appendChild($('<style type="text/css">' + css.join('') + '</style>')[0]);

            for (var i = 0; i < resizeHandlers.length; i++) {
                resizeHandlers[i]();
            }
        },

        /**
         * Adds a callback to be called whenever setOptimalSize is executed.
         * @param {function} fn
         */
            addResizeHandler = function(fn) {
            if (typeof fn != 'function') {
                alert('resizeHandler must be a function!');
                return;
            }
            resizeHandlers[resizeHandlers.length] = fn;
        },

        /**
         * Sets up correct tooltips for all data cells of the table.
         * Head may contain rowspanned cells and colspanned cells.
         * @todo FIX for Objektmatrix
         */
            updateTooltips = function() {
            var jForm = $('form.werteErfassen');
            if (!jForm.length) {
                // alert('Whoops - .werteErfassen-Form?!');
                return;
            }

            var xHeadlines = [],
                metaColsCount = $('table:first tbody tr:first td.name, table:first tbody tr:first td.action', jForm).length, // #cols before first td.value
                dataColsCount = $('table:first tbody tr:first td.value', jForm).length, // #cols of type td.value
                headRowsCount = $('table:first thead tr', jForm).length,
                BREAK_CHAR = ($.browser.msie) ? "\n" : ' -> ',
                rowspanOffset = 0,
                colIndex = -1;

            LOGGER.log('metaCols: ' + metaColsCount);
            LOGGER.log('dataCols: ' + dataColsCount);
            LOGGER.log('headRows: ' + headRowsCount);

            /**
             * iterate THs of first header line
             * meta columns MUST NOT be colspanned but may be rowspanned
             * data columns MUST NOT be rowspanned but may be colspanned
             */
            $('table:first thead tr:eq(0) th', jForm).each(function(x, th) {
                if (x < metaColsCount) {
                    colIndex++;
                    if ($(th).attr('rowspan') == 2) rowspanOffset++;
                    return;
                }
                LOGGER.log('rowspanOffset: %i', rowspanOffset);

                var num = parseInt($(th).attr('colspan') || 1) || 1;
                LOGGER.log('colspan for "%s" == %i', th.innerHTML, num);
                while (num--) {
                    xHeadlines[++colIndex] = th.innerHTML;
                    LOGGER.log('setting xHeadlines[%i] to %s', colIndex, th.innerHTML);
                }
            });

            LOGGER.log(xHeadlines);

            // iterate second header row
            $('table:first thead tr:eq(1) th', jForm).each(function(x, th) {
                var offs = x + rowspanOffset;
                if (offs < metaColsCount) return;
                xHeadlines[x + rowspanOffset] += BREAK_CHAR + th.innerHTML;
            });

            LOGGER.log(xHeadlines);

            $('table:first tbody tr', jForm).each(function(y, tr) {
                var rowHeadline = '';
                $('td', tr).each(function(x, td) {
                    if (x == 0) {
                        rowHeadline = this.innerHTML;
                    }
                    if (!xHeadlines[x]) return;
                    this.setAttribute('title', xHeadlines[x] + BREAK_CHAR + rowHeadline);
                    LOGGER.log('setting title ' + y + ':' + x + ' to ' + this.getAttribute('title'));
                });
            });
        };

    return {
        init: init,
        prepare: prepare,
        addResizeHandler: addResizeHandler
    }
})();


/**
 * The Tree singleton for use in new splitter layout.
 * Usage: call kgst.Tree2.init() BEFORE domReady but AFTER splitter layout has been initialized.
 */
kgst.Tree2 = kgst.Tree2 || (function() {

    var COOKIE_NAME = 'ikonTree',
        MAP_COOKIE_NAME = 'treeMap2',
        LOAD_TIMEOUT = 10000,
        LOAD_FLAG_ATTRIBUTE = '_kgstload',
        RETRY_FLAG = '_kgstRetry',
        SAVE_COOKIE_INTERVAL = 700,
        EMULATE_IE6_HOVER = true,
        saveCookieTimer = 0,
        treeInitialized = false,
        treeActionMap = null,

        jTree = null,
        jStyle = $('<style type="text/css"></style>'),
        state = {
            scrollLeft: 0,
            scrollTop: 0
        },

        logger = kgst.LOGGER,

        getTreeLoadAjaxUrl = function(str) {
            var url = dotCmsAjaxURLTreeLoadAjax;
            return (isDotCms(url)) ? getDotCmsAjaxUrl(url, str) : (url + '/' + str);
        },

        getTreeNodeOpenAjaxUrl = function(str) {
            var url = dotCmsAjaxURLTreeNodeOpenAjax;
            return (isDotCms(url)) ? getDotCmsAjaxUrl(url, str) : (url + '/' + str);
        },

        getTreeNodeCloseAjaxUrl = function(str) {
            var url = dotCmsAjaxURLTreeNodeCloseAjax;
            return (isDotCms(url)) ? getDotCmsAjaxUrl(url, str) : (url + '/' + str);
        },

        getTreeNodePageUrl = function(str) {
            if (treeBaseUrl == null) {
                treeBaseUrl = $('#treeHomeUrl').attr('href').replace('adminVR?rmtparams=', 'adminVR/?rmtparams=');
            }
            var url = treeBaseUrl;
            return (isDotCms(url)) ? getDotCmsPageUrl(url, str) : treeBaseUrlPrefix + '/' + str;
        },

        loadSubTree = function(id, li) {
            var jWait = $('<div class="waitDiv">Lade...</div>'),
                nodeURL = kgst.Tools.antiCacheURL(getTreeLoadAjaxUrl(id)),
                requestTime = new Date();
            $(li).append(jWait);

            $.ajax({
                url: nodeURL,
                dataType: 'html',
                timeout: LOAD_TIMEOUT,
                success: function(data) {
                    jWait.remove();
                    li.removeAttribute(RETRY_FLAG);
                    li.removeAttribute(LOAD_FLAG_ATTRIBUTE);
                    if (data && $.trim(data).length) {
                        // if user has logged out meanwhile, returned data is the login page WITHOUT proper HTTP status code
                        if (data.indexOf('<form') >= 0) {
                            alert('Sie sind nicht eingeloggt. Sie werden nun zur Login-Seite weitergeleitet.');
                            kgst.Tools.jumpToLogin();
                            return;
                        }
                        $(li).append(data);
                    } else {
                        // logged('empty data returned -> probably no children?' );
                        $(li).children('img:first').remove();
                    }
                },
                error: function(xmlHttpRequest, textStatus, errorThrown) {
                    jWait.remove();
                    li.removeAttribute(LOAD_FLAG_ATTRIBUTE);
                    $('> img:first', li).removeClass('close');
                    if (!li.getAttribute(RETRY_FLAG)) {
                        // $('>div', li).remove();
                        li.setAttribute(RETRY_FLAG, true);
                        setTimeout(function() {
                            loadSubTree(id, li);
                        }, 50);
                    } else {
                        $(li).append('<div>Fehler</div>');
                        li.removeAttribute(RETRY_FLAG);

                        // error logging..
                        var statusCode = xmlHttpRequest.status || 'unbekannt',
                            textStatus = textStatus || '',
                            time = new Date,
                            hexTime = (+time).toString(16),
                            secondsAgo = '' + Math.round((time - requestTime) / 1000),
                            requestedURL = nodeURL || '',
                            traceImgURL = '/kgst-webapp/images/TREE_ERROR_' + hexTime + '.gif?' +
                                'statusCode=' + encodeURIComponent('' + statusCode) +
                                '&textStatus=' + encodeURIComponent('' + textStatus) +
                                '&secondsAgo=' + secondsAgo +
                                '&requestURL=' + encodeBase64(requestedURL),
                            jTraceImg = $('<img>').hide().attr('src', traceImgURL);

                        $(document.body).append(jTraceImg);

                        alert('Es ist ein Fehler aufgetreten.' +
                            "\nFehlercode -> " + statusCode +
                            "\nTextStatus -> " + textStatus +
                            "\nlokale Zeit -> " + time.toString() +
                            "\nAnfrage-URL -> " + requestedURL
                        );
                    }
                }
            });
        },

        saveStatus = function(url) {
            $.ajax({url: url, success: HANDLERS.NOP});
        },

        HANDLERS = {
            /**
             * Click on +/- toggler icon of a node.
             * (this: IMG)
             */
            TOGGLE_CLICK: function() {
                var id = this.parentNode.className.match(/id([^ ]*)/)[1],
                    jImg = $(this);
                jUL = jImg.siblings('ul:first'),

                    logger.log('toggle img clicked -- ID is ' + id);

                if (!jUL.length) {
                    var li = this.parentNode,
                        isLoading = !!li.getAttribute(LOAD_FLAG_ATTRIBUTE);
                    if (isLoading) {
                        // Subtree was already requested (probably double-clicked on +/- icon)
                        $('> .waitDiv', li).css({
                            color: 'red',
                            letterSpacing: '1px'
                        });
                        return;
                    }
                    li.setAttribute(LOAD_FLAG_ATTRIBUTE, 1);
                    $('>div', li).remove();
                    jImg.addClass('close');
                    loadSubTree(id, li);
                } else {
                    if (jUL.hasClass('clsd')) {
                        jUL.removeClass('clsd');
                        jImg.addClass('close');
                        saveStatus(getTreeNodeOpenAjaxUrl(id));
                    } else {
                        jUL.addClass('clsd');
                        jImg.removeClass('close');
                        saveStatus(getTreeNodeCloseAjaxUrl(id));
                    }
                }
            },

            /**
             * Mousedown on a node link (right- or leftclick, enabling "open new tab/window")
             * (this: A)
             */
            LINK_MOUSEDOWN: function() {
                loadTreeActionMap();
                var padBereich = $(this).parents('li').children("a:contains('PAD')").length > 0 ? true : false;
                var explNodePathParam = '',
                    split = this.name.split('/'),
                    obj = {
                        controller: split[0],
                        action: split[1],
                        id: split[2],
                        padBereich: padBereich
                    },
                    hash = urlObj2Hash(obj),
                    mapObj = treeActionMap[hash],
                    isSelected = $(this).hasClass('selected'),
                    hasNutzerTab = /#N$/.test(this.href),    // only if a Bereich-link has this URL, mapObj may redirect to the "Nutzer"-Tab
                    tabRedirectForbidden = (mapObj) ? (mapObj.c == 'adminBereich' && mapObj.a == 'showUser' && !hasNutzerTab) : false;

                if (mapObj && (isSelected || tabRedirectForbidden)) {
                    mapObj = null;
                    delete treeActionMap[hash];
                    saveTreeActionMap();
                }

                if (mapObj) {
                    explNodePathParam = '?explNodePath=' + this.name.replace(/\//g, '|');
                    this.name = mapObj.c + '/' + mapObj.a + '/' + obj.id;
                }

                this.href = getTreeNodePageUrl(this.name + explNodePathParam);
            },

            IE6_TOGGLE_OVER: function() {
                this.id = 'ie6th'
            },
            IE6_TOGGLE_OUT: function() {
                this.id = ''
            },

            NOP: function() {
                return false
            },

            /**
             * Click on a tab of the right pane.
             * (this: A)
             */
            TAB_CLICK: function() {
                var treeObj = selectedNode2object();
                var tabObj = kgst.Tools.url2object(this.href);

                if (!treeObj || !tabObj) return;

                loadTreeActionMap();

                var treeObjHash = urlObj2Hash(treeObj),
                    mapObj = {c:tabObj.controller, a:tabObj.action},
                    TREEMAP_WHITELIST = {
                        'adminBereich_show': ['adminBereich/show','adminBereich/showBeschreibungen','adminTeilnehmer/listByBereich','adminBereich/showUser']
                    };

                // Check whitelist of controller/action pairs allowed for a special tree node. Relevant only for nodes that
                // may remain 'selected' in the tree while actually leaving the Bereich context, KGST-2154)
                if (TREEMAP_WHITELIST[treeObjHash]) {
                    var newMapObj = 0,
                        mapStr = mapObj.c + '/' + mapObj.a;
                    for (var i = TREEMAP_WHITELIST[treeObjHash].length - 1; i >= 0; i--) {
                        if (mapStr != TREEMAP_WHITELIST[treeObjHash][i]) continue;
                        newMapObj = mapObj;
                        break;
                    }
                    mapObj = newMapObj;
                }
                if (mapObj) {
                    treeActionMap[treeObjHash] = mapObj;
                    saveTreeActionMap();
                }
            }
        },

        /**
         * Reads the last state from the cookie.
         */
            restoreStateFromCookie = function() {
            var cookie = $.cookie(COOKIE_NAME),
                obj = null;

            if (!cookie) return null;
            try {
                obj = eval(cookie);
                $.extend(state, obj);
            } catch(ignore) {
                logger.log('invalid tree cookie');
            }
            return obj;
        },

        /**
         * Determines the current state of the tree and saves it into the cookie.
         * The state object will be updated to actual current scroll positions etc.
         * @param {boolean} force (optional) set TRUE to force renewal of cookie even if no values have changed
         */
            saveStateToCookie = function(force) {
            var scrollLeft = jTree.scrollLeft(),
                scrollTop = jTree.scrollTop(),
                hasChanged = (scrollTop != state.scrollTop) || (scrollLeft != state.scrollLeft);

            if (hasChanged || force) {
                state.scrollLeft = scrollLeft;
                state.scrollTop = scrollTop;
                var cookieValue = '({scrollLeft:' + state.scrollLeft + ',scrollTop:' + state.scrollTop + '})';
                $.cookie(COOKIE_NAME, cookieValue, {path:'/'});
            }
        },

        /**
         * Updates the Tree's scroll position based on the {state} properties.
         * If the optional callback is given, smooth scrolling will be used.
         * @param {function} callback (optional) function to be called when smooth scrolling has finished
         */
            adaptScrollStatus = function(callback) {
            logger.log('adaptScrollStatus -> left:' + state.scrollLeft + ' , top:' + state.scrollTop);
            if (!callback) {
                // simple case: just set scroll pos immediately
                jTree.scrollLeft(state.scrollLeft).scrollTop(state.scrollTop);
                return;
            }
            // otherwise use smooth scrolling
            jTree.scrollTo({
                top: state.scrollTop,
                left: state.scrollLeft
            }, {
                duration: 300,
                onAfter: callback
            });
        },

        /**
         * Determines whether the selected node is visible within the Tree viewport.
         * If not, the viewport will be scrolled appropriately to reveal the node.
         */
            assureSelectedInViewport = function() {
            // logger.log('Checking selected node is in viewport...');
            var jSelected = jTree.find('a.selected:first');
            if (jSelected.length) {
                var viewportHeight = jTree.height() - 18,    // 18 pixels for possible hori-scrollbar
                    viewportScroll = jTree.scrollTop(),
                    selectedTop = jSelected.position().top || 0,
                    selectedHeight = jSelected.height(),
                    isInViewport = (selectedTop > 0 && selectedTop < (viewportHeight - selectedHeight));

                logger.log('viewportHeight=' + viewportHeight + ' | ' +
                    'viewportScroll=' + viewportScroll + ' | ' +
                    'selectedTop=' + selectedTop + ' | ' +
                    'selectedHeight=' + selectedHeight + ' | ' +
                    'isInViewport=' + isInViewport);

                if (!isInViewport) {
                    jTree.scrollTo('a.selected:first', {
                        duration:300,
                        axis:'xy',
                        offset: -23
                    });
                }
            } else {
                logger.log('No node selected!');
                jTree.scrollTo({top:0,left:0}, {duration:300});
            }
        },

        /**
         * Empties the tree cache.
         * (!) FOR DEBUG ONLY (!)
         */
            emptyTreeCache = function() {
            $(function() {
                var iframe = $('<iframe>').attr('src', 'http://localhost:57880/kgst-webapp/adminTree/releaseTreeFromSession?' + (+new Date)).hide().bind('load', function() {
                    logger.log('tree cache flushed');
                });
                $(document.body).append(iframe);
            });
        },

        loadTreeActionMap = function() {
            treeActionMap = $.evalJSON($.cookie(MAP_COOKIE_NAME) || '{}');
        },

        saveTreeActionMap = function() {
            $.cookie(MAP_COOKIE_NAME, $.toJSON(treeActionMap), {path:'/'});
            // console.log($.cookie(MAP_COOKIE_NAME));
        },

        urlObj2Hash = function(obj) {
            var padBereich = obj.padBereich ? 'pad_' : ''
            return padBereich + obj.controller + '_' + obj.action;
        },

        bindEventHandlers = function() {
            logger.log('binding event handlers..');

            $('#tree img').live('click', HANDLERS.TOGGLE_CLICK);
            $('#tree a').live('mousedown', HANDLERS.LINK_MOUSEDOWN)

            if (EMULATE_IE6_HOVER && /msie|MSIE 6/.test(navigator.userAgent)) {
                $('#tree img').live('mouseover', HANDLERS.IE6_TOGGLE_OVER);
                $('#tree img').live('mouseout', HANDLERS.IE6_TOGGLE_OUT);
            }

            // save controller-dependent active tab
            $('#ikonPageTabs table td a').bind('click', HANDLERS.TAB_CLICK);
        },

        /**
         * Fixes zombie-togglers ("pseudo-open" nodes with missing subtree UL)
         */
            fixZombies = function() {
            var zombies;
            //var stopper = new kgst.Stopwatch();
            if (!$.browser.msie) {
                // selector too slow for IE (>200ms IE vs. <10ms FF)
                zombies = $('#tree img.close ~ a:last-child ').each(function(i, a) {
                    $(a).prev()[0].className = '';
                });
            } else {
                zombies = {length:0};
                $('#tree img.close').each(function(i, img) {
                    if ($(this).siblings('ul').length) return;
                    this.className = '';
                    zombies.length++;
                });
            }
            //logger.log(zombies.length + ' zombie togglers repaired. (' + stopper.getTime() + 'ms)');
        },

        /**
         * Returns the currently selected tree node's link as an object like {controller:'abc',action:'def',id:'123}.
         * Analog to {kgst.Tools.url2Object}.
         * @return {object|null}
         */
            selectedNode2object = function() {
            var a = jTree.find('a.selected')[0];
            if (!a) return null;
            var matches = (a.name || '').match(/([^\/]+)\/([^\/]+)\/(\d*)/);
            if (!matches || matches.length < 4) return null;
            var padBereich = $('a.selected').parents('li').children("a:contains('PAD')").length > 0 ? true : false;
            return {
                controller: matches[1],
                action: matches[2],
                id: matches[3],
                padBereich: padBereich
            };
        };


    // Return the actual singleton...
    return {
        state: state,

        selectedNode2object: selectedNode2object,

        /**
         * To be called inline before domready.
         * @param {boolean} debug (optional) set TRUE to enable logging
         */
        init: function(_debug) {
            if (_debug) DEBUG = _debug;
            logger.log('Tree.init()');

            jTree = $('#leftPane');

            if (!jTree.show().length) {
                return;
            }

            if (treeInitialized) {
                alert('Whoops, Tree2 already initialized!?');
                return;
            } else {
                treeInitialized = true;
            }

            document.getElementsByTagName('head')[0].appendChild(jStyle[0]);

            restoreStateFromCookie();
            adaptScrollStatus();
            assureSelectedInViewport();  // for animation orgy use assureSelectedInViewport as param for adaptScrollStatus()

            bindEventHandlers();

            fixZombies();

            saveStateToCookie(true);
            saveCookieTimer = window.setInterval(function() {
                saveStateToCookie()
            }, SAVE_COOKIE_INTERVAL);
            logger.log('Tree.init() done.');
        },

        /**
         * Removes the treemap cookie from the previous login session.
         */
        deleteCookies: function() {
            if ($.cookie(MAP_COOKIE_NAME)) {
                $.cookie(MAP_COOKIE_NAME, null, {path:'/'});
            }
            if ($.cookie(COOKIE_NAME)) {
                $.cookie(COOKIE_NAME, null, {path:'/'});
            }
        }
    }
})();


// ===================================================================================
//                        *** HELPER FUNCTIONS ***
// ===================================================================================


function urlDecode(str) {
    str = str.replace(new RegExp('\\+', 'g'), ' ');
    return unescape(str);
}
;

function urlEncode(str) {
    str = escape(str);
    str = str.replace(new RegExp('\\+', 'g'), '%2B');
    return str.replace(new RegExp('%20', 'g'), '+');
}
;

var END_OF_INPUT = -1;

var base64Chars = new Array(
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '+', '/'
);

var reverseBase64Chars = new Array();
for (var i = 0; i < base64Chars.length; i++) {
    reverseBase64Chars[base64Chars[i]] = i;
}
;

var base64Str;
var base64Count;
function setBase64Str(str) {
    base64Str = str;
    base64Count = 0;
}
;

function readBase64() {
    if (!base64Str) return END_OF_INPUT;
    if (base64Count >= base64Str.length) return END_OF_INPUT;
    var c = base64Str.charCodeAt(base64Count) & 0xff;
    base64Count++;
    return c;
}
;

function encodeBase64(str) {
    setBase64Str(str);
    var result = '';
    var inBuffer = new Array(3);
    var lineCount = 0;
    var done = false;
    while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT) {
        inBuffer[1] = readBase64();
        inBuffer[2] = readBase64();
        result += (base64Chars[ inBuffer[0] >> 2 ]);
        if (inBuffer[1] != END_OF_INPUT) {
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]);
            if (inBuffer[2] != END_OF_INPUT) {
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]);
                result += (base64Chars [inBuffer[2] & 0x3F]);
            } else {
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]);
                result += ('=');
                done = true;
            }
        } else {
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]);
            result += ('=');
            result += ('=');
            done = true;
        }
        lineCount += 4;
        if (lineCount >= 76) {
            result += ('\n');
            lineCount = 0;
        }
    }
    return result;
}
;

function readReverseBase64() {
    if (!base64Str) return END_OF_INPUT;
    while (true) {
        if (base64Count >= base64Str.length) return END_OF_INPUT;
        var nextCharacter = base64Str.charAt(base64Count);
        base64Count++;
        if (reverseBase64Chars[nextCharacter]) {
            return reverseBase64Chars[nextCharacter];
        }
        if (nextCharacter == 'A') return 0;
    }
    return END_OF_INPUT;
}
;

function ntos(n) {
    n = n.toString(16);
    if (n.length == 1) n = "0" + n;
    n = "%" + n;
    return unescape(n);
}
;

function decodeBase64(str) {
    setBase64Str(str);
    var result = "";
    var inBuffer = new Array(4);
    var done = false;
    while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT
        && (inBuffer[1] = readReverseBase64()) != END_OF_INPUT) {
        inBuffer[2] = readReverseBase64();
        inBuffer[3] = readReverseBase64();
        result += ntos((((inBuffer[0] << 2) & 0xff) | inBuffer[1] >> 4));
        if (inBuffer[2] != END_OF_INPUT) {
            result += ntos((((inBuffer[1] << 4) & 0xff) | inBuffer[2] >> 2));
            if (inBuffer[3] != END_OF_INPUT) {
                result += ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3]));
            } else {
                done = true;
            }
        } else {
            done = true;
        }
    }
    return result;
}
;

var digitArray = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
function toHex(n) {
    var result = ''
    var start = true;
    for (var i = 32; i > 0;) {
        i -= 4;
        var digit = (n >> i) & 0xf;
        if (!start || digit != 0) {
            start = false;
            result += digitArray[digit];
        }
    }
    return (result == '' ? '0' : result);
}
;

function pad(str, len, pad) {
    var result = str;
    for (var i = str.length; i < len; i++) {
        result = pad + result;
    }
    return result;
}
;

function encodeHex(str) {
    var result = "";
    for (var i = 0; i < str.length; i++) {
        result += pad(toHex(str.charCodeAt(i) & 0xff), 2, '0');
    }
    return result;
}
;

var hexv = {
    "00":0,"01":1,"02":2,"03":3,"04":4,"05":5,"06":6,"07":7,"08":8,"09":9,"0A":10,"0B":11,"0C":12,"0D":13,"0E":14,"0F":15,
    "10":16,"11":17,"12":18,"13":19,"14":20,"15":21,"16":22,"17":23,"18":24,"19":25,"1A":26,"1B":27,"1C":28,"1D":29,"1E":30,"1F":31,
    "20":32,"21":33,"22":34,"23":35,"24":36,"25":37,"26":38,"27":39,"28":40,"29":41,"2A":42,"2B":43,"2C":44,"2D":45,"2E":46,"2F":47,
    "30":48,"31":49,"32":50,"33":51,"34":52,"35":53,"36":54,"37":55,"38":56,"39":57,"3A":58,"3B":59,"3C":60,"3D":61,"3E":62,"3F":63,
    "40":64,"41":65,"42":66,"43":67,"44":68,"45":69,"46":70,"47":71,"48":72,"49":73,"4A":74,"4B":75,"4C":76,"4D":77,"4E":78,"4F":79,
    "50":80,"51":81,"52":82,"53":83,"54":84,"55":85,"56":86,"57":87,"58":88,"59":89,"5A":90,"5B":91,"5C":92,"5D":93,"5E":94,"5F":95,
    "60":96,"61":97,"62":98,"63":99,"64":100,"65":101,"66":102,"67":103,"68":104,"69":105,"6A":106,"6B":107,"6C":108,"6D":109,"6E":110,"6F":111,
    "70":112,"71":113,"72":114,"73":115,"74":116,"75":117,"76":118,"77":119,"78":120,"79":121,"7A":122,"7B":123,"7C":124,"7D":125,"7E":126,"7F":127,
    "80":128,"81":129,"82":130,"83":131,"84":132,"85":133,"86":134,"87":135,"88":136,"89":137,"8A":138,"8B":139,"8C":140,"8D":141,"8E":142,"8F":143,
    "90":144,"91":145,"92":146,"93":147,"94":148,"95":149,"96":150,"97":151,"98":152,"99":153,"9A":154,"9B":155,"9C":156,"9D":157,"9E":158,"9F":159,
    "A0":160,"A1":161,"A2":162,"A3":163,"A4":164,"A5":165,"A6":166,"A7":167,"A8":168,"A9":169,"AA":170,"AB":171,"AC":172,"AD":173,"AE":174,"AF":175,
    "B0":176,"B1":177,"B2":178,"B3":179,"B4":180,"B5":181,"B6":182,"B7":183,"B8":184,"B9":185,"BA":186,"BB":187,"BC":188,"BD":189,"BE":190,"BF":191,
    "C0":192,"C1":193,"C2":194,"C3":195,"C4":196,"C5":197,"C6":198,"C7":199,"C8":200,"C9":201,"CA":202,"CB":203,"CC":204,"CD":205,"CE":206,"CF":207,
    "D0":208,"D1":209,"D2":210,"D3":211,"D4":212,"D5":213,"D6":214,"D7":215,"D8":216,"D9":217,"DA":218,"DB":219,"DC":220,"DD":221,"DE":222,"DF":223,
    "E0":224,"E1":225,"E2":226,"E3":227,"E4":228,"E5":229,"E6":230,"E7":231,"E8":232,"E9":233,"EA":234,"EB":235,"EC":236,"ED":237,"EE":238,"EF":239,
    "F0":240,"F1":241,"F2":242,"F3":243,"F4":244,"F5":245,"F6":246,"F7":247,"F8":248,"F9":249,"FA":250,"FB":251,"FC":252,"FD":253,"FE":254,"FF":255
};

function decodeHex(str) {
    str = str.toUpperCase().replace(new RegExp("s/[^0-9A-Z]//g"));
    var result = "";
    var nextchar = "";
    for (var i = 0; i < str.length; i++) {
        nextchar += str.charAt(i);
        if (nextchar.length == 2) {
            result += ntos(hexv[nextchar]);
            nextchar = "";
        }
    }
    return result;

}
;

String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

String.prototype.beginsWith = function(t, i) {
    if (i == false) {
        return (t == this.substring(0, t.length));
    } else {
        return (t.toLowerCase() == this.substring(0, t.length).toLowerCase());
    }
};

String.prototype.endsWith = function(t, i) {
    if (i == false) {
        return (t == this.substring(this.length - t.length));
    } else {
        return (t.toLowerCase() == this.substring(this.length - t.length).toLowerCase());
    }
};


/*
 var dotCmsAjaxURLStatus = "/servlets/plugins/kgst/post?rmtparams=cm10YWM9YWRtaW5Ja29uJnJtdGF1PWh0dHAlM0ElMkYlMkZsb2NhbGhvc3QlM0E4MDkwJTJGa2dz_dC13ZWJhcHAlMkZhZG1pbk5hY2hyaWNodCUyRnN0YXR1cw--";
 rmtac=adminIkon&rmtau=http%3A%2F%2Flocalhost%3A57880%2Fkgst-webapp%2FadminNachricht%2Fstatus
 command: >>> console.log(urlDecode(decodeBase64("cm10YWM9YWRtaW...3ZWJhcHAlMkZhZG1pbk5hY2hyaWNodCUyRnN0YXR1cw--")));
 rmtac=adminIkon&rmtau=http://localhost:57880/kgst-webapp/adminNachricht/status
 */

var RMTPARAMS = '?rmtparams=';

function isDotCms(str) {
    return str.indexOf(RMTPARAMS) > -1;
}
;

function getDotCmsAjaxUrl(url, str) {
    if (url == null) {
        return null;
    }

    var arrSplit = url.split(RMTPARAMS);

    if (arrSplit == null || arrSplit.length < 2) {
        return url + '/' + str;
    }

    var baseUrl = arrSplit[0] + RMTPARAMS;
    var params = decodeBase64(arrSplit[1]);

    arrSplit = params.split('&');

    var rmtac;
    var rmtau;

    for (var i = 0; i < arrSplit.length; i++) {
        if (arrSplit[i].indexOf('rmtac') > -1) {
            rmtac = arrSplit[i];
        } else
        if (arrSplit[i].indexOf('rmtau') > -1) {
            rmtau = urlDecode(arrSplit[i]);
        }
    }

    rmtau += '/' + str;
    // params = rmtac + '&' + urlEncode(rmtau);
    params = rmtac + '&' + rmtau;
    params = encodeBase64(params);
    url = baseUrl + params;

    return url;
}
;

function getDotCmsPageUrl(url, str) {
    if (url == null) {
        return null;
    }

    var arrSplit = url.split(RMTPARAMS);

    if (arrSplit == null || arrSplit.length < 2) {
        return url + '/' + str;
    }

    var baseUrl = arrSplit[0] + RMTPARAMS;
    var params = decodeBase64(arrSplit[1]);

    arrSplit = params.split('&');

    var rmtac;
    var rmtau = null;

    for (var i = 0; i < arrSplit.length; i++) {
        if (arrSplit[i].indexOf('rmtac') > -1) {
            rmtac = arrSplit[i];
        } else
        if (arrSplit[i].indexOf('rmtau') > -1) {
            rmtau = 'rmtau=' + urlEncode(treeBaseUrlPrefix + '/' + str);
        }
    }

    // params = rmtac + '&' + urlEncode(rmtau);
    params = rmtac + '&' + rmtau;
    params = encodeBase64(params);
    url = baseUrl + params;

    return url;
}
;


// Werteerfassen-Dialoge -> change class of Text-Inputs on focus/blur
kgst.Layout.addFeature(function() {
    $('form.werteErfassen table a').attr('tabIndex', '-1');
    $('form.werteErfassen table input:text').attr('autocomplete', 'off');
    return;
    /*
     var jAssignForm = $('form.werteErfassen ');
     if (!jAssignForm.length) return;
     var KEY_UP = 38,
     KEY_DN = 40,
     KEY_TAB = 9,
     keyHandler = function(e) {
     var jTargetInput;
     switch (e.which) {
     case KEY_UP:
     return;
     // later
     var cellIndex = this.parentNode.cellIndex;
     jTargetInput = $(this).parents('tr:first').prev('tr:first').find('td:eq('+cellIndex+') input[type=text]:first');
     break;
     case KEY_DN:
     return;
     // later
     var cellIndex = this.parentNode.cellIndex;
     jTargetInput = $(this).parents('tr:first').next('tr:first').find('td:eq('+cellIndex+') input[type=text]:first');
     break;
     case KEY_TAB:
     if (! /inpIdx(\d+)/.exec(this.className||'') ) return;
     var ownIndex = parseInt(RegExp.$1);
     targetIndex = ownIndex + ((e.shiftKey)? -1 : 1);
     jTargetInput = $(this).parents('form:first').find('input.inpIdx'+targetIndex+':first');
     break;
     default:
     return;
     }
     if (!jTargetInput || !jTargetInput.length) return;
     jTargetInput.trigger('focus');
     return false;
     };

     $('input[type=text]', jAssignForm).attr('autocomplete','off').bind('keydown', keyHandler).each(function(i,inp) {
     $(this).addClass('inpIdx'+i);
     });
     */
});

if ((top.location.href || '').indexOf('http://localhost:8080/') == 0)
    $(function() {
        var url = top.location.href;
        if (!/rmtparams=([^#]*)/.exec(url)) return;
        var decodedURL = decodeURIComponent(decodeBase64(RegExp.$1));
        $(document.body).append($('<div style="position:absolute;left:0;top:0;background:yellow">').html(decodedURL));
    });

// KGST-1765: focus first textarea in thickbox if exists
(function() {
    if (typeof tb_show == 'undefined') return;
    var oldFn = tb_show;
    tb_show = function() {
        oldFn.apply(top, arguments);
        $('#TB_ajaxContent textarea:first').each(function() {
            this.focus();
        });
    };
})();


/**
 * Adds a print link to a thickbox window if it contains some element with class=ajaxPrintable
 */
kgst.Tools.addTickboxPrintLink = function() {
    $('.ajaxPrintable:first', $('#TB_ajaxContent')).each(function(i, div) {
        var jPrintLink = $('<a target="_blank" href="' + kgst.PRINT_URL + '">Drucken</a>');
        $('#TB_closeWindowButton').before(jPrintLink, ' | ');
    });
    ;
};

kgst.Tools.getEmailStatus = function(dotCmsAjaxURLStatus, dotCmsJsURLList) {
    $.ajax({
        url: dotCmsAjaxURLStatus,
        success: function(data) {
            var topnav = $('#topnav');
            var isDotCms = !!topnav.length;
            var attrs = 'url="ikonMailInfo" id="ikonMailInfo"';

            if (isDotCms) {
                attrs += ' style="float:right;"';
                topnav.html(topnav.html() + '<a href="' + dotCmsJsURLList + '" ' + attrs + '></a>');
            } else {
                // isStandalone
                var header = $('.header').find('.userinfo');
                var isHeader = !!header.length;

                if (isHeader) {
                    header.html('<div><a href="' + dotCmsJsURLList + '" ' + attrs + '>' + data + '</a></div>' + header.html());
                } else {
                    var tree = $('#tree');
                    tree.html('<div><a href="' + dotCmsJsURLList + '" ' + attrs + '>' + data + '</a></div>' + tree.html());
                }
            }

            kgst.Tools.updateEmailStatus(dotCmsAjaxURLStatus);
            setInterval(function() {
                kgst.Tools.updateEmailStatus(dotCmsAjaxURLStatus);
            }, 60000); // every minute
        }
    });
};

kgst.Tools.updateEmailStatus = function(dotCmsAjaxURLStatus) {
    $.ajax({
        url: dotCmsAjaxURLStatus,
        dataType: 'html',
        success: function(data) {
            if (data && $.trim(data).length) {
                // if user has logged out meanwhile, returned data is the login page WITHOUT proper HTTP status code
                if (data.indexOf('<form') >= 0) {
                    // alert('Sie sind nicht eingeloggt. Sie werden nun zur Login-Seite weitergeleitet.');
                    $('#ikonMailInfo').html('');
                    kgst.Tools.jumpToLogin();
                    return;
                } else {
                    $('#ikonMailInfo').html(data);
                }
            }
        }
    });
};

if (typeof _etc == 'undefined') {
    _etc = function() {
    }; // tracker function causing error when testing locally
}
