/**
 * Frontend uses jQuery, backend uses YUI (YAHOO)
 *
 * TODO: This all needs documentation
**/

ZD = window.ZD || {};

ZD.get = function(id) {
    if (!id)
        return null;
    var el = (typeof id == "string" ? document.getElementById(id) : id);
    // Decode any quote entities (mainly for allowing easier JSON handling)
    if (el && el.value && el.value.replace)
        el.value = el.value.replace(/&quot;/g, '"');
    return el;
}
if (typeof jQuery == "undefined")
    $ = ZD.get;

if (typeof YAHOO != "undefined") {
    // YUI available -- Manager
    ZD.event = YAHOO.util.Event;
    ZD.on = function() { ZD.event.on.apply(ZD.event, arguments); };
    ZD.select = Ext.DomQuery.select;  
} else {
    // YUI not available -- make API match YUI's.
    ZD.event = {
        stopEvent: function(evt) {
            evt.stopPropagation();
            evt.preventDefault();
        }
    };
    ZD.on = function(el, type, fnArg, obj, override) {
        if (typeof el == "string")
            el = ZD.get(el);
        var fn;
        if (override) {
            fn = fnArg.bind(obj);
        } else {
            fn = function(evt) {
               fnArg.call(this, evt, obj);
            };
        }
        if (typeof obj != "undefined" && !override)
            jQuery(el).bind(type, obj, fn);
        else
            jQuery(el).bind(type, fn); 
    };
    ZD.select = function(query, context) {
        return jQuery.makeArray(jQuery(query, context));
    };
}

ZD.forms = {    
    setAction: function(form, action) {
        for (var i = 0; i < form.elements.length; i++) {
            if (form.elements[i].name == "action") {
                form.elements[i].value = action;
                break;
            }
        }
    },
    replaceSelectWithFieldSpec: function(el, spec) {
        vals = spec.values.split(/,/),
        opts = spec.options.split(/,/),
        options = vals.map(function(val, idx) { return [opts[idx], val]; });
        if (spec.first_opt)
            options.unshift([spec.first_opt, ""]);
        ZD.forms.replaceSelectOptions(el, options);
    },
    replaceSelectWithRows: function(el, rows, valField, optField) {
        optField = optField || valField;
        var options = [];
        for (var i = 0; i < rows.length; i++)
            options.push([rows[i][optField], rows[i][valField]]);
        ZD.forms.replaceSelectOptions(el, options);
    },
    replaceSelectOptions: function(el, options) {
        el = ZD.get(el);
        while (el.hasChildNodes())
            el.removeChild(el.firstChild);
        var opt;
        if (options instanceof Array) {
            options.forEach(function(option) {
                if (option instanceof Array) {
                    var label = option[0],
                        value = option[1];
                } else {
                    var label = option.label,
                        value = option.value;
                }
                opt = document.createElement('option');
                opt.value = value;
                opt.appendChild(document.createTextNode(label.replace(/ /g, "\u00a0")));
                if (value == "~") {
                    opt.setAttribute("disabled", "disabled");
                }
                el.appendChild(opt);
            });
        } else {
            for (var key in options) {
                opt = document.createElement('option');
                opt.value = options[key];
                // use non-breaking unicode spaces
                opt.appendChild(document.createTextNode(key.replace(/ /g, "\u00a0")));
                if (options[key] == "~") {
                    opt.setAttribute("disabled", "disabled");
                }
                el.appendChild(opt);
            }
        }
    },
    getOptionText: function(sel, value) {
        value = value || sel.value;
        for (var i = 0; i < sel.options.length; i++) {
            if (sel.options[i].value == value) {
                return sel.options[i].text;
            }
        }
    },
    focusFirstField: function(form) {
        var form = form || document.forms[ZD.currentState.replace("_form", "")] || document.forms[ZD.currentMethod.replace("_form", "")];
        if (form && form.elements) {
            var els = form.elements;
            for (var i = 0; i < els.length; i++) {
                var type = els[i].type;
                var disp = ZD.dom.getStyle(els[i], "display");
                var disabled = els[i].disabled == "disabled";
                if ((type == "text" || type == "select-one") && disp != "none" && !disabled) {
                    if (els[i].select) {
                        els[i].select();
                    }
                    try {
                        els[i].focus();
                    } catch (e) {}
                    break;
                }
            }
        }
    },
    initTabs: function(tabs, defaultTab, callback, form, global) {
        var global = global || !form;
        var defaultTab = defaultTab || 'Content';
        var form = form || ZD.select(".tabbed-form")[0];
        var f = (form.tagName == "FORM" ? form : ZD.select("form",form)[0]);
        if (form.tagName == "FORM" && form.parentNode.className == 'tabbed-form')
            form = form.parentNode;
        form.style.display = "block";
        f.style.display = "block";
        var exclude = [];
        var showTabContent = function(curtab) {
            var curtab = curtab || defaultTab;
            // Special case: summary tab for community
            if (ZD.currentMethod.indexOf('edit_form') != -1 && ZD.get("community-summary")) {
                if (curtab == "Summary") {
                    ZD.dom.hide(ZD.select(".form"));
                    ZD.dom.show("community-summary");
                } else {
                    ZD.dom.show(ZD.select(".form"));
                    ZD.dom.hide("community-summary");
                }
            }
            if (typeof callback == "function") {
                var callbackResult = callback(curtab);
                if (callbackResult === false) return(false);
                else if (typeof(callbackResult) == "object")
                    exclude = callbackResult;
                else exclude = [];
            }
            ZD.dom.show(ZD.select('.tabbed-submit',form)[0]);
            for (var tab in tabs) {
                var display = (tab == curtab ? "" : "none");
                tabs[tab].forEach(function(name) {
                    if (tab == curtab && exclude.contains(name)) return;
                    var name = name.replace(/ /g, "-");
                    var tr = ZD.get("zd-field-" + name + "-tr")
                    if (!tr) return;
                    var el = ZD.get("zd-field-" + name);
                    if (tab == curtab && exclude.contains(el)) return;
                    ZD.dom.setStyle(tr, 'display', display);
                });
                if (tab == curtab && !tabs[tab].length)
                    ZD.dom.hide(ZD.select('.tabbed-submit',form)[0]);
            }
            return(curtab);
        }
        form.showTab = function(tab) {
            var tab = showTabContent(tab);
            if (tab) {
                ZD.select("ul.tabs a",form).forEach(function(a) {
                    a.parentNode.parentNode.className = (a.innerHTML == tab ?
                        "tab-active" : "tab-inactive");
                });
                ZD.forms.focusFirstField(!global ? form : false);
            }
        }
        if (global) ZD.forms.showTab = form.showTab;
        ZD.select("ul.tabs a",form).forEach(function(a) {
            if (!global) ZD.event.removeListener(a,"click");
            ZD.onClick(a,function(e) {
                form.showTab(this.innerHTML);
            });
        });
        
        if (form && f) {
            var oldSb = ZD.select("td.cell-submit input",form)[0];
            if (!oldSb) ZD.select("input.button",form).forEach(function(button) {
                if (button.type == "submit" || button.value == "Submit")
                    oldSb = button;
            });
            if (oldSb) {
                var newSb = ZD.html.INPUT({type: 'submit', 'class': 'button', value: oldSb.value});
                newSb._srcForm = f;
                ZD.dom.hide(ZD.select(".row-submit",form));
                f.appendChild(ZD.html.DIV({'class': 'tabbed-submit'}, [newSb]));
                ZD.onClick(newSb, function() { f.submit(); });
            }
            for (var tab in tabs) {
                // set class name of each tabs' table rows
                var tab_name = tab.replace(" ","-").toLowerCase();
                for (var tab_index=0; tab_index<tabs[tab].length; tab_index++) {
                    var field_name = String(tabs[tab][tab_index]).replace(" ","-");
                    var field = ZD.get("zd-field-"+field_name+"-tr");
                    if (!field) continue;
                    field.className = field.className + " form-tab-" + tab_name;
                }
            }
            ZD.select(".label-problem",form).forEach(function(label) {
                var tr = label.parentNode.parentNode.parentNode;
                var fn = tr.id.replace(/(^zd-field-|-tr$)/g, "");
                var hlTabs = [];
                for (var tab in tabs) {
                    if (tabs[tab].contains(fn)) {
                        hlTabs.push(tab);
                    }
                }
                ZD.select("ul.tabs a",form).forEach(function(a) {
                    if (hlTabs.contains(a.innerHTML)) {
                        a.className = " tab-problem";
                    }
                });
            });
        }

        form.showTab(defaultTab);
    },
    
    hideFieldRow: function(el) {
        var trs, i;
        if (!(el instanceof Array))
            el = [el];
        for (i = 0; i < el.length; i++) {
            if (typeof el[i] == "string")
                el[i] = ZD.get(el[i]);
            trs = jQuery(el[i]).parents("tr");
            if (!trs.length)
                continue;
            ZD.dom.addClass(trs[0], "hidden");
        }
    },
    
    showFieldRow: function(el) {
        var trs, i;
        if (!(el instanceof Array))
            el = [el];
        for (i = 0; i < el.length; i++) {
            if (typeof el[i] == "string")
                el[i] = ZD.get(el[i]);
            trs = jQuery(el[i]).parents("tr");
            if (!trs.length)
                continue;
            ZD.dom.removeClass(trs[0], "hidden");
        }
    },
    
    showFieldLoading: function(el) {
        var img = new ZD.html.IMG({src: "images/field-loading.gif"});
        img.className = "field-loading";
        jQuery(img).insertAfter(el);
    },
    
    hideFieldLoading: function(el) {
        var img = jQuery(el).nextAll("img.field-loading")[0];
        ZD.dom.remove(img);
    },
    
    initMultiAdder: function(outerShell, oneRequired) {
        outerShell = jQuery(outerShell);
        var adderField = outerShell.next().find(".multi-adder-select").get(0),
            adderName = adderField.name.replace(/\[\]$/, ""),
            adderInput = adderField.form[adderName + '_hidden'],
            adderVals = adderInput.value && ZD.json.decode(adderInput.value.replace(/&quot;/g, '"')),
            shell = outerShell.find(".multi-adders").get(0),
            adder = outerShell.find(".multi-adder").get(0),
            catCounter = 0;

        adderField.disabled = "disabled";
        
        function updateAdderInput() {
            var vals = [],
                adderFields = jQuery(shell).find("select");
            adderFields.each(function() {
                if (this && this.value && !vals.contains(this.value))
                    vals.push(this.value);
            });
            adderInput.value = ZD.json.encode(vals);
            if (!vals.length)
                shell.innerHTML = "<div class='none'>None</div>";
        }

        function appendAdderField(value) {
            var newField = adderField.cloneNode(true),
                fieldShell = ZD.html.DIV({'class': "adder-field"});
            newField.disabled = "";
            catCounter++;
            newField.id = "multi-adder-select-" + catCounter;
            newField.value = value || "";
            ZD.on(newField, "change", updateAdderInput);
            fieldShell.appendChild(newField);
            
            var remover = ZD.html.A({href: "#", 'class': "remover"}, ["remove"]);
            ZD.onClick(remover, function() {
                ZD.dom.remove(this.parentNode);
                updateAdderInput();
            });
            fieldShell.appendChild(remover);
            
            shell.appendChild(fieldShell);
        }

        if (!adderVals.length) {
            if (oneRequired)
                appendAdderField(adderName == "categories" ? ZD.inputGet['cat_id'] : "");
            else
                shell.innerHTML = "<div class='none'>None</div>";
        } else
            adderVals.forEach(function(val) { appendAdderField(val); });

        ZD.onClick(adder, function() {
            var noneEl = ZD.select(".none", shell);
            ZD.dom.hide(noneEl);
            appendAdderField();
        });
    }
};

ZD.ajax = {
    selectUpdater: function(el, dataSource, exclude) {
        el = ZD.get(el);
        var form = el.form;
        var exclude = ['id', 'action', 'prev_action', 'ayear', 'academic_year',
            'global_campus'].concat(exclude || []);
        dataSource += (dataSource.indexOf("?") != -1 ? "&" : "?");
        if (form) {
            for (var i = 0; i < form.elements.length; i++) {
                if (!form.elements[i].name || exclude.contains(form.elements[i].name)) continue;
                dataSource += form.elements[i].name + "=";
                dataSource += encodeURIComponent(form.elements[i].value) + "&";
            }
        }
        dataSource += "&___stamp=" + (new Date()).getTime(); // prevent IE caching
        dataSource += "&___md5=" + ZD.cookie.get("md5"); // CSRF prevention
        var updateHandler = function(req) {
            ZD.forms.replaceSelectOptions(el, ZD.json.decode(req.responseText));
        }
        YAHOO.util.Connect.asyncRequest(
            'GET',
            dataSource,
            { success: updateHandler }
        );
    },
    get: function(state, params, callback, selfOverride) {
        params = params || {};
        if (ZD.globalHttpParams)
            for (var key in ZD.globalHttpParams)
                params[key] = ZD.globalHttpParams[key];
        callback = callback || {};
        var url = selfOverride || (ZD.instanceBaseUrl ? ZD.instanceBaseUrl + "&" : ZD.selfUrl + "?");
        if (!params.id && ZD.instanceId && ZD.currentMethod)
            params.id = ZD.instanceId;
        params.action = state;
        params.___stamp = (new Date()).getTime(); // prevent IE caching
        params.___md5 = ZD.cookie.get("md5"); // CSRF prevention
        YAHOO.util.Connect.asyncRequest(
            'GET',
            url + ZD.ajax.makeQueryString(params),
            callback
        );
    },
    post: function(state, params, callback, selfOverride) {
        params = params || {};
        if (ZD.globalHttpParams)
            for (var key in ZD.globalHttpParams)
                params[key] = ZD.globalHttpParams[key];
        if (!params.id && ZD.instanceId)
            params.id = ZD.instanceId;
        params.action = state;
        params.___stamp = (new Date()).getTime(); // prevent IE caching
        params.___md5 = ZD.cookie.get("md5"); // CSRF prevention
        callback = callback || null;
        YAHOO.util.Connect.asyncRequest(
            'POST',
            selfOverride || ZD.selfUrl,
            callback,
            ZD.ajax.makeQueryString(params)
        );
    },
    // for non-CMS gets
    fetch: function(url, params, success, failure) {
        params = params || {};
        if (ZD.globalHttpParams)
            for (var key in ZD.globalHttpParams)
                params[key] = ZD.globalHttpParams[key];
        params.___stamp = (new Date()).getTime(); // prevent IE caching
        params.___md5 = ZD.cookie.get("md5"); // CSRF prevention
        YAHOO.util.Connect.asyncRequest(
            'GET',
            url + (url.indexOf("?") != -1 ? "&" : "?") + ZD.ajax.makeQueryString(params),
            {success: success, failure: failure}
        );
    },
    rawPost: function(url, qs, success, failure) {
        if (ZD.globalHttpParams)
            for (var key in ZD.globalHttpParams)
                qs += params[key] + "=" + encodeURIComponent(ZD.globalHttpParams[key]);
        YAHOO.util.Connect.asyncRequest(
            'POST',
            url + "?" + qs,
            {success: success, failure: failure}
        );
    },
    // Potentially dangerous! Use only with trusted sites!
    jsonpGet: function(baseUrl, state, params) {
        params = params || {};
        var scr = document.createElement('script');
        scr.src = ZD.ajax.makeHref(
            state,
            params,
            baseUrl);
        document.body.appendChild(scr);
    },
    makeQueryString: function(data) {
        var seperator = '', url = '', i, len, suffix;
        for (name in data) {
            if (data[name] instanceof Array) {
                suffix = (/\[\]$/.test(name) ? "" : "[]");
                for (i = 0, len = data[name].length; i < len; i++) {
                    url += seperator + encodeURIComponent(name + suffix) + "=" + encodeURIComponent(data[name][i]);
                    seperator = '&';
                }
            } else {
                url += seperator + encodeURIComponent(name) + '=' + encodeURIComponent(data[name]);
            }
            seperator = '&';
        }
        return url;
    },
    makeHref: function(state, params, baseUrl, noMd5) {
        params = params || {};
        if (baseUrl) {
            var url = baseUrl + "?";
        } else {
            var url = ZD.instanceBaseUrl ? ZD.instanceBaseUrl + "&" : ZD.selfUrl + "?";
        }
        params.action = state;
        if (!noMd5)
            params.___md5 =  ZD.cookie.get("md5"); // CSRF prevention
        return url + ZD.ajax.makeQueryString(params);
    },
    // Request remote credentials and provide them to a function
    authWrap: function(fn, thisObj, params) {
        if (!ZD.remoteAuthUrl) {
            alert("No remote authentication available!");
            return;
        }
        YAHOO.util.Connect.asyncRequest(
            'GET',
            ZD.remoteAuthUrl,
            {
                success: function(req) {
                    var creds = ZD.json.decode(req.responseText);
                    fn.call(thisObj || window, creds, params);
                }
            },
            {___md5: ZD.cookie.get("md5")} // CSRF prevention
        );
    },
    jsonpAuthGet: function(url, state, params) {
        ZD.ajax.authWrap(function(creds) {
            for (var key in creds) {
                params[key] = creds[key];
            }
            ZD.ajax.jsonpGet(url, state, params);
        });
    }
};
ZD.ajax.updateSelect = ZD.ajax.selectUpdater;

ZD.html = {
    // based on http://www.vivabit.com/code/dombuilder/dombuilder.js
    exportTags: function(context) {
        context = context || window;
        var tags = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" +
                    "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" +
                    "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
                    "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" +
                    "label|dfn|kbd|samp|var|iframe").split("|");
        var tag;
        for (var i = 0; i < tags.length; i++) {
            tag = tags[i];
            context[tag.toUpperCase()] = (function(tag) {
                return function (attrs, children) {
                    return ZD.html.tag(tag, attrs, children)
                }
            })(tag);
        }
    },
    tag: function(tag, attrs, children) {
        attrs = attrs || {};
        children = children || [];
        var isIE = navigator.userAgent.match(/MSIE/);
        var el = document.createElement((isIE && attrs.name) ? "<" + tag + " name=" + attrs.name + ">" : tag);
        for (var key in attrs) {
            if (typeof attrs[key] != 'function') {
                if (isIE) {
                    ZD.html.ieAttrSet(el, attrs, key);
                } else {
                    el.setAttribute(key, attrs[key]);
                }
            }
        }
        for (var i=0; i<children.length; i++) {
            if (typeof children[i] == 'string') {
                children[i] = document.createTextNode(children[i]);
            }
            el.appendChild(children[i]);
        }
        return el;
      },
    IE_TRANSLATIONS: {
        'class' : 'className',
        'for' : 'htmlFor'
    },
    ieAttrSet: function(el, a, i) {
        var trans;
        if (trans = this.IE_TRANSLATIONS[i]) {
            el[trans] = a[i];
        } else if (i == 'style') {
            el.style.cssText = a[i];
        } else if (i.match(/^on/)) {
            el[i] = new Function(a[i]);
        } else {
            el.setAttribute(i, a[i]);
        }
    }
}
ZD.html.exportTags(ZD.html);

ZD.dom = {
    remove: function(el) {
        el = ZD.get(el);
        if (!el) return;
        el.parentNode.removeChild(el);
    },
    replace: function(oldEl, newEl) {
        oldEl = ZD.get(oldEl);
        oldEl.parentNode.replaceChild(newEl, oldEl);
    },
    show: function(el, display) {
        el = ZD.get(el);
        if (!el) return;
        if (!(el instanceof Array)) {
            el = [el];
        }
        display = display || "block";
        var len = el.length;
        for (var i = 0; i < len; i++) {
            if (el[i]) el[i].style.display = display;
        }
    },
    hide: function(el) {
        el = ZD.get(el);
        if (!el) return;
        if (!(el instanceof Array)) {
            el = [el];
        }
        var len = el.length;
        for (var i = 0; i < len; i++) {
            if (el[i]) el[i].style.display = "none";
        }
    },
    toggle: function(el, display) {
        el = ZD.get(el);
        display = display || "block";
        el.style.display = (el.style.display == "none" || !el.style.display) ?
            display : "none";
    },
    getOffset: function(el) {
        var node = el, elX = 0, elY = 0;
        while (node) {
            elX += node.offsetLeft;
            elY += node.offsetTop;
            node = node.offsetParent ? node.offsetParent : null;
        }
        return [elX, elY];
    }
}

if (typeof YAHOO != "undefined") {
    ZD.dom.addClass = YAHOO.util.Dom.addClass;
    ZD.dom.removeClass = YAHOO.util.Dom.removeClass;
    ZD.dom.hasClass = YAHOO.util.Dom.hasClass;
    ZD.dom.getStyle = YAHOO.util.Dom.getStyle;
    ZD.dom.setStyle = YAHOO.util.Dom.setStyle;
} else {
    ZD.dom.addClass = function(el, cls) {
        jQuery(el).addClass(cls);
    };
    ZD.dom.removeClass = function(el, cls) {
        jQuery(el).removeClass(cls);
    };
    ZD.dom.hasClass = function(el, cls) {
        return jQuery(el).hasClass(cls);
    };
    ZD.dom.getStyle = function(el, prop) {
        return jQuery(el).css(prop);
    };
    ZD.dom.setStyle = function(el, prop, val) {
        jQuery(el).css(prop, val);
    };
}

/**
 *  Adds a click handler and stops any default actions
**/
ZD.onClick = function(els, fn) {
    if (typeof els == "string") {
        els = ZD.select(els);
    }
    if (els == null)
        return;
    ZD.on(els, "click", function(evt) {
        fn.call(this, evt);
        ZD.event.stopEvent(evt);
    });
}

/*
 * (c)2006 Dean Edwards/Matthias Miller/John Resig
 * Special thanks to Dan Webb's domready.js Prototype extension
 * and Simon Willison's addLoadEvent
 *
 * For more info, see:
 * http://dean.edwards.name/weblog/2006/06/again/
 * http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
 * http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 *
 * Thrown together by Jesse Skinner (http://www.thefutureoftheweb.com/)
 *
 * Modified for use in the ZD-CMS by Justin Kramer <jkkramer@gmail.com>
 *
 * To use: call addDOMLoadEvent one or more times with functions, ie:
 *
 *    function something() {
 *       // do something
 *    }
 *    addDOMLoadEvent(something);
 *
 *    addDOMLoadEvent(function() {
 *        // do other stuff
 *    });
 *
 */
ZD.onReady = function(func) {
    if (typeof YAHOO != "undefined")
        YAHOO.util.Event.onDOMReady(func);
    else
        jQuery(document).ready(func);
}


/**
 * @class
 *
 * This is Jack Slocum's YUI-ext class (http://jackslocum.com/),
 * which is a:
 * Modified version of Douglas Crockford's json.js that doesn't
 * mess with the Object prototype
 * http://www.json.org/js.html
 */
ZD.json = new function(){
    var useHasOwn = {}.hasOwnProperty ? true : false;
    var validRE = /^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/;

    var pad = function(n) {
        return n < 10 ? '0' + n : n;
    };

    var m = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    };

    var encodeString = function(s){
        if (/["\\\x00-\x1f]/.test(s)) {
            return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                var c = m[b];
                if(c){
                    return c;
                }
                c = b.charCodeAt();
                return '\\u00' +
                    Math.floor(c / 16).toString(16) +
                    (c % 16).toString(16);
            }) + '"';
        }
        return '"' + s + '"';
    };

    var encodeArray = function(o){
        var a = ['['], b, i, l = o.length, v;
            for (i = 0; i < l; i += 1) {
                v = o[i];
                switch (typeof v) {
                    case 'undefined':
                    case 'function':
                    case 'unknown':
                        break;
                    default:
                        if (b) {
                            a.push(',');
                        }
                        a.push(v === null ? "null" : ZD.json.encode(v));
                        b = true;
                }
            }
            a.push(']');
            return a.join('');
    };

    var encodeDate = function(o){
        return '"' + o.getFullYear() + '-' +
                pad(o.getMonth() + 1) + '-' +
                pad(o.getDate()) + 'T' +
                pad(o.getHours()) + ':' +
                pad(o.getMinutes()) + ':' +
                pad(o.getSeconds()) + '"';
    };

    this.encode = function(o){
        if(typeof o == 'undefined' || o === null){
            return 'null';
        }else if(o instanceof Array){
            return encodeArray(o);
        }else if(o instanceof Date){
            return encodeDate(o);
        }else if(typeof o == 'string'){
            return encodeString(o);
        }else if(typeof o == 'number'){
            return isFinite(o) ? String(o) : "null";
        }else if(typeof o == 'boolean'){
            return String(o);
        }else {
            var a = ['{'], b, i, v;
            for (var i in o) {
                if(!useHasOwn || o.hasOwnProperty(i)) {
                    v = o[i];
                    switch (typeof v) {
                    case 'undefined':
                    case 'function':
                    case 'unknown':
                        break;
                    default:
                        if(b){
                            a.push(',');
                        }
                        a.push(this.encode(i), ':',
                                v === null ? "null" : this.encode(v));
                        b = true;
                    }
                }
            }
            a.push('}');
            return a.join('');
        }
    };

    this.decode = function(json){
        try{
            if(validRE.test(json)) {
                return eval('(' + json + ')');
            }
        }catch(e){
        }
        throw new SyntaxError("parseJSON");
    };
}();

ZD.json.passthru = function(fn, thisObj) {
    return function(req) {
        var data = ZD.json.decode(req.responseText);
        fn.call(thisObj || window, data);
    }
}

/** DEPRECATED -- DO NOT USE **/
ZD.tooltips = {};
ZD.tooltips.add = function(e) {
    if (!e || !e.title || !YAHOO.widget.Tooltip) return;
    if (!ZD.tooltips.all) ZD.tooltips.all = [];
    if (!ZD.tooltips.count) ZD.tooltips.count = 1;
    else ZD.tooltips.count++;
    var ttID = "tooltip_auto_"+ZD.tooltips.count;
    if (!e.id)
        e.id = "agid_"+ttID;
    ZD.tooltips.all[ttID] = new YAHOO.widget.Tooltip(
        ttID,{
            "context": e,
            "effect": { "effect": YAHOO.widget.ContainerEffect.FADE,"duration":0.25}
        }
    );
}

ZD.cookie = {
    get: function(name) {
        var re = new RegExp(name + "=([^;]+)");
        var value = re.exec(document.cookie);
        return (value != null) ? unescape(value[1]) : null;
    },
    set: function(name, value, temp) {
        var today = new Date();
        var expiry = new Date(today.getTime() + 365 * 24 * 60 * 60 * 1000); // plus 365 days
        document.cookie = name + "=" + escape(value) + (temp ? "" : "; expires=" + expiry.toGMTString());
    }
}

ZD.popup = function(url, params, name) {
    var defaults = {
        menubar: "no",
        toolbar: "no",
        location: "no",
        resizable: "yes",
        scrollbars: "yes",
        status: "no",
        width: 500,
        height: 350
    };
    params = params || {};
    for (key in defaults) {
        if (!params[key]) params[key] = defaults[key];
    }
    var paramsStr = "";
    for (key in params) {
        paramsStr += key + "=" + params[key] + ",";
    }
    name = name || "zd_popup_window";
    return window.open(url, name, paramsStr);
}
/**
 * Make given anchor elements use popups instead of following links
 **/
ZD.popupify = function(links, params, name) {
    if (!(links instanceof Array)) {
        links = [ZD.get(links)];
    }
    links.forEach(function(link) {
        ZD.on(link, "click", function(evt) {
            ZD.popup(this.href, params, name)
            ZD.event.stopEvent(evt);
        });
    });
}

/**
 * Make a popup that resizes to the size of the image it links to. In development.
**/
ZD.imagepopup = function(url, params, name) {
    var image = new Image();
    image.src = url;
    var interval = setInterval(function() {
        if (image.width) {
            ZD.popup(url, {width: image.width + 20, height: image.height + 20}, name);
            clearInterval(interval);
        }
    }, 10);
}

/**
 * Make given anchor elements use popups instead of following links
 **/
ZD.imagepopupify = function(links, params, name) {
    if (!(links instanceof Array)) {
        links = [ZD.get(links)];
    }
    links.forEach(function(link) {
		var h = link.href;
        ZD.on(link, "click", function(evt) {
            ZD.event.stopEvent(evt);
            ZD.imagepopup(h, params, name);
        });
    });
}

// Taken from jQuery
// evalulates a script in global context
// not reliable for safari
ZD.globalEval = function(data) {
    if (window.execScript) {
        window.execScript(data);
    } else if (ZD.browser.safari) {
        // safari doesn't provide a synchronous global eval
        window.setTimeout(data, 0);
    } else {
        eval.call(window, data);
    }
};

(function() {
    var b = navigator.userAgent.toLowerCase();
    // Figure out what browser is being used
    ZD.browser = {
        version: (b.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
        safari: /webkit/.test(b),
        opera: /opera/.test(b),
        msie: /msie/.test(b) && !/opera/.test(b),
        mozilla: /mozilla/.test(b) && !/(compatible|webkit)/.test(b)
    };
})();

ZD.onReady(function() {
    /* Make any elements with class "zd-auto-popup" popups automatically */
    ZD.popupify(ZD.select('.zd-auto-popup'));
    ZD.imagepopupify(ZD.select('.zd-image-popup'));
    if (jQuery) {
        // Creates IFRAME, isolated styles
        // TODO: add more docs
        jQuery(".zd-auto-dialog").click(function() {
            var dialog = ZD.html.DIV({id: "zd-dialog"}),
                iframe = ZD.html.IFRAME({src: this.href});
            jQuery(iframe).css({
                width: "100%",
                height: "98%",
                border: "none"});
            iframe.frameBorder = 0; // IE
            dialog.appendChild(iframe);
            jQuery(dialog).dialog({
                title: this.title || "ZD-CMS Website Manager",
                width: 700,
                height: 550,
                modal: true
                /*,
                buttons: {
                    "Close": function() {
                        jQuery(this).dialog("close");
                    }
                }*/
            });
            return false;
        });
        // Uses Ajax, inherits styles
        // TODO: add more docs
        jQuery(".zd-auto-popup-inpage").click(function() {
            if (!ZD.inpagePopup) {
                ZD.inpagePopup = jQuery(ZD.html.DIV({id: "zd-popup-inpage"}));
                ZD.inpagePopup.dialog({
                    title: this.title || "ZD-CMS Website Manager",
                    width: 700,
                    height: 550,
                    modal: true,
                    buttons: {
                        Close: function() {
                            ZD.inpagePopup.dialog("close");
                        }
                    }
                });
            } else {
                ZD.get("zd-popup-inpage").innerHTML = "";
                ZD.inpagePopup.dialog("option", "title", this.title || "ZD-CMS Website Manager");
                ZD.inpagePopup.dialog("open");
            }
            ZD.ajax.fetch(this.href, {_ZD_BODY_ONLY: 1}, function(req) {
                ZD.get("zd-popup-inpage").innerHTML = req.responseText;
            });
            return false;
        });
    }
});

ZD.initToggleField = function(domId) {
    var el = jQuery("#" + domId),
        label = jQuery("#zd-label-" + domId),
        name = label.html().toLowerCase().replace(/:(\s|&nbsp;)*$/, ""),
        toggler = jQuery(ZD.html.A({href: "#", 'class': "expander-link-off auto-toggle"}, ["Show " + name]));
    el.addClass("auto-toggle-el");
    
    function toggleHandler() {
        if (el.css("display") != "none") {
            toggler.removeClass("expander-link-on")
                .addClass("expander-link-off")
                .html("Show " + name);
            el.hide();
            el.val("");
        } else {
            toggler.removeClass("expander-link-off")
                .addClass("expander-link-on")
                .html("Hide " + name);
            el.show();
        }
        return false;
    }

    toggler.click(toggleHandler);
    toggler.insertBefore(el);
    label.hide();

    if (el.val())
        el.hide();
    toggleHandler();
    
    ZD.togglers = ZD.togglers || {};
    ZD.togglers[domId] = toggleHandler;
}

ZD.escapeHtml = function(str) {
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
}

/**
 * Create an array from an iterable object
**/
Array.from = function(it) {
    var arr = [];
    for (var i = 0; i < it.length; i++) {
        arr[i] = it[i];
    }
    return arr;
}

String.prototype.ucFirst = function () {
   return this.substr(0,1).toUpperCase() + this.substr(1,this.length);
}
String.prototype.urlencode = function() {
  var output = '';
  var x = 0;
  var regex = /(^[a-zA-Z0-9_.]*)/;
  while (x < this.length) {
    var match = regex.exec(this.substr(x));
    if (match != null && match.length > 1 && match[1] != '') {
        output += match[1];
      x += match[1].length;
    } else {
      if (this[x] == ' ')
        output += '+';
      else {
        var charCode = this.charCodeAt(x);
        var hexVal = charCode.toString(16);
        output += '%' + ( hexVal.length < 2 ? '0' : '' ) + hexVal.toUpperCase();
      }
      x++;
    }
  }
  return output;
}

Function.prototype.bind = function($thisObj) {
    var $method = this;
    var $args = Array.from(arguments).slice(1);
    return function() {
        return $method.apply($thisObj, $args.concat(Array.from(arguments)));
    }
};

