var az = az || {};

az.event = az.event || {}; // register you available events here

az.events = {
    _listeners : {},
    register : function(event, callback) {
        if (!event) throw('register unknown event: '+event);
        if (!this._listeners[event]) this._listeners[event] = [];
        var info = {callback:callback};
        this._listeners[event].push(info);
        return [event, (this._listeners[event].length-1)].join('-');
    },
    unregister : function(id) {
        var e = id.split('-')[0];
        var i = parseInt(id.split('-')[1], 10);
        var spliced = this._listeners[e].splice(i, 1);
        if (spliced.length===1) return true;
        else return false;
    },
    trigger : function(event, data) {
        if (!event) throw('az.events.trigger(ev,data) - event is not defined');
        var listeners = this._listeners[event];
        if (listeners) {
            for (var i=0; i<listeners.length;i++) {
                listeners[i].callback.call(this, data);
            }
        }
    }
};

/*
  some basis js library stuff.
  e.g. douglas crockford's helper functions.
 */

if (typeof Object.create != 'function'){
    Object.create = function(o){
        var F = function(){};
        F.prototype = o;
        return new F();
    }
};


/*
  write and read cookies.
 */

var az = az || {};
az.cookie = {
    toString : function(){
        return "[object az.cookie]";
    },

    set : function(c_name,value,expiredays,path){
        if (!path) path="/";
        var exdate = new Date();
        var c = c_name+ "=" +value;
        if (expiredays !== null){
            exdate.setDate(exdate.getDate()+expiredays);
            c += ";expires="+exdate.toGMTString();
        }
        c += ";path="+path;
        document.cookie = c;
    },

    get : function(c_name){
        if (document.cookie.length>0) {
            c_start = document.cookie.indexOf(c_name + "=");
            if (c_start!=-1) {
                c_start=c_start + c_name.length+1;
                c_end=document.cookie.indexOf(";",c_start);
                if (c_end==-1) c_end=document.cookie.length;
                return unescape(document.cookie.substring(c_start,c_end));
            }
        }
        return false;
    }

};

az.localStorage = {
    set : function(name, val){
        if (window['localStorage']){
            window.localStorage[name] = val;
        }
        else{
            az.cookie.set(name, val, 100);
        }
    },
    get : function(name){
        if (window['localStorage'] && window.localStorage[name]){
            return window.localStorage[name];
        }
        return az.cookie.get(name);
    }
};
/*
  the logger Singleton logs info messages into a array-buffer. when the buffer
  gets too long, the oldest messages are dropped.

  log messages with:
  az.logger.log(modulename, message)

  you can request the tail of messages with
  az.logger.trace()

  or you can watch a specific module with
  az.logger.watch(modulename)

  and unwatch it with
  az.logger.unwatch(modulename)

  When DEBUG is set to false, nothing gets logged for performance issues.
 */
var az = az || {};

az.logger = {
    DEBUG : true,
    WATCH_ALL : false,
    BUFFER_LEN : 100,
    buffer : [],
    keywords : {},
    target : null,
    initialized : false,
    makeLoggable : function(ptr, channel){
        if (!ptr) throw "az.logger.makeLoggable Error. ptr is null";
        ptr._log_channel = channel;
        ptr.log = function(msg){
            if (!az.logger.DEBUG) return;
            var args = $.merge([], arguments);
            args.splice(0, 0, this._log_channel);
            az.logger.log.apply(az.logger, args);
        };
    },
    getCookie : function(){
        try{
            if (window['localStorage']){
                return localStorage['azLogWatch'];
            }
        }
        catch(e){
            // no local storage
        }
        return az.cookie.get('azLogWatch');
    },
    setCookie : function(val){
        try{
            if (window['localStorage']){
                return localStorage['azLogWatch'] = val;
            }
        }
        catch(e){
            // no local storage
        }
        return az.cookie.set('azLogWatch', val);
    },
    init : function(){
        this.initialized = true;
        var watch_cookie = this.getCookie();
        if (!watch_cookie) return;
        var watchers = watch_cookie.split(",");
        this.watch.apply(this, watchers);
    },
    log : function(){
        if (!this.DEBUG) return;
        if (!this.initialized) this.init();
        if (typeof(console) == "undefined") return;
        // remembers the logentry in the buffer or prints it immediatly if DEBUG is on
        this.buffer.push({args:arguments});
        if (this.buffer.length>this.BUFFER_LEN){
            this.buffer.splice(0,1);
        }
        if (this.WATCH_ALL || this.keywords[arguments[0]]) {
            this.print.apply(this, arguments);
        }
    },
    print : function(){
        var i;
        if (typeof(console) == "undefined") return;
        // prints the log message to the console
        if (console.notifyFirebug) {
            console.notifyFirebug(arguments,'debug','firebugAppendConsole');
        } else {
            try{
                console.log.apply(console, arguments);
            }
            catch(e){
                var str = "";
                for (var i=0; i<arguments.length; i++) str+=arguments[i]+";";
                console.log(str);
            }
        }
        if (this.target){
            var msg = "";
            for (i=0; i<arguments.length; i++) {
                msg += arguments[i];
            }
            var node = document.createTextNode(msg);
            this.target.prepend("<hr />");
            this.target.prepend(msg);
        }
    },
    trace : function(keyword){
        // prints the buffer. if keyword is given, it acts like a filter
        for (var i=0; i<this.buffer.length; i++){
            if (!keyword || this.buffer[i].args[0]==keyword){
                this.print.apply(this, this.buffer[i].args);
            }
        }
    },
    clearBuffer : function(){
        this.buffer = [];
    },
    watch : function(){
        this.DEBUG = true;
        for (var i=0; i<arguments.length; i++){
            var arg = arguments[i];
            if (arg){
                this.keywords[arg] = true;
            }
        }
        var watch_cookie = "";
        for (var p in this.keywords){
            watch_cookie+=p+",";
        }
        this.setCookie(watch_cookie);
    },
    watchAll : function(){
        this.DEBUG = true;
        this.WATCH_ALL = true;
    },
    unwatch : function(){
        var watch_cookie = this.getCookie();
        if (!watch_cookie) watch_cookie="";
        var watchers = watch_cookie.split(",");

        for (var i=0; i<arguments.length; i++){
            delete this.keywords[arguments[i]];
            for (var j=0; j<watchers.length; j++){
                if (watchers[j] == arguments[i]){
                    watchers.splice(j,1);
                }
            }
        }
        var new_cookie = watchers.join(",");
        if (new_cookie.length>0) new_cookie+=",";
        this.setCookie(new_cookie);
    },
    unwatchAll : function(){
        this.WATCH_ALL = false;
        this.DEBUG = false;
        for (var k in this.keywords){
            this.unwatch(k);
        }
    }
};

// create evil global variable for convenient access to the logger.
var logger = az.logger;
/*
  the az task queue is a simple queue object which runs a given
  command after everything is loaded. if the component ist not loaded
  yet, it keeps trying to call it. this is usefull if the javascripts
  for the component are loaded at the very bottom of the page for speed
  issues but the user already clicks a button on the loaded parts of the
  page.

  usage:
  az.queue.call('az.myobject.load', param1, param2...);

  just call functions where you do not require a callback because
  they can be called delayed!
 */


var az = az || {};

az.queue = {};

az.queue.task = {
    func: null,
    args: null,
    call_function : null,
    call_object : null,
    preparePointers : function(){
        var cb_obj, cb_func, path, i = null;
        if (!this.func){
            throw{name:'ValueError',message:'az.queue.tasks has no func value'};
        }
        if (this.call_object && this.call_function){
            return true;
        }
        cb = window;
        path = this.func.split(".");
        for (i=0; i<path.length; i++){
            cb = cb[path[i]];
            if (!cb){
                // path is not ready yet.
                return false;
            }
            if (i === path.length-2){
                this.call_object = cb;
            }
            else if (i === path.length-1){
                this.call_function = cb;
            }
        }
        return true;

    },
    process : function(){
        var success = this.preparePointers();
        if (!success){
            return false;
        }
        this.call_function.apply(this.call_object, this.args);
        return true;
    }
};

az.queue.mgr = {
    tasks : [],
    timeout_id : null,
    dom_ready : false,
    add : function(task){
        this.tasks.push(task);
        this.process();
    },
    process : function(){
        if (this.dom_ready){
            for (var i=this.tasks.length-1; i>=0; i--){
                var success = this.tasks[i].process();
                if (success){
                    this.tasks.splice(i,1);
                }
            }
        }
        if (this.tasks.length>0 && !this.timeout_id){
            this.timeout_id = setTimeout(function(){
                    az.queue.mgr.timeout_id = null;
                    az.queue.mgr.process();
                }, 4000);
        }
    }
};

$(document).ready(function(){
        az.queue.mgr.dom_ready = true;
        az.queue.mgr.process();
    });

az.queue.call = function(ptr){
    if (!ptr){
        throw {name:'ValueError', message:'az.queue.call requires ptr (and optionaly args)'};
    }
    args = $.merge([], arguments); // create a real array instance
    args.splice(0,1); // cut the call func.
    var task = Object.create(this.task);
    task.func = ptr;
    task.args = args;
    if (!task.func) {
        throw {name:'ValueError', message:'az.queue.call requires ptr (and optionaly args)'};
    }
    az.queue.mgr.add(task);

    return false; // returns false because the method call is used in onmouseclick
};
var az = az || {};

az.wemf = {
    iframe : null,
    init : function(){
        if (this.iframe) return;
        this.iframe = this.createIframe();
        this.log("init");
    },
    register : function(){
        // register event names which fire a pixel
        for (var i=0; i<arguments.length; i++){
            var event_name = arguments[i];
            this.log("register event "+event_name);
            az.events.register(event_name, function(){ az.wemf.refreshPixel(); });
        }
    },
    createIframe : function(){
        var html = '<iframe id="wemfpixel" class="wmfpixel" src=""></iframe>';
        $('body').append(html);
        return $('#wemfpixel');
    },
    refreshPixel : function(url){
        this.log('wemf refresh');
        if (!this.iframe) this.init();
        this.iframe[0].contentWindow.location.replace('/@@wemf.html');
        if (window['pageTracker']){
            pageTracker._trackPageview();
        }
    }
};
az.logger.makeLoggable(az.wemf, 'pixel');

/*############################################################################
 *#
 *# Copyright (c) 2007 Lovely Systems and Contributors.
 *# All Rights Reserved.
 *#
 *############################################################################
 *
 * THIS FILE MUST BE LOADED ON TOP OF THE PAGE
 * BEFORE EVERY OTHER SCRIPT. ONLY JQuery NEEDS TO BE LOADED FIRST!
 *
 * THIS IS A SET OF FUNCTIONS AND EXTENSIONS USED FOR
 * ALL LOVELYSYSTEMS JAVASCRIPT PACKAGES
 *
 */

if (!window.APP_SETTINGS) var APP_SETTINGS = {};

/* SET DOCUMENT DOMAIN */
//try {
    //APP_SETTINGS.DOMAIN = document.domain = window.location.hostname.replace(/.*\.(.+\..+)$/, '$1');
//} catch(e) {
//    if (console && console.warn) console.warn(e.message);
//}

/* PROVIDE FIREBUG CONSOLE FOR ALL BROWSERS */
(function(){
    if (!window.console) {
        var names = ["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd","notifyFirebug"];
        window.console = {};
        for (var i = 0; i < names.length; ++i) {
            window.console[names[i]] = function(){};
        }
    }
})();

/* CALLABLE TYPE */
var callableType = function(constructor) {
    return function() {
        var callableInstance = function() {
            var args = [];
            for (var i=0; i<arguments.length;i++) {
                args[i] = arguments[i];
            }
            args.unshift($(this));
            return callableInstance.__call__.apply(callableInstance, args);
        };
        constructor.apply(callableInstance, arguments);
        return callableInstance;
    };
};

/* FUNCTION FOR SUBCLASSING */
var extend = function(self, parents) {
    var l = parents.length, i=0;
    for (i; i<l; i++) {
        var parent = parents[i];
        var __constr__ = self.prototype.__init__;
        var __super__ = function(self, parent){
            return function(){
                parent.prototype.__init__.apply(this, arguments);
                __constr__.apply(this, arguments);
            };
        };
        self.prototype.__init__ = __super__(self,parent);
        for (var m in parent.prototype) {
            if (m != "__init__") {
                self.prototype[m] = parent.prototype[m];
            }
        }
    }
};

/* APPLY DISPATCHER TO SINGLETON */
var __makeDispatchable = function(singleton) {
    singleton._dispatcher = new ls.EventDispatcher();
    singleton.addEventListener = function(event,callback,weight) {
        this._dispatcher.addEventListener(event,callback,weight);
    };
    singleton.addEventOnce = function(event,callback,weight){
        this._dispatcher.addEventOnce(event,callback,weight);
    };
    singleton.removeEventListener = function(event) {
        this._dispatcher.removeEventListener(event);
    };
    singleton.dispatchEvent = function(event){
        this._dispatcher.dispatchEvent(event);
    };
};

/* COPY OBJECT ONTO ANOTHER */
var cp = function(from, base) {
    var obj = typeof base == "object" ? base : {};
    for (var k in from) {
        obj[k] = from[k];
    }
    return obj;
};


/* CHECK IF VALUE IS AN ARRAY */
var isArray = function(value) {
    return value &&
        typeof value === "object" &&
        typeof value.length === "number" &&
        typeof value.splice === "function" &&
        !(value.propertyIsEnumerable("length"));
};

/* EXTEND ARRAY TYPE */
Array.prototype.contains = function(value) {
    for (var i=0; i<this.length;i++) {
        if (this[i] === value) {
            return true;
        }
    }
    return false;
};
Array.prototype.index = function(value) {
    for (var i=0; i<this.length;i++) {
        if (this[i] === value) {
            return i;
        }
    }
    return null;
};
Array.prototype.groupBy = function(k,i) {
    var key = k || null;
    var indexed = i || false;
    if (!key) return this;
    var a = [];
    var b = indexed ? {} : [];
    for (var i=0; i<this.length; i++) {
        var v = this[i][k];
        if (!a.contains(v)) {
            a.push(v);
            if (indexed) {
                b[v] = [];
            } else {
                b.push([]);
            }
        }
        if (indexed) {
            b[v].push(this[i]);
        } else {
            var j = a.index(v);
            b[j].push(this[i]);
        }
    }
    return b;
};
Array.prototype.orderBy = function(k,o) {
    var key = k || null;
    var order = (o && ["asc","desc"].contains(o)) ? o : "asc";
    if (!key) return this;
    this.sort(function(a,b){
            var ak = a[key];
            var bk = b[key];
            if (!ak || !bk) return 0;
            if (ak == bk) return 0;
            return (ak > bk) ? 1 : -1;
        });
    if (order == "desc") return this.reverse();
    return this;
};

/* EXTEND STRING TYPE */
String.prototype.trim = function(chars) {
    var res = this.toString().replace(/^\s+|\s+$/g,"");
    if (chars) {
        if (typeof chars == "string") {
            var re = new RegExp(chars,"g");
            res = res.replace(re,"");
        } else if (isArray(chars)) {
            var regStr = "";
            for (var i=0; i<chars.length; i++) {
                regStr += chars[i];
                if (i < chars.length-1) regStr += "|";
            }
            regStr = ls.LovelyUtils.compileString("[${args}]",{"args":regStr});
            var re = new RegExp(regStr,"g");
            res = res.replace(re,"");
        } else if (typeof chars == "number") {
            var max = parseInt(chars,10);
            if (max <= res.length) max = res.length;
            res = res.substr(0,max);
        }
    }
    return res;
};
String.prototype.unicode = function(prefix) {
    var prefix = prefix || "\\u00";
    var res = this.toString();
    for (var i=128; i<256; i++) {
        var charx = String.fromCharCode(i);
        var re = new RegExp(charx,"ig");
        res = res.replace(re, escape(charx).replace("%",prefix));
    }
    return res;
};
String.prototype.normalize = function() {
    var res = this.toString().toLowerCase();
    var repl = [[String.fromCharCode(228),"ae"],
                [String.fromCharCode(246),"oe"],
                [String.fromCharCode(252),"ue"],
                [String.fromCharCode(223),"ss"],
                [String.fromCharCode(38),"und"]];
    res = res.replace(/^\s+|\s+$/g,"");
    for (var i=0; i<repl.length; i++) {
        var re = new RegExp(repl[i][0],"ig");
        res = res.replace(re,repl[i][1]);
    }
    res = res.replace(/[\+\.,;\\\/?!\^§$%\(\)]+/g," ");
    res = res.replace(/\s+/g,"-");
    res = res.replace(/[#\*'`´"“”˜·¯˙˚]+/g,"");
    return res;
};
String.prototype.noUmlNormalize = function() {
    var res = this.toString().toLowerCase();
    res = res.replace(/^\s+|\s+$/g,"");
    res = res.replace(/[\+\.,;\\\/?!\^§$%\(\)]+/g," ");
    res = res.replace(/\s+/g,"-");
    res = res.replace(/[#\*'`´"“”˜·¯˙˚]+/g,"");
    return res;
};

/*
    http://www.JSON.org/json2.js
    2008-09-01

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be used to
            select the members to be serialized. It filters the results such
            that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON2) {
    JSON2 = {};
}
(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON2 !== 'function') {

        Date.prototype.toJSON2 = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON2 =
        Number.prototype.toJSON2 =
        Boolean.prototype.toJSON2 = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapeable.lastIndex = 0;
        return escapeable.test(string) ?
            '"' + string.replace(escapeable, function (a) {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON2 method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON2 === 'function') {
            value = value.toJSON2(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON2 numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

            if (typeof value.length === 'number' &&
                    !value.propertyIsEnumerable('length')) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON2 values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON2 object does not yet have a stringify method, give it one.

    if (typeof JSON2.stringify !== 'function') {
        JSON2.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON2 text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON2.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON2 object does not yet have a parse method, give it one.

    if (typeof JSON2.parse !== 'function') {
        /** Lovely change
          * automatically parse datetime
          **/
        JSON2.parse = function (text, reviver) {
            if (!reviver) {
                var reviver = function(key,value){
                    var a;
                    if (key === "__jsonclass__" && typeof value === "object") {
                        a = value.date;
                        if (a) {
                            return a;
                        }
                    }
                    return value;
                };
            };

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON2 text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON2 patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON2 backslash pairs with '@' (a non-JSON2 character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON2 parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON2.parse');
        };
    }
})();

/*!
 * jQuery Form Plugin
 * version: 2.95 (30-JAN-2012)
 * @requires jQuery v1.3.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *	http://www.opensource.org/licenses/mit-license.php
 *	http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
	Usage Note:
	-----------
	Do not use both ajaxSubmit and ajaxForm on the same form.  These
	functions are intended to be exclusive.  Use ajaxSubmit if you want
	to bind your own submit handler to the form.  For example,

	$(document).ready(function() {
		$('#myForm').bind('submit', function(e) {
			e.preventDefault(); // <-- important
			$(this).ajaxSubmit({
				target: '#output'
			});
		});
	});

	Use ajaxForm when you want the plugin to manage all the event binding
	for you.  For example,

	$(document).ready(function() {
		$('#myForm').ajaxForm({
			target: '#output'
		});
	});

	When using ajaxForm, the ajaxSubmit function will be invoked for you
	at the appropriate time.
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
	// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
	if (!this.length) {
		log('ajaxSubmit: skipping submit process - no element selected');
		return this;
	}
	
	var method, action, url, $form = this;

	if (typeof options == 'function') {
		options = { success: options };
	}

	method = this.attr('method');
	action = this.attr('action');
	url = (typeof action === 'string') ? $.trim(action) : '';
	url = url || window.location.href || '';
	if (url) {
		// clean url (don't include hash vaue)
		url = (url.match(/^([^#]+)/)||[])[1];
	}

	options = $.extend(true, {
		url:  url,
		success: $.ajaxSettings.success,
		type: method || 'GET',
		iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
	}, options);

	// hook for manipulating the form data before it is extracted;
	// convenient for use with rich editors like tinyMCE or FCKEditor
	var veto = {};
	this.trigger('form-pre-serialize', [this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
		return this;
	}

	// provide opportunity to alter form data before it is serialized
	if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSerialize callback');
		return this;
	}

	var traditional = options.traditional;
	if ( traditional === undefined ) {
		traditional = $.ajaxSettings.traditional;
	}
	
	var qx,n,v,a = this.formToArray(options.semantic);
	if (options.data) {
		options.extraData = options.data;
		qx = $.param(options.data, traditional);
	}

	// give pre-submit callback an opportunity to abort the submit
	if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSubmit callback');
		return this;
	}

	// fire vetoable 'validate' event
	this.trigger('form-submit-validate', [a, this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
		return this;
	}

	var q = $.param(a, traditional);
	if (qx) {
		q = ( q ? (q + '&' + qx) : qx );
	}	
	if (options.type.toUpperCase() == 'GET') {
		options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
		options.data = null;  // data is null for 'get'
	}
	else {
		options.data = q; // data is the query string for 'post'
	}

	var callbacks = [];
	if (options.resetForm) {
		callbacks.push(function() { $form.resetForm(); });
	}
	if (options.clearForm) {
		callbacks.push(function() { $form.clearForm(options.includeHidden); });
	}

	// perform a load on the target only if dataType is not provided
	if (!options.dataType && options.target) {
		var oldSuccess = options.success || function(){};
		callbacks.push(function(data) {
			var fn = options.replaceTarget ? 'replaceWith' : 'html';
			$(options.target)[fn](data).each(oldSuccess, arguments);
		});
	}
	else if (options.success) {
		callbacks.push(options.success);
	}

	options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
		var context = options.context || options;	// jQuery 1.4+ supports scope context 
		for (var i=0, max=callbacks.length; i < max; i++) {
			callbacks[i].apply(context, [data, status, xhr || $form, $form]);
		}
	};

	// are there files to upload?
	var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
	var hasFileInputs = fileInputs.length > 0;
	var mp = 'multipart/form-data';
	var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

	var fileAPI = !!(hasFileInputs && fileInputs.get(0).files && window.FormData);
	log("fileAPI :" + fileAPI);
	var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;

	// options.iframe allows user to force iframe mode
	// 06-NOV-09: now defaulting to iframe mode if file input is detected
	if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
		// hack to fix Safari hang (thanks to Tim Molendijk for this)
		// see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
		if (options.closeKeepAlive) {
			$.get(options.closeKeepAlive, function() {
				fileUploadIframe(a);
			});
		}
  		else {
			fileUploadIframe(a);
  		}
	}
	else if ((hasFileInputs || multipart) && fileAPI) {
		options.progress = options.progress || $.noop;
		fileUploadXhr(a);
	}
	else {
		$.ajax(options);
	}

	 // fire 'notify' event
	 this.trigger('form-submit-notify', [this, options]);
	 return this;

	 // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
	function fileUploadXhr(a) {
		var formdata = new FormData();

		for (var i=0; i < a.length; i++) {
			if (a[i].type == 'file')
				continue;
			formdata.append(a[i].name, a[i].value);
		}

		$form.find('input:file:enabled').each(function(){
			var name = $(this).attr('name'), files = this.files;
			if (name) {
				for (var i=0; i < files.length; i++)
					formdata.append(name, files[i]);
			}
		});

		if (options.extraData) {
			for (var k in options.extraData)
				formdata.append(k, options.extraData[k])
		}

		options.data = null;

		var s = $.extend(true, {}, $.ajaxSettings, options, {
			contentType: false,
			processData: false,
			cache: false,
			type: 'POST'
		});

      //s.context = s.context || s;

      s.data = null;
      var beforeSend = s.beforeSend;
      s.beforeSend = function(xhr, o) {
          o.data = formdata;
          if(xhr.upload) { // unfortunately, jQuery doesn't expose this prop (http://bugs.jquery.com/ticket/10190)
              xhr.upload.onprogress = function(event) {
                  o.progress(event.position, event.total);
              };
          }
          if(beforeSend)
              beforeSend.call(o, xhr, options);
      };
      $.ajax(s);
   }

	// private function for handling file uploads (hat tip to YAHOO!)
	function fileUploadIframe(a) {
		var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
		var useProp = !!$.fn.prop;

		if (a) {
			if ( useProp ) {
				// ensure that every serialized input is still enabled
				for (i=0; i < a.length; i++) {
					el = $(form[a[i].name]);
					el.prop('disabled', false);
				}
			} else {
				for (i=0; i < a.length; i++) {
					el = $(form[a[i].name]);
					el.removeAttr('disabled');
				}
			};
		}

		if ($(':input[name=submit],:input[id=submit]', form).length) {
			// if there is an input with a name or id of 'submit' then we won't be
			// able to invoke the submit fn on the form (at least not x-browser)
			alert('Error: Form elements must not have name or id of "submit".');
			return;
		}
		
		s = $.extend(true, {}, $.ajaxSettings, options);
		s.context = s.context || s;
		id = 'jqFormIO' + (new Date().getTime());
		if (s.iframeTarget) {
			$io = $(s.iframeTarget);
			n = $io.attr('name');
			if (n == null)
			 	$io.attr('name', id);
			else
				id = n;
		}
		else {
			$io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
			$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
		}
		io = $io[0];


		xhr = { // mock object
			aborted: 0,
			responseText: null,
			responseXML: null,
			status: 0,
			statusText: 'n/a',
			getAllResponseHeaders: function() {},
			getResponseHeader: function() {},
			setRequestHeader: function() {},
			abort: function(status) {
				var e = (status === 'timeout' ? 'timeout' : 'aborted');
				log('aborting upload... ' + e);
				this.aborted = 1;
				$io.attr('src', s.iframeSrc); // abort op in progress
				xhr.error = e;
				s.error && s.error.call(s.context, xhr, e, status);
				g && $.event.trigger("ajaxError", [xhr, s, e]);
				s.complete && s.complete.call(s.context, xhr, e);
			}
		};

		g = s.global;
		// trigger ajax global events so that activity/block indicators work like normal
		if (g && ! $.active++) {
			$.event.trigger("ajaxStart");
		}
		if (g) {
			$.event.trigger("ajaxSend", [xhr, s]);
		}

		if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
			if (s.global) {
				$.active--;
			}
			return;
		}
		if (xhr.aborted) {
			return;
		}

		// add submitting element to data if we know it
		sub = form.clk;
		if (sub) {
			n = sub.name;
			if (n && !sub.disabled) {
				s.extraData = s.extraData || {};
				s.extraData[n] = sub.value;
				if (sub.type == "image") {
					s.extraData[n+'.x'] = form.clk_x;
					s.extraData[n+'.y'] = form.clk_y;
				}
			}
		}
		
		var CLIENT_TIMEOUT_ABORT = 1;
		var SERVER_ABORT = 2;

		function getDoc(frame) {
			var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
			return doc;
		}
		
		// Rails CSRF hack (thanks to Yvan Barthelemy)
		var csrf_token = $('meta[name=csrf-token]').attr('content');
		var csrf_param = $('meta[name=csrf-param]').attr('content');
		if (csrf_param && csrf_token) {
			s.extraData = s.extraData || {};
			s.extraData[csrf_param] = csrf_token;
		}

		// take a breath so that pending repaints get some cpu time before the upload starts
		function doSubmit() {
			// make sure form attrs are set
			var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (!method) {
				form.setAttribute('method', 'POST');
			}
			if (a != s.url) {
				form.setAttribute('action', s.url);
			}

			// ie borks in some cases when setting encoding
			if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
				$form.attr({
					encoding: 'multipart/form-data',
					enctype:  'multipart/form-data'
				});
			}

			// support timout
			if (s.timeout) {
				timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
			}
			
			// look for server aborts
			function checkState() {
				try {
					var state = getDoc(io).readyState;
					log('state = ' + state);
					if (state.toLowerCase() == 'uninitialized')
						setTimeout(checkState,50);
				}
				catch(e) {
					log('Server abort: ' , e, ' (', e.name, ')');
					cb(SERVER_ABORT);
					timeoutHandle && clearTimeout(timeoutHandle);
					timeoutHandle = undefined;
				}
			}

			// add "extra" data to form if provided in options
			var extraInputs = [];
			try {
				if (s.extraData) {
					for (var n in s.extraData) {
						extraInputs.push(
							$('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
								.appendTo(form)[0]);
					}
				}

				if (!s.iframeTarget) {
					// add iframe to doc and submit the form
					$io.appendTo('body');
					io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
				}
				setTimeout(checkState,15);
				form.submit();
			}
			finally {
				// reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
				if(t) {
					form.setAttribute('target', t);
				} else {
					$form.removeAttr('target');
				}
				$(extraInputs).remove();
			}
		}

		if (s.forceSync) {
			doSubmit();
		}
		else {
			setTimeout(doSubmit, 10); // this lets dom updates render
		}

		var data, doc, domCheckCount = 50, callbackProcessed;

		function cb(e) {
			if (xhr.aborted || callbackProcessed) {
				return;
			}
			try {
				doc = getDoc(io);
			}
			catch(ex) {
				log('cannot access response document: ', ex);
				e = SERVER_ABORT;
			}
			if (e === CLIENT_TIMEOUT_ABORT && xhr) {
				xhr.abort('timeout');
				return;
			}
			else if (e == SERVER_ABORT && xhr) {
				xhr.abort('server abort');
				return;
			}

			if (!doc || doc.location.href == s.iframeSrc) {
				// response not received yet
				if (!timedOut)
					return;
			}
			io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

			var status = 'success', errMsg;
			try {
				if (timedOut) {
					throw 'timeout';
				}

				var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
				log('isXml='+isXml);
				if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
					if (--domCheckCount) {
						// in some browsers (Opera) the iframe DOM is not always traversable when
						// the onload callback fires, so we loop a bit to accommodate
						log('requeing onLoad callback, DOM not available');
						setTimeout(cb, 250);
						return;
					}
					// let this fall through because server response could be an empty document
					//log('Could not access iframe DOM after mutiple tries.');
					//throw 'DOMException: not available';
				}

				//log('response detected');
				var docRoot = doc.body ? doc.body : doc.documentElement;
				xhr.responseText = docRoot ? docRoot.innerHTML : null;
				xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
				if (isXml)
					s.dataType = 'xml';
				xhr.getResponseHeader = function(header){
					var headers = {'content-type': s.dataType};
					return headers[header];
				};
				// support for XHR 'status' & 'statusText' emulation :
				if (docRoot) {
					xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
					xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
				}

				var dt = (s.dataType || '').toLowerCase();
				var scr = /(json|script|text)/.test(dt);
				if (scr || s.textarea) {
					// see if user embedded response in textarea
					var ta = doc.getElementsByTagName('textarea')[0];
					if (ta) {
						xhr.responseText = ta.value;
						// support for XHR 'status' & 'statusText' emulation :
						xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
						xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
					}
					else if (scr) {
						// account for browsers injecting pre around json response
						var pre = doc.getElementsByTagName('pre')[0];
						var b = doc.getElementsByTagName('body')[0];
						if (pre) {
							xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
						}
						else if (b) {
							xhr.responseText = b.textContent ? b.textContent : b.innerText;
						}
					}
				}
				else if (dt == 'xml' && !xhr.responseXML && xhr.responseText != null) {
					xhr.responseXML = toXml(xhr.responseText);
				}

				try {
					data = httpData(xhr, dt, s);
				}
				catch (e) {
					status = 'parsererror';
					xhr.error = errMsg = (e || status);
				}
			}
			catch (e) {
				log('error caught: ',e);
				status = 'error';
				xhr.error = errMsg = (e || status);
			}

			if (xhr.aborted) {
				log('upload aborted');
				status = null;
			}

			if (xhr.status) { // we've set xhr.status
				status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
			}

			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
			if (status === 'success') {
				s.success && s.success.call(s.context, data, 'success', xhr);
				g && $.event.trigger("ajaxSuccess", [xhr, s]);
			}
			else if (status) {
				if (errMsg == undefined)
					errMsg = xhr.statusText;
				s.error && s.error.call(s.context, xhr, status, errMsg);
				g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
			}

			g && $.event.trigger("ajaxComplete", [xhr, s]);

			if (g && ! --$.active) {
				$.event.trigger("ajaxStop");
			}

			s.complete && s.complete.call(s.context, xhr, status);

			callbackProcessed = true;
			if (s.timeout)
				clearTimeout(timeoutHandle);

			// clean up
			setTimeout(function() {
				if (!s.iframeTarget)
					$io.remove();
				xhr.responseXML = null;
			}, 100);
		}

		var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
			if (window.ActiveXObject) {
				doc = new ActiveXObject('Microsoft.XMLDOM');
				doc.async = 'false';
				doc.loadXML(s);
			}
			else {
				doc = (new DOMParser()).parseFromString(s, 'text/xml');
			}
			return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
		};
		var parseJSON = $.parseJSON || function(s) {
			return window['eval']('(' + s + ')');
		};

		var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4

			var ct = xhr.getResponseHeader('content-type') || '',
				xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
				data = xml ? xhr.responseXML : xhr.responseText;

			if (xml && data.documentElement.nodeName === 'parsererror') {
				$.error && $.error('parsererror');
			}
			if (s && s.dataFilter) {
				data = s.dataFilter(data, type);
			}
			if (typeof data === 'string') {
				if (type === 'json' || !type && ct.indexOf('json') >= 0) {
					data = parseJSON(data);
				} else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
					$.globalEval(data);
				}
			}
			return data;
		};
	}
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *	is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *	used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */
$.fn.ajaxForm = function(options) {
	// in jQuery 1.3+ we can fix mistakes with the ready state
	if (this.length === 0) {
		var o = { s: this.selector, c: this.context };
		if (!$.isReady && o.s) {
			log('DOM not ready, queuing ajaxForm');
			$(function() {
				$(o.s,o.c).ajaxForm(options);
			});
			return this;
		}
		// is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
		log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
		return this;
	}

	return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
		if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
			e.preventDefault();
			$(this).ajaxSubmit(options);
		}
	}).bind('click.form-plugin', function(e) {
		var target = e.target;
		var $el = $(target);
		if (!($el.is(":submit,input:image"))) {
			// is this a child element of the submit el?  (ex: a span within a button)
			var t = $el.closest(':submit');
			if (t.length == 0) {
				return;
			}
			target = t[0];
		}
		var form = this;
		form.clk = target;
		if (target.type == 'image') {
			if (e.offsetX != undefined) {
				form.clk_x = e.offsetX;
				form.clk_y = e.offsetY;
			} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
				var offset = $el.offset();
				form.clk_x = e.pageX - offset.left;
				form.clk_y = e.pageY - offset.top;
			} else {
				form.clk_x = e.pageX - target.offsetLeft;
				form.clk_y = e.pageY - target.offsetTop;
			}
		}
		// clear form vars
		setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
	});
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
	return this.unbind('submit.form-plugin click.form-plugin');
};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
	var a = [];
	if (this.length === 0) {
		return a;
	}

	var form = this[0];
	var els = semantic ? form.getElementsByTagName('*') : form.elements;
	if (!els) {
		return a;
	}

	var i,j,n,v,el,max,jmax;
	for(i=0, max=els.length; i < max; i++) {
		el = els[i];
		n = el.name;
		if (!n) {
			continue;
		}

		if (semantic && form.clk && el.type == "image") {
			// handle image inputs on the fly when semantic == true
			if(!el.disabled && form.clk == el) {
				a.push({name: n, value: $(el).val(), type: el.type });
				a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
			}
			continue;
		}

        v = $.fieldValue(el, true);

		if (v && v.constructor == Array) {
			for(j=0, jmax=v.length; j < jmax; j++) {
				a.push({name: n, value: v[j]});
			}
		}
		else if (v !== null && typeof v != 'undefined') {
			a.push({name: n, value: v, type: el.type});
		}
	}

	if (!semantic && form.clk) {
		// input type=='image' are not found in elements array! handle it here
		var $input = $(form.clk), input = $input[0];
		n = input.name;
		if (n && !input.disabled && input.type == 'image') {
			a.push({name: n, value: $input.val()});
			a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
		}
	}
	return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
	//hand off to jQuery.param for proper encoding
	return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
	var a = [];
	this.each(function() {
		var n = this.name;
		if (!n) {
			return;
		}
		var v = $.fieldValue(this, successful);
		if (v && v.constructor == Array) {
			for (var i=0,max=v.length; i < max; i++) {
				a.push({name: n, value: v[i]});
			}
		}
		else if (v !== null && typeof v != 'undefined') {
			a.push({name: this.name, value: v});
		}
	});
	//hand off to jQuery.param for proper encoding
	return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *	  <input name="A" type="text" />
 *	  <input name="A" type="text" />
 *	  <input name="B" type="checkbox" value="B1" />
 *	  <input name="B" type="checkbox" value="B2"/>
 *	  <input name="C" type="radio" value="C1" />
 *	  <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *	array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
	for (var val=[], i=0, max=this.length; i < max; i++) {
		var el = this[i];
		var v = $.fieldValue(el, successful);
		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
			continue;
		}
		v.constructor == Array ? $.merge(val, v) : val.push(v);
	}
	return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
	if (successful === undefined) {
		successful = true;
	}

	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
		(t == 'checkbox' || t == 'radio') && !el.checked ||
		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
		tag == 'select' && el.selectedIndex == -1)) {
			return null;
	}

	if (tag == 'select') {
		var index = el.selectedIndex;
		if (index < 0) {
			return null;
		}
		var a = [], ops = el.options;
		var one = (t == 'select-one');
		var max = (one ? index+1 : ops.length);
		for(var i=(one ? index : 0); i < max; i++) {
			var op = ops[i];
			if (op.selected) {
				var v = op.value;
				if (!v) { // extra pain for IE...
					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
				}
				if (one) {
					return v;
				}
				a.push(v);
			}
		}
		return a;
	}
	return $(el).val();
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function(includeHidden) {
	return this.each(function() {
		$('input,select,textarea', this).clearFields(includeHidden);
	});
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
	var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
	return this.each(function() {
		var t = this.type, tag = this.tagName.toLowerCase();
		if (re.test(t) || tag == 'textarea' || (includeHidden && /hidden/.test(t)) ) {
			this.value = '';
		}
		else if (t == 'checkbox' || t == 'radio') {
			this.checked = false;
		}
		else if (tag == 'select') {
			this.selectedIndex = -1;
		}
	});
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
	return this.each(function() {
		// guard against an input with the name of 'reset'
		// note that IE reports the reset function as an 'object'
		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
			this.reset();
		}
	});
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
	if (b === undefined) {
		b = true;
	}
	return this.each(function() {
		this.disabled = !b;
	});
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
	if (select === undefined) {
		select = true;
	}
	return this.each(function() {
		var t = this.type;
		if (t == 'checkbox' || t == 'radio') {
			this.checked = select;
		}
		else if (this.tagName.toLowerCase() == 'option') {
			var $sel = $(this).parent('select');
			if (select && $sel[0] && $sel[0].type == 'select-one') {
				// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	});
};

// expose debug var
$.fn.ajaxSubmit.debug = false;

// helper fn for console logging
function log() {
	if (!$.fn.ajaxSubmit.debug) 
		return;
	var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
	if (window.console && window.console.log) {
		window.console.log(msg);
	}
	else if (window.opera && window.opera.postError) {
		window.opera.postError(msg);
	}
};

})(jQuery);

/*############################################################################
 *#
 *# Copyright (c) 2007 Lovely Systems and Contributors.
 *# All Rights Reserved.
 *#
 *############################################################################
 *
 * @requires bootstrap.js
 * @requires jquery.form.js
 * @requires json2.js
 * @requires com.lovelysystems.api.js
 *
 */

var ls = ls || {};
ls.VERSION = "0.2a";
ls.AUTHOR = "Lovely Systems";
ls.DEBUG = false;
ls.log = function(){
    if (this.DEBUG) {
        if (console.notifyFirebug) {
            console.notifyFirebug(arguments,'debug','firebugAppendConsole');
        } else {
            for (var i=0; i<arguments.length; i++) {
                console.log(i, arguments[i]);
            }
        }
    }
};


ls.get = function(url, callback){
    $.ajax({
            method : "GET",
            url : url,
            error : function(e){
                ls.log(e);
            },
            success : function(response){
                var res;
                try {
                    res = JSON2.parse(response);
                } catch(e) {
                    res = response;
                }
                callback.call(this,res);
            }
        });
};
ls.filename = function(){
    var e = new TypeError("FILENAME");
    for (var key in e) {
        console.log(key+":",e[key]);
    }
    return e.fileName;
};


ls.Delegate = {
    context : null,
    delegations : [],
    create : function(obj, func, data) {
        var delegation = new ls.Delegation(obj, func, data);
        this.delegations.push(delegation);
        return delegation;
    },
    destroy : function() {
        for (var i=0; i<this.delegations.length; i++) {
            this.delegations[i].destroy();
            this.delegations[i] = null;
        }
        this.delegations = null;
        delete this.delegations;
    }
};


ls.Delegation = callableType(function(obj, func, data) {
    this.obj = obj;
    this.func = func;
    this.data = data;
    this.__call__ = function() {
        var args = ls.Utils.createArgumentsArray(arguments);
        args.unshift(this.data);
        this.func.apply(this.obj, args);
    };
    this.destroy = function() {
        this.obj = null;
        this.func = null;
        this.data = null;
        delete this.obj;
        delete this.func;
        delete this.data;
    };
});



ls.DOMClass = function(){
    this.DOMClass.apply(this,arguments);
};

ls.DOMClass.prototype.DOMClass = function(n,p,a){
    this.context = n || $(document);
    this.p = p || {};
    this.a = a || {};
    this.__init__.apply(this,arguments);
};

ls.DOMClass.prototype.__init__ = function(){
    ls.log(this.toString(),arguments);
};

ls.DOMClass.prototype.toString = function(){
    return "[object DOMClass]";
};





ls.AjaxEvent = function(type,data){
    this.type = type;
    this.data = (data) ? data : null;
    this.target = null;
};

ls.Event = {
    RPC_RESPONSE : "response",
    RPC_ERROR : "error",
    RPC_SUCCESS : "success",
    RPC_FAIL : "fail"
};


ls.EventDispatcher = function(){
    this.EventDispatcher.apply(this,arguments);
};

ls.EventDispatcher.prototype.EventDispatcher = function(){
    this.__init__.apply(this,arguments);
};

ls.EventDispatcher.prototype.__init__ = function(){
    this.listeners = {};
    this.once = {};
    this.unique = {};
};

ls.EventDispatcher.prototype.addEventListener = function(event,callback,weight) {
    if (typeof(event) != "string") {
        ls.log("Could not register Event ",event);
        return;
    }
    if (!this.listeners[event]) this.listeners[event] = [];
    var info = {"callback":callback};
    this.listeners[event].push(info);
    if (weight) {
        this.listeners[event].sort(function(a, b) { return a.weight-b.weight; });
    }
};

ls.EventDispatcher.prototype.removeEventListener = function(id) {
    if (!id) {
        ls.log("Could not unregister Event ",id);
        return false;
    }
    var e = id.split('-')[0];
    var i = parseInt(id.split('-')[1],10);
    var spliced = this.listeners[e].splice(i, 1);
    if (spliced.length == 1) {
        return true;
    }
    return false;
};

ls.EventDispatcher.prototype.dispatchEvent = function(event) {
    var stack = this.listeners[event.type];
    if (stack) {
        for (var i=0; i<stack.length; i++) {
            ls.log("CALLBACK",i,stack[i].callback, event.data);
            stack[i].callback.call(this, event.data);
        }
    }
    this.clearOnce(event);
};

ls.EventDispatcher.prototype.addEventOnce = function(event, callback, weight) {
    if (!this.once[event]) this.once[event] = [];
    var id = this.addEventListener(event, callback, weight);
    this.once[event].push(id);
    return id;
};

ls.EventDispatcher.prototype.unique = function(event, key, callback, weight) {
    if (!this.unique[event]) this._unique[event] = {};
    if (this.unique[event][key]) this.removeEventListener(this.unique[event][key]);
    var id = this.addEventListener(event, callback, weight);
    this.unique[event][key] = id;
};

ls.EventDispatcher.prototype.clearOnce = function(event) {
    if (!this.once[event]) return;
    for (var i=0; i<this._once[event].length;i++) {
      this.unregister(this._once[event][i]);
    }
    delete this.once[event];
};

ls.Document = new ls.EventDispatcher();



ls.AjaxLoader = {
    WRAP : "<div id='${id}\' class='${class}'></div>",
    LOADER : "<div class='loader-image-holder'><div class='loader-image'><img src='${src}' /></div></div>",
    LOADERSOURCE : APP_SETTINGS.MEDIA_URL + "images/ajax-loader.gif",
    ERRORSOURCE : APP_SETTINGS.MEDIA_URL + "images/ajax-error.gif",
    HTTPSTATUS :  null,
    AJAX_CLASS : "ajax-wrapper",

    target: null,

    init : function(data, context, event) {
        event.preventDefault();
        var url = $(event.target).attr("href");
        var target = $(event.target).attr("target");
        this.load(url, target, true, event);
    },

    load : function(url, id, o, event) {
        console.log("AjaxLoader.load", url, id, o, event);
        if (!url || !id){
            console.log("AjaxLoader.load abort");
            return false;
        }

        this.target = $(document.getElementById(id));
        console.log("target: ", this.target);

        var showOverlay = typeof o == "boolean" ? o : true;

        if (this.target.length < 0 || this.target.length > 1) {
            ls.log("No or too many targets.", arguments);
            return false;
        }
        if (!this.target.parent().hasClass(this.AJAX_CLASS)) {
            var wrapper = ls.Utils.compileString(this.WRAP,{"id" : "ajax-wrapper-"+new Date().getTime(),
                                                            "class" : this.AJAX_CLASS});
            this.target.wrap(wrapper);
        }
        var node = this.target.parent();
        /* in order to refresh comments
         * you have to load same page twice or more often
         * so let 'em do! */
        if (node.attr("ajax:current") == url) {
            return false;
        } else if (!url.match(/comment/)) {
            node.attr("ajax:current",url);
        }
        var historyData = {"key":id,"value":url};
        this.dispatchEvent(new ls.AjaxEvent(ls.Event.AJAX_LOAD, historyData));
        if (showOverlay) this.createLoaderImage();
        var eventData = {'node':node,'event':event};


        $.ajax({ url: url,
                 type: "GET",
                 error: function(xhr){
                    console.log("AjaxLoader on error: ",xhr, arguments);
                    ls.AjaxLoader.HTTPSTATUS = xhr.status;
                     ls.AjaxLoader.onErrorHTTP(url);
                 },
                 success: ls.Delegate.create(ls.AjaxLoader,
                                             ls.AjaxLoader.onAjaxLoadComplete,
                                             eventData)
               });
        return false;
    },
    onAjaxLoadComplete : function(data,context,response){
        if (response.indexOf("document.write") != -1){
            console.error("abort. must not inject content with document.write commands.");
            return;
        }
        data.node.empty().append(response);
        var attr = com.lovelysystems.Application.getCommandAttrs(data.event.target);
        var eventData = {
            'node': data.node,
            'code': attr.code
        };
        this.dispatchEvent(new ls.AjaxEvent(ls.Event.AJAX_COMPLETE, eventData));
        ls.Scrolling.scrollToNode(data[0]);
    },
    onErrorHTTP : function(url){
        ls.log('Got a '+this.HTTPSTATUS+' Status by this url: '+url);
        this.destroyLoaderImage(url);
        return;
    },
    createLoaderImage : function(){
        if (this.LOADERSOURCE) {
            var node = this.target.parent();
            node.css("position","relative");
            if($('.loader-image-holder',node).length < 1){
                var w = this.target.width();
                var h = this.target.height();
                node.prepend(ls.Utils.compileString(this.LOADER,{"src":this.LOADERSOURCE}));
                var loader = $(".loader-image-holder", node);
                loader.css("height",h);
                loader.css("width",w);
                var image = $(".loader-image", loader);
                var t = h/2 - image.height()/2;
                image.css("top",t);
            }
        }
    },
    destroyLoaderImage : function(url){
        var node = this.target.parent();
        var loaderNode = $(".loader-image", node);
        var tpl = '<p class="error">${message}<br />${status}<br />URL: ${url}</p>';
        var d = {'message':_('error-occured'),
                 'status':this.HTTPSTATUS,
                 'url':url};
        if (loaderNode.children().length < 2){
            loaderNode.append(ls.Utils.compileString(tpl,d));
        }
    },
    restoreHistory : function(data,context,event){
        for (n in event) {
            if (document.getElementById(n) !== null) {
                var id = n;
                var url = event[n];
                if (url == "DEFAULT") {
                    url = this.getBaseURL(id);
                }
                if (!url || !url.indexOf("/") === 0) {
                    ls.log(url+" is not a valid url");
                } else {
                    this.load(url,id);
                }
            }
        }
    },
    getBaseURL : function(id){
        if (typeof id == "string") {
            var node = $(document.getElementById(id));
            return node ? node.attr("ajax:base") : null;
        }
    }
};
__makeDispatchable(ls.AjaxLoader);



ls.NavigationDropdown = function(node){
    this.NavigationDropdown(node);
};

ls.NavigationDropdown.prototype.NavigationDropdown = function(node){
    this.context = node;
    this.__init__(node);
};

ls.NavigationDropdown.prototype.__init__ = function(node){
    this.bind();
};

ls.NavigationDropdown.prototype.bind = function(){
    this.context.bind("change", ls.Delegate.create(this,
                                                   this.onSelect,
                                                   null));
};

ls.NavigationDropdown.prototype.onSelect = function(data,context,event){
    ls.Utils.redirect(event.target.value);
};


/*############################################################################
 *#
 *# Copyright (c) 2007 Lovely Systems and Contributors.
 *# All Rights Reserved.
 *#
 *############################################################################
 *
 * @requires bootstrap.js
 *
 */

var com = {};

com.lovelysystems = {

    VERSION : "0.1rc1",
    AUTHOR : "Lovely Systems AG",

    load : function(lib,callback,hash){
        /* Firefox only! */
        var js = "com.lovelysystems."+lib+".js";
        if (hash) js += "?hash=" +hash;
        var e = new Error();
        var m = e.fileName.split("/");
        var url = e.fileName.replace(m[m.length-1], js);
        var start = new Date().getTime();
        var h = document.getElementsByTagName("head")[0];
        var k = document.createElement("script");
        k.setAttribute("type", "application/x-javascript");
        k.setAttribute("language", "javascript");
        k.setAttribute("charset", "UTF-8");
        k.setAttribute("src", url);
        k.onload = callback || function(){
            var end = new Date().getTime();
            var d = end-start;
            console.info(url+" loaded in "+d+"ms");
        };
        h.appendChild(k);
    },

    Application : {

        CSS_CLASS : 'appCmd',
        NAMESPACE : 'ajax',
        ATTR : 'attr',
        CMD : 'command',

        context : null,
        commands : {},

        toString : function(){
            return "<com.lovelysystems.Application "+com.lovelysystems.VERSION+">";
        },

        execute : function(context) {
            this.context = context || document;
            var elements = $("."+this.CSS_CLASS, this.context);
            var len = elements.length;
            for (var i=0; i<len; i++) {
                var elem = elements[i];
                var ajax_attr = this.getCommandAttrs(elem);
                var native_attr = this.getNativeAttrs(elem);
                var nsCmd = this.NAMESPACE + ':' + this.CMD;
                var cmd = elem.getAttribute(nsCmd);
                if (cmd) {
                    if (typeof this.commands[cmd] === "function") {
                        this.commands[cmd].call(this.commands[cmd], cmd, elem, ajax_attr, native_attr);
                    } else {
                        console.warn(cmd + " is not a valid function");
                    }
                }
            }
        },

        getCommandAttrs : function(elem) {
            var nsAttr = this.NAMESPACE + ':' + this.ATTR;
            var plist = elem.getAttribute(nsAttr);
            plist = plist ? plist.trim().split(";") : [];

            var attrs = {};
            for (var j=0; j<plist.length; j++) {
                var q = plist[j].trim();
                if (q) {
                    var p = q.split("=");
                    var key = p[0].trim();
                    attrs[key] = eval(p[1]);
                }
            }
            return attrs;
        },

        getNativeAttrs : function(elem) {
            var native_attr = {};
            for (var k=0; k<elem.attributes.length; k++) {
                var a = elem.attributes[k];
                if (a.nodeName.indexOf(this.NAMESPACE) == -1) {
                    native_attr[a.nodeName] = a.nodeValue;
                }
            }
            return native_attr;
        },

        registerCommand : function(cmd, callback) {
            this.commands[cmd] = callback;
        },

        removeCommand : function(cmd) {
            if (this.commands[cmd]) {
                this.commands[cmd] = null;
                delete this.commands[cmd];
            }
        },

        setBodyClass : function(){
            var body = document.getElementsByTagName('body')[0];
            if (isIE6) body.className = "ie6";
            if (isIE7) body.className = "ie7";
            if (isIE8) body.className = "ie8";
            if (isFF) body.className = "ff";
            if (isSF) body.className = "safari";
        },

        registry : {
            add : function(key,obj){
                if (this.datastore[key]) {
                    this.datastore[key].push(obj);
                } else {
                    this.datastore[key] = [obj];
                }
                return obj;
            },
            get : function(key){
                var res = this.datastore[key] || [];
                return res;
            },
            datastore : {}
        }
    }
};


/*
  simple router for calling javascript code
  based on hashtags.
*/
var az = az || {};
az.router = {};
az.router.hash = null;
az.router.setHash = function(hash){
    // use this method if you do not want to trigger
    // the route
    this.hash = '#'+hash;
    window.location.hash = hash;
};
az.router.checkHash = function(){
    // analyses the url and tries to find the
    // corresponding route to execute
    var hash = window.location.hash;

    if (hash == this.hash){
        this.log("abort checkhash. hash is still the old one", hash);
        return;
    }

    this.log("checkHash()", hash);
    if (!hash || hash=="#") return false;

    for (var route in this.routes){
        this.log(hash, ".match: ", route);
        if (hash.match(route)){
            this.log("match found: ", route);
            this.routes[route].apply(this);
            return true;
        }
        else{
            this.log("no match");
        }
    }
    this.log("no match found");
    return false;
};

az.router.routes = {
    "!/dashboard": function(){
        this.log("openDashboard without url");
        az.overlayManager.openDashboard();
    },
    "!/__dashboard/@@" : function(){
        // we use __dashboard for being able to pass comple urls
        // to the dashboard
        var url = window.location.hash.slice(2);
        this.log("openDashboard with url", url);
        az.overlayManager.openDashboard(url);
    },
    "!/login" : function(){
        this.log("openLogin");
        az.overlayManager.openLogin();
    },
    "!/signup" : function(){
        this.log("openSignup");
        az.overlayManager.openSignup();
    },
    "!/lostpassword" : function(){
        this.log("openLostPassword");
        az.overlayManager.openLostPassword();
    },
    "!/redirectAuthenticated" : function(){
        // redirect if url is in the pattern:
        // /#!/redirectAuthenticated=http://www.targeturl.com
        var url = window.location.hash;
        url = url.substr(url.indexOf("=")+1);
        az.authManager.redirectAuthenticated(url);
    }
};

az.logger.makeLoggable(az.router, 'router');

if ($.browser.msie && parseInt($.browser.version,10) < 8){
    az.current_hash = "";
    window.setInterval(function(){
        var hash = window.location.hash;
        if (hash && hash!=az.current_hash){
            az.current_hash = hash;
            az.router.checkHash();
        }
    }, 500);
}
else{
    $(window).bind('hashchange', function(){
        az.router.checkHash();
     });
}
/*############################################################################
 *#
 *# Copyright (c) 2007 Lovely Systems and Contributors.
 *# All Rights Reserved.
 *#
 *############################################################################
 *
 * @requires bootstrap.js
 * @requires com.lovelysystems.core.js
 * @requires com.lovelysystems.i18n.js
 *
 */

ls.Utils = {

    toString : function(){
        return "[object ls.Utils]";
    },

    compileString : function(string, dict) {
        var string = unescape(string);
        for (var p in dict) {
            var re = new RegExp('\\${'+p+'}', 'g');
            var repl = dict[p] || '';
            string = string.replace(re, repl);
        }
        return string;
    },

    equal : function(obj1, obj2) {
        if (!obj1 && obj2) return false;
        if (obj1 && !obj2) return false;

        var equal = true;
        for (p in obj1) {
            if (obj1[p] != obj2[p]) return false;
        }
        return equal;
    },

    equalArr : function(arr1, arr2) {
        if (arr1.length != arr2.length) return false;
        equal = true;
        for (var i=0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) return false;
        }
        return equal;
    },

    bool : function(obj) {
      if (typeof(obj) == 'object') {
        for (var p in obj) return true;
        return false;
      }
      return Boolean(obj);
    },

    regMap : function(str, map) {
      for (var name in map) if (str.match(map[name])) return name;
    },

    createArgumentsArray : function(args) {
      var arr = [];
      for (var i=0; i<args.length;i++) {
        arr[i]=args[i];
      }
      return arr;
    },

    convertTimestamp : function(timestamp) {
        var date = new Date(timestamp);
        var day  = (date.getDate().toString().length == 1) ? "0"+date.getDate() : date.getDate();
        var month = (date.getMonth().toString().length == 1) ? "0"+date.getMonth() : date.getMonth();
        var year = date.getFullYear();
        var hours = (date.getHours().toString().length == 1) ? "0"+date.getHours() : date.getHours();
        var seconds = (date.getMinutes().toString().length == 1) ? "0"+date.getMinutes() : date.getMinutes();
        return day+"."+month+"."+year+" "+hours+":"+seconds+" Uhr";
    },

    getNSAttributes : function(elem, namespace) {
        var attrs = {};
        for (var k=0; k<elem.attributes.length; k++) {
            var a = elem.attributes[k];
            if (a.nodeName.indexOf(namespace) == 0) {
                attrs[a.nodeName] = a.nodeValue;
            }
        }
        return attrs;
    },

    parseQueryString : function(qs, type, html){
        var queryString = (typeof qs === "string" && qs.length > 0) ? qs : null;
        if (!queryString) return null;
        var type = ['dict','data'].contains(type) ? type : 'dict';
        var allowHtml = typeof html === 'boolean' ? html : false;
        var kvpairs = queryString.split("&");
        if (!kvpairs) {
            if (type == 'data') {
                return {'data':{}};
            } else if (type == 'dict') {
                return {};
            }
        }

        var o = {};
        for (var i=0; i<kvpairs.length; i++) {
            var kv = kvpairs[i].split("=");
            o = this.extendObject(o, kv);
        }
        ls.log("parseQueryString:", qs, type, html, o);
        return o;
    },

    extendObject : function(obj, kv){
        ls.log(obj, kv);
        var v = kv[1];
        var k = kv[0].split('.');
        var key = k.shift();
        if (!obj) {
            if (key) {
                var obj = {};
            } else {
                v = decodeURIComponent(v.replace(/\+/g,"%20"));
                v = v.replace(/<script\b[^>]*>(.*?)<\/script>/i,"");
                if (['True','true'].contains(v)) v = true;
                if (['False','false'].contains(v)) v = false;
                if (['None','none'].contains(v)) v = null;
                return v;
            }
        }
        obj[key] = this.extendObject(obj[key], [k.join('.'),v]);
        return obj;
    },

    redirect : function(url, context, event){
        if (!url) return;
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        var same = false;
        if (url.match(/#/g) && window.location.href && url) {
            var h = window.location.href.split('#');
            var u = url.split('#');
            if (h && u && (h[0].indexOf(u[0]) >= 0)) {
                same = true;
            }
        }
        if (same) {
            window.location.href = url;
            window.location.reload();
        } else {
            window.setTimeout(function() {
                    window.location.href = url;
                }, 5);
        }
    },

    reload : function(){
        window.setTimeout(function() {
                window.location.hash = '';
                window.location.reload();
            },5);
    }

};


ls.Scrolling = {

    toString : function(){
        return "[object ls.Scrolling]";
    },

    scrollToElement : function(elementId, yoffset) {
        var yoffset = (yoffset) ? yoffset : 0;
        var el = document.getElementById(elementId);
        window.scrollTo(0, this.getOffset(el,yoffset));
    },

    scrollToNode : function(element) {
        if (!element) return;
        var offset = this.getOffset(element,0);
        if (offset<window.pageYOffset) {
            window.scrollTo(0, offset);
        }
    },

    getOffset : function(element,offset){
        var yoffset = yoffset ? yoffset : 0;
        var scrollY = 0;
        while (element != null){
            scrollY += element.offsetTop;
            element = element.offsetParent;
        }
        return scrollY + offset;
    }

};


ls.GarbageCollector = {

    toString : function(){
        return "[object ls.GarbageCollector]";
    },

    garbage : [],

    addItem : function(item) {
        this.garbage.push(item);
    },

    purge : function() {
        while (this.garbage.length > 0) {
            var item = this.garbage.pop();
            delete item;
        }
    }

};

__makeDispatchable(ls.GarbageCollector);


ls.Cookie = {

    toString : function(){
        return "[object ls.Cookie]";
    },

    set : function(c_name,value,expire,path){
        if (!path) path="/";
        var exdate = new Date();
        if (typeof(expire) === "object"){
            // seems to be a date
            exdate = expire;
        }
        else{
            // seems to be a number
            exdate.setDate(exdate.getDate()+expire);
        }
        var c = c_name+ "=" +value;
        if (expire != null) c += ";expires="+exdate.toGMTString();
        c += ";path="+path;
        document.cookie = c;
    },

    get : function(c_name){
        if (document.cookie.length>0) {
            c_start = document.cookie.indexOf(c_name + "=");
            if (c_start!=-1) {
                c_start=c_start + c_name.length+1;
                c_end=document.cookie.indexOf(";",c_start);
                if (c_end==-1) c_end=document.cookie.length;
                return unescape(document.cookie.substring(c_start,c_end));
            }
        }
        return false;
    }

};


ls.StorageManager = {

    toString : function(){
        return "[object ls.StorageManager]";
    },

    getDomain : function(){
        return window.location.hostname.replace(/\./g,"");
    },

    getWindowData : function(){
        var data = {};
        try {
            data = (window.name != "") ? JSON2.parse(window.name) : {};
        } catch (e) {
            window.name = "{}";
        }
        return data;
    },

    getData : function(key) {
        var ns = this.getDomain();
        var win = this.getWindowData();
        return win[ns] ? (win[ns][key] || win[ns]) : {};
    },

    addData : function(key,val){
        var win = this.getWindowData();
        var ns = this.getDomain();
        if (!win[ns]) win[ns] = {};
        win[ns][key] = val;
        window.name = JSON2.stringify(win);
    },

    pushData : function(key,val){
        var data = this.getData(key);
        if (isArray(data)) {
            if (isArray(val)) {
                for (var i=0; i<val.length; i++) {
                    data.push(val[i]);
                }
            } else {
                data.push(val);
            }
        } else {
            data = [val];
        }
        this.addData(key, data);
    }

};

__makeDispatchable(ls.StorageManager);

az = az || {};
az.authManager = {
    whoami:null,
    redirectAuthenticated: function (target_url) {
        //is used to switch to a different domain and be sure that you
        //are logged in
        if (this.isLoggedIn()) {
            window.location.href = target_url;
            return;
        }
        az.router.setHash("!/redirectAuthenticated="+target_url);
        az.overlayManager.openLogin();
    },
    isLoggedIn: function(){
        if (this.whoami===null){
            $.ajax({url: '/auth',
                    type : 'POST',
                    async: false,
                    data: JSON2.stringify({
                        id:1,
                        version:'1.1',
                        method:'whoami',
                        params:[[]]
                    }),
                    dataType: 'json',
                    contentType: 'application/json-rpc',
                    success : function(response){
                        if (response['result']){
                            az.authManager.whoami = response.result.title;
                        }
                        else{
                            az.authManager.whoami = "";
                        }
                    }
                   });
        }
        this.log("isLoggedIn: ", this.whoami);
        return this.whoami
    }
};
az.logger.makeLoggable(az.authManager, 'authmgr');

/*
  az overlay manager

  opens the given URL in the overlay.
  depending on the URL, it opens the overlay in a specific size
  and mode (iframe or ajax)

  Usage:
  please use the overlay manager in combination with router.js
  e.g. start dashboard using #!/dashboard

  for iframe overlays, make sure that the <body> has a margin = 0px.
*/
az = az || {};
az.overlayManager = {
    OFFSET_TOP : 25, // y position of the overlay
    MARGIN_CONTENT : 0, // inner margin of the iframe content
    DEFAULT_WIDTH : 571, // width for #overlay-inner-content
    width : null,
    minHeight : 540,
    isOpen : false,
    isIframe : false,
    content : null,
    resize_interval : null,
    hide_header : false,
    nodes : {
        holder : null,
        content : null,
        content_iframe : null,
        overlay_position : null,
        title : null,
        inner_content : null,
        header : null,
        footer : null,
        background : null,
        background_iframe : null
    },
    toString : function(){
        return "[object az.overlayManager]";
    },
    openLogin: function(){
        // trigger this method directly in a link or with router.js
        this.open('/@@login.ajax');
    },
    openSignup: function(){
        this.open('/@@register.ajax');
    },
    openLostPassword: function(){
        this.open('/@@lostPassword.ajax');
    },
    openDashboard: function(url){
        if (!url) url = "/__dashboard/@@__dashboard";
        this.open(url, {iframe:true, width:860, login_required:true, hide_header:true});
    },
    open : function(url, options) {
        this.log("open", url, options);
        if (options && options.login_required){
            if (!az.authManager.isLoggedIn()){
                this.log("login required, but user is unauthenticated. switch to login");
                this.openLogin();
                return false;
            }
        }
        if (!this.nodes.holder || this.nodes.holder.length === 0){
            // 1st call of open()
            this.nodes.holder = $("#ls #overlay-holder");
            this.nodes.content = $("#ls #overlay-content");
            this.nodes.overlay_position = $("#ls #overlay-position");
            this.nodes.content_iframe = $("#ls #overlay-content-iframe");
            this.nodes.title = $('#overlay-content-title');
            this.nodes.inner_content = $("#ls #overlay-inner-content");
            this.nodes.header = $('#ls #overlay-content-header');
            this.nodes.footer = $('#ls #overlay-content-footer');
            this.nodes.background = $("#ls #overlay-background");
            this.nodes.background_iframe = $("#ls #overlay-background-iframe");
            this.nodes.loading_spinner = $("#ls #overlay-loading-spinner");
        }
        else{
            this.reset();
        }

        if (this.nodes.holder.length === 0){
            // we do have a timing problem. the dom is not ready yet.
            return false;
        }

        this.isIframe = options && options['iframe'] || false;
        this.width = options && options['width'] || this.DEFAULT_WIDTH;
        this.hide_header = options && options['hide_header'] || false;

        if (this.isIframe){
            this.nodes.content_iframe.attr("src", "");
            this.nodes.content_iframe.show();
            this.nodes.content.hide();
            var interv_func = function(){
                    // resize the iframe, no scrollbar should be required
                var height = 0;
                try{
                    height =  $(az.overlayManager.nodes.
                                content_iframe[0].contentWindow.
                                document.body).height(); // + 25; // 25 - additional buffer
                }
                catch(err){}
                if (height === 0) return;
                az.overlayManager.resizeOverlay(height);
                // set the titlle of the overlay depending the <title> of the iframe
                var title_tags = (az.overlayManager.nodes.content_iframe[0].
                                  contentWindow.document.getElementsByTagName("title"));
                if (title_tags.length>0){
                    var title = title_tags[0].innerHTML;
                    az.overlayManager.nodes.title.text(title);
                }
            };
            this.resize_interval = window.setInterval(interv_func,1000);
            interv_func();
        }
        else{
            this.nodes.content.show();
        }
        if (this.isOpen) {
            this.switchOverlay(url);
        }
        else {
            this.openOverlay(url);
        }
        if (this.hide_header) this.nodes.header.hide();
    },
    resizeOverlay : function(height){
        if (height < this.minHeight) height = this.minHeight;
        if (!this.hide_header) {
            height += this.nodes.header.height();
        }
        height += this.MARGIN_CONTENT;
        if (this.isIframe){
            this.nodes.content_iframe.css("width", this.width);
            this.nodes.content_iframe.css("height", height);
            this.nodes.inner_content.css("height", height);
            this.nodes.footer.hide();
        }
        else{
            this.nodes.inner_content.css("height", "");
            this.nodes.footer.show();
        }
        var h = $(document).height();
        this.nodes.background.css("height", h);
        this.nodes.background_iframe.css("height", h);
    },
    openOverlay : function(url) {
        var h = $(document).height();
        var top;
        if ($.browser.safari) {
            top = document.body.scrollTop;
        }
        else {
            top = document.documentElement.scrollTop;
        }
        this.nodes.overlay_position.css("top",top+this.OFFSET_TOP);
        this.nodes.inner_content.css("width", this.width);

        this.resizeOverlay(this.minHeight);

        this.nodes.holder.show();
        this.loadOverlayContent(url);
    },
    loadOverlayContent : function(url) {
        if (this.isIframe){
            this.setBusy(true);
            this.nodes.content_iframe.attr("src", url);
            this.onOverlayComplete();
        }
        else{
            this.setBusy(true);
            $.ajax({ url: url,
                        type: "GET",
                        error: ls.Delegate.create(this,
                                                  this.onError,
                                                  null),
                        success: ls.Delegate.create(this,
                                                    this.onSuccess,
                                                    null)
                        });
        }
    },
    onSuccess : function(data, context, response) {
        this.log("onSuccess");
        this.onOverlayComplete(response);
    },
    onError : function(data, context, response) {
        var xhr = response || {'status':'undefined','statusText':'No XHR response!'};
        response = '<h4 class="ajax-error">Es ist ein Fehler aufgetreten!</h4><p>'+xhr.status+' '+xhr.statusText+'</p>';
        this.onOverlayComplete(response);
    },
    onOverlayComplete : function(data) {
        this.log("onOverlayComplete");
        this.log("debug: ", this.nodes);
        if (!this.isIframe){
            this.nodes.content.show();
            this.nodes.content.empty().append(data);
            this.log("debug: appended data ", data);
            this.nodes.title.text($('#overlay_box').attr('data-overlayTitle'));
        }
        this.nodes.loading_spinner.hide();
        $(".close-button span", this.nodes.content).show();
        this.nodes.content.css("height", "");
        this.nodes.content.css("visibility","visible");
        az.pretty_tooltip.init();
        $(".ajax-init").mouseover().removeClass('ajax-init');
    },
    switchOverlay : function(url) {
        var h = this.nodes.content.height();
        this.nodes.content.css("height", h);
        this.loadOverlayContent(url);
    },
    closeOverlay : function(data, context, event) {
        this.nodes.holder.hide();
        this.isOpen = false;
        this.onOverlayClosed();
        this.clearInterval();
        az.router.setHash(""); // make sure a second click on a route does work!
    },
    clearInterval : function(){
        if (this.resize_interval){
            window.clearInterval(this.resize_interval);
            this.resize_interval = null;
        }
    },
    onOverlayClosed : function(){
        $(".close-button span", this.nodes.content).hide();
        this.nodes.content.css("width","");
        this.nodes.content.css("height","");
        this.nodes.content.empty();
        this.reset();
    },
    reset: function(){
        this.width = null;
        this.nodes.title.text("");
        this.nodes.content.empty();
        if (this.nodes.content_iframe[0] &&
            this.nodes.content_iframe[0].contentWindow &&
            this.nodes.content_iframe[0].contentWindow.document &&
            this.nodes.content_iframe[0].contentWindow.document.body){
            this.nodes.content_iframe[0].contentWindow.document.body.innerHTML = "";
        }
        this.nodes.content_iframe.css("width", "0px").css("height","0px");
        this.nodes.holder.hide();
        this.nodes.content_iframe.hide();
        this.nodes.footer.hide();
        this.nodes.content.css("width", "");
        this.nodes.content.css("height", "");
        this.nodes.inner_content.css("width", "");
        this.nodes.inner_content.css("height", "");
        this.nodes.background.css("height", "");
        this.nodes.overlay_position.css("top","");
        this.clearInterval();
    },
    setBusy : function(bool) {
        this.isOpen = true;
        this.nodes.content.hide();
        this.nodes.loading_spinner.show();
    }
};
az.logger.makeLoggable(az.overlayManager, 'overlaymgr');


/* used for links with title to show fancy tooltips */
var az = az || {};
az.pretty_tooltip = {};
az.pretty_tooltip.count = 0;
az.pretty_tooltip.hijack = function (nodes){
    var name = 'tooltip';
    nodes.each(function(i){
            if ($(this).attr("title")) {
                var count = az.pretty_tooltip.count++;
                $("body").append("<div class='"+name+"' id='"+name+count+"'><p>"+
                                 $(this).attr('title')+"</p></div>");
                var my_tooltip = $("#"+name+count);
                $(this).removeAttr("title").mouseover(function(){
                        my_tooltip.css({"opacity":0.8, "display":"none"}).show();
                    }).mousemove(function(kmouse){
                            var ttheight = my_tooltip.height();
                            var ttwidth = my_tooltip.width();
                            var x = kmouse.pageX;
                            if (x + ttwidth > $(window).width()) {
                                my_tooltip.attr('class',name + 'lefty');
                                my_tooltip.css({"left":kmouse.pageX - ttwidth,
                                            "top":kmouse.pageY - ttheight - 28});
                            }
                            else {
                                my_tooltip.attr('class',name);
                                my_tooltip.css({"left":kmouse.pageX,
                                            "top":kmouse.pageY - ttheight - 28});
                            }
                        }).mouseout(function(){
                                my_tooltip.hide();
                            });
            }
        });
};

az.pretty_tooltip.init = function(node, selector){
    if (!node) node = $('body');
    if (!selector) selector = "a.hastooltip";
    var nodes = $(selector, node);
    this.hijack(nodes);
};

