/*
    http://www.JSON.org/json2.js
    2008-03-24

    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 three methods: stringify,
    parse, and quote.


        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 without a toJSON
                        method. It can be a function or an array.

            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'), 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.

            This is the toJSON method added to Dates:

                function toJSON(key) {
                    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 no replacer parameter is provided, then a default replacer
            will be used:

                function replacer(key, value) {
                    return Object.hasOwnProperty.call(this, key) ?
                        value : undefined;
                }

            The default replacer is passed the key and value for each item in
            the structure. It excludes inherited members.

            If the replacer parameter is an array, 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 representaions, 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
            then 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]'


        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;
            });


        JSON.quote(text)
            This method wraps a string in quotes, escaping some characters
            as needed.


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD THIRD PARTY
    CODE INTO YOUR PAGES.
*/

/*jslint regexp: true, forin: true, evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length,
    parse, propertyIsEnumerable, prototype, push, quote, replace, stringify,
    test, toJSON, toString
*/

if (!this.JSON) {

// Create a JSON object only if one does not already exist. We create the
// object in a closure to avoid global variables.

    JSON = function () {

        function f(n) {    // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function () {

// Eventually, this method will be based on the date.toISOString method.

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };


        var escapeable = /["\\\x00-\x1f\x7f-\x9f]/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.

            return escapeable.test(string) ?
                '"' + string.replace(escapeable, function (a) {
                    var c = meta[a];
                    if (typeof c === 'string') {
                        return c;
                    }
                    c = a.charCodeAt();
                    return '\\u00' + Math.floor(c / 16).toString(16) +
                                               (c % 16).toString(16);
                }) + '"' :
                '"' + 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 toJSON method, call it to obtain a replacement value.

            if (value && typeof value === 'object' &&
                    typeof value.toJSON === 'function') {
                value = value.toJSON(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':

// JSON 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-JSON 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 (typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value, rep);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        v = str(k, value, rep);
                        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;
            }
        }


// Return the JSON object containing the stringify, parse, and quote methods.

        return {
            stringify: function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON 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 (space) {

// 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 no replacer parameter, use the default replacer.

                if (!replacer) {
                    rep = function (key, value) {
                        if (!Object.hasOwnProperty.call(this, key)) {
                            return undefined;
                        }
                        return value;
                    };

// The replacer can be a function or an array. Otherwise, throw an error.

                } else if (typeof replacer === 'function' ||
                        (typeof replacer === 'object' &&
                         typeof replacer.length === 'number')) {
                    rep = replacer;
                } else {
                    throw new Error('JSON.stringify');
                }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

                return str('', {'': value});
            },


            parse: function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON 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 three stages. In the first stage, we run the text against
// regular expressions that look for non-JSON 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 first stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace all backslash pairs with '@' (a non-JSON 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(/\\["\\\/bfnrtu]/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the second 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 third 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 JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('JSON.parse');
            },

            quote: quote
        };
    }();
}


if(!this.JS) {

/**
 * Copyright (c) 2007-2008 James Coglan
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Parts of this software are derived from the following open-source projects:
 *
 * - The Prototype framework, (c) 2005-2007 Sam Stephenson
 * - Alex Arnell's Inheritance library, (c) 2006, Alex Arnell
 * - Base, (c) 2006-7, Dean Edwards
 */

JS = {
  extend: function(object, methods) {
    methods = methods || {};
    for (var prop in methods) {
      if (object[prop] === methods[prop]) continue;
      object[prop] = methods[prop];
    }
    return object;
  },
  
  makeFunction: function() {
    return function() {
      return this.initialize
          ? (this.initialize.apply(this, arguments) || this)
          : this;
    };
  },
  
  makeBridge: function(klass) {
    var bridge = function() {};
    bridge.prototype = klass.prototype;
    return new bridge;
  },
  
  delegate: function(property, method) {
    return function() {
      return this[property][method].apply(this[property], arguments);
    };
  },
  
  bind: function() {
    var args = JS.array(arguments), method = args.shift(), object = args.shift() || null;
    return function() {
      return method.apply(object, args.concat(JS.array(arguments)));
    };
  },
  
  callsSuper: function(func) {
    return func.SUPER === undefined
        ? func.SUPER = /\bcallSuper\b/.test(func.toString())
        : func.SUPER;
  },
  
  mask: function(func) {
    var string = func.toString().replace(/callSuper/g, 'super');
    func.toString = function() { return string };
    return func;
  },
  
  array: function(iterable) {
    if (!iterable) return [];
    if (iterable.toArray) return iterable.toArray();
    var length = iterable.length, results = [];
    while (length--) results[length] = iterable[length];
    return results;
  },
  
  indexOf: function(haystack, needle) {
    for (var i = 0, n = haystack.length; i < n; i++) {
      if (haystack[i] === needle) return i;
    }
    return -1;
  },
  
  isFn: function(object) {
    return object instanceof Function;
  },
  
  ignore: function(key, object) {
    return /^(include|extend)$/.test(key) && typeof object === 'object';
  }
};

JS.Module = JS.makeFunction();
JS.extend(JS.Module.prototype, {
  initialize: function(methods, options) {
    options = options || {};
    this.__mod__ = this;
    this.__inc__ = [];
    this.__fns__ = {};
    this.__dep__ = [];
    this.__res__ = options._resolve || null;
    this.include(methods || {});
  },
  
  define: function(name, func, options) {
    options = options || {};
    this.__fns__[name] = func;
    if (JS.Module._notify && options._notify && JS.isFn(func))
        JS.Module._notify(name, options._notify);
    var i = this.__dep__.length;
    while (i--) this.__dep__[i].resolve();
  },
  
  instanceMethod: function(name) {
    var method = this.lookup(name).pop();
    return JS.isFn(method) ? method : null;
  },
  
  include: function(module, options, resolve) {
    if (!module) return resolve && this.resolve();
    options = options || {};
    var inc = module.include, ext = module.extend, modules, i, n, method,
        includer = options._included || this;
    
    if (module.__inc__ && module.__fns__) {
      this.__inc__.push(module);
      module.__dep__.push(this);
      if (options._extended) module.extended && module.extended(options._extended);
      else module.included && module.included(includer);
    }
    else {
      if (options._recall) {
        for (method in module) {
          if (JS.ignore(method, module[method])) continue;
          this.define(method, module[method], {_notify: includer || options._extended || this});
        }
      } else {
        if (typeof inc === 'object') {
          modules = [].concat(inc);
          for (i = 0, n = modules.length; i < n; i++)
            includer.include(modules[i], options);
        }
        if (typeof ext === 'object') {
          modules = [].concat(ext);
          for (i = 0, n = modules.length; i < n; i++)
            includer.extend(modules[i], false);
          includer.extend();
        }
        options._recall = true;
        return includer.include(module, options, resolve);
      }
    }
    resolve && this.resolve();
  },
  
  includes: function(moduleOrClass) {
    if (Object === moduleOrClass || this === moduleOrClass || this.__res__ === moduleOrClass.prototype)
      return true;
    var i = this.__inc__.length;
    while (i--) {
      if (this.__inc__[i].includes(moduleOrClass))
        return true;
    }
    return false;
  },
  
  ancestors: function(results) {
    results = results || [];
    for (var i = 0, n = this.__inc__.length; i < n; i++)
      this.__inc__[i].ancestors(results);
    var klass = (this.__res__||{}).klass,
        result = (klass && this.__res__ === klass.prototype) ? klass : this;
    if (JS.indexOf(results, result) === -1) results.push(result);
    return results;
  },
  
  lookup: function(name) {
    var ancestors = this.ancestors(), results = [], i, n, method;
    for (i = 0, n = ancestors.length; i < n; i++) {
      method = ancestors[i].__mod__.__fns__[name];
      if (method) results.push(method);
    }
    return results;
  },
  
  make: function(name, func) {
    if (!JS.isFn(func) || !JS.callsSuper(func)) return func;
    var module = this;
    return function() {
      return module.chain(this, name, arguments);
    };
  },
  
  chain: JS.mask( function(self, name, args) {
    var callees = this.lookup(name),
        stackIndex = callees.length - 1,
        currentSuper = self.callSuper,
        params = JS.array(args),
        result;
    
    self.callSuper = function() {
      var i = arguments.length;
      while (i--) params[i] = arguments[i];
      stackIndex -= 1;
      var returnValue = callees[stackIndex].apply(self, params);
      stackIndex += 1;
      return returnValue;
    };
    
    result = callees.pop().apply(self, params);
    currentSuper ? self.callSuper = currentSuper : delete self.callSuper;
    return result;
  } ),
  
  resolve: function(target) {
    var target = target || this, resolved = target.__res__, i, n, key, made;
    
    if (target === this) {
      i = this.__dep__.length;
      while (i--) this.__dep__[i].resolve();
    }
    
    if (!resolved) return;
    
    for (i = 0, n = this.__inc__.length; i < n; i++)
      this.__inc__[i].resolve(target);
    for (key in this.__fns__) {
      made = target.make(key, this.__fns__[key]);
      if (resolved[key] !== made) resolved[key] = made;
    }
  }
});

JS.ObjectMethods = new JS.Module({
  __eigen__: function() {
    if (this.__meta__) return this.__meta__;
    var module = this.__meta__ = new JS.Module({}, {_resolve: this});
    module.include(this.klass.__mod__);
    return module;
  },
  
  extend: function(module, resolve) {
    return this.__eigen__().include(module, {_extended: this}, resolve !== false);
  },
  
  isA: function(moduleOrClass) {
    return this.__eigen__().includes(moduleOrClass);
  },
  
  method: function(name) {
    var self = this, cache = self.__mcache__ = self.__mcache__ || {};
    if ((cache[name] || {}).fn === self[name]) return cache[name].bd;
    return (cache[name] = {fn: self[name], bd: JS.bind(self[name], self)}).bd;
  }
});

JS.Class = JS.makeFunction();
JS.extend(JS.Class.prototype = JS.makeBridge(JS.Module), {
  initialize: function(parent, methods) {
    var klass = JS.extend(JS.makeFunction(), this);
    klass.klass = klass.constructor = this.klass;
    if (!JS.isFn(parent)) {
      methods = parent;
      parent = Object;
    }
    klass.inherit(parent);
    klass.include(methods, null, false);
    klass.resolve();
    do {
      parent.inherited && parent.inherited(klass);
    } while (parent = parent.superclass);
    return klass;
  },
  
  inherit: function(klass) {
    this.superclass = klass;
    
    if (this.__eigen__) {
      this.__eigen__().include(klass.__eigen__
          ? klass.__eigen__()
          : new JS.Module(klass.prototype));
      this.__meta__.resolve();
    }
    
    this.subclasses = [];
    (klass.subclasses || []).push(this);
    
    var p = this.prototype = JS.makeBridge(klass);
    p.klass = p.constructor = this;
    
    this.__mod__ = new JS.Module({}, {_resolve: this.prototype});
    this.include(JS.ObjectMethods, null, false);
    
    if (klass !== Object) this.include(klass.__mod__ || new JS.Module(klass.prototype,
        {_resolve: klass.prototype}), null, false);
  },
  
  include: function(module, options, resolve) {
    if (!module) return;
    var mod = this.__mod__, options = options || {};
    options._included = this;
    return mod.include(module, options, resolve !== false);
  },
  
  extend: function(module) {
    if (!this.callSuper) return;
    this.callSuper();
    var i = this.subclasses.length;
    while (i--) this.subclasses[i].extend();
  },
  
  define: function() {
    var module = this.__mod__;
    module.define.apply(module, arguments);
    module.resolve();
  },
  
  includes:   JS.delegate('__mod__', 'includes'),
  ancestors:  JS.delegate('__mod__', 'ancestors'),
  resolve:    JS.delegate('__mod__', 'resolve')
});

JS.Module = JS.extend(new JS.Class(JS.Module.prototype), JS.ObjectMethods.__fns__);
JS.Module.include(JS.ObjectMethods);
JS.Class = JS.extend(new JS.Class(JS.Module, JS.Class.prototype), JS.ObjectMethods.__fns__);
JS.Module.klass = JS.Module.constructor =
JS.Class.klass = JS.Class.constructor = JS.Class;

JS.Module.extend({
  _observers: [],
  methodAdded: function(block, context) {
    this._observers.push([block, context]);
  },
  _notify: function(name, object) {
    var obs = this._observers, i = obs.length;
    while (i--) obs[i][0].call(obs[i][1] || null, name, object);
  }
});

JS.extend(JS, {
  Interface: new JS.Class({
    initialize: function(methods) {
      this.test = function(object, returnName) {
        var n = methods.length;
        while (n--) {
          if (!JS.isFn(object[methods[n]]))
            return returnName ? methods[n] : false;
        }
        return true;
      };
    },
    
    extend: {
      ensure: function() {
        var args = JS.array(arguments), object = args.shift(), face, result;
        while (face = args.shift()) {
          result = face.test(object, true);
          if (result !== true) throw new Error('object does not implement ' + result + '()');
        }
      }
    }
  }),
  
  Singleton: new JS.Class({
    initialize: function(parent, methods) {
      return new (new JS.Class(parent, methods));
    }
  })
});

}


/*
    Class: DLSupport
        JavaScript framework containing common functionalities.

        This is used when we cannot use Prototype.
*/
var DLSupport = {
    /*
        Group: Data

        Object: Browser
            Contains browser information.

            The stucture of this object will look something similar to the
            following:
            (code)
            Object(
                Gecko: {
                    (boolean) Whether or not the current browser is Gecko based
                    like Firefox.
                },
                IE: {
                    (boolean) Whether or not the current browser is Internet
                    Explorer.
                },
                MobileSafari: {
                    (boolean) Whether or not the current browser is Mobile
                    Safari like the iPhone.
                },
                Opera: {
                    (boolean) Whether or not the current browser is Opera.
                },
                WebKit: {
                    (boolean) Whether or not the current browser is WebKit based
                    like Safari and Google Chrome.
                }
            )
            (end)
    */
    Browser: {},

    /*
        Array: ErrorLog
            (array) Contains a log of all the errors during the script
            execution.
    */
    ErrorLog: [],

    /*
        Integer: IdCounter
            (int) Counter used for generating unqiue IDs.
    */
    IdCounter: 1,

    /*
        String: ModuleUrl
            (string) The URL to the directory containing this module. This will
            be set dynamically after the script loads.
    */
    ModuleUrl: '',

    /*
        Group: Methods

        Method: addClassName
            Add the given class name ot the given DOM element.

        Parameters:
            obj - (object) The DOM element that the class name will be added to
                if it does not already have it.
            class_name - (string) The class name to be added.
    */
    addClassName: function(obj, class_name) {
        try {
            if(!DLSupport.hasClassName(obj, class_name)) {
                obj.className += (obj.className ? ' ' : '') + class_name;
            }
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.addClassName', [obj, class_name], e.message);
        }
    },

    /*
        Method: addErrorLog
            Add a message to the error log.

        Parameters:
            method - (string) The name of the method that generated the error.
            parameters - (array) The parameters that was given to the method
                that generated the error.
            message - (string) The error message.
    */
    addErrorLog: function(method, parameters, message) {
        var item = {
            method: method,
            parameters: parameters,
            message: message
        };

        DLSupport.ErrorLog.push(item);
    },

    /*
        Method: checboxSelectAllToggle
            Check or uncheck all input checkboxes with the same class name as
            the given target based on the current status of the given element.

        Parameters:
            element - (object) The DOM element in which if it is checked, all
                input boxes with the given class name, will be checked and vice
                versa.
            target - (string) The class name of all input checkboxes that will
                be checked or unchecked.
    */
    checkboxSelectAllToggle: function(element, target) {
        // Get all input check boxes
        var check_boxes = document.getElementsByTagName('input');

        // Now go through list of input boxes
        var class_name;
        for(var i = 0; i < check_boxes.length; ++i) {
            class_name = check_boxes[i].className;

            // Set element's status if input has given class name
            if(class_name.indexOf(target) >= 0) {
                check_boxes[i].checked = element.checked;
            }
        }
    },

    /*
        Method: childElements
            Retrieves an array of all direct child DOM elements of the given
            element.

        Parameters:
            parent - (object) The DOM element that will serve as the parent.

        Returns:
            (array) An array of DOM elements that is the child of the given DOM
            element.
    */
    childElements: function(parent) {
        try {
            var result = [];

            for(var i = 0; i < parent.childNodes.length; ++i) {
                // Make sure it is a DOM element
                if(parent.childNodes[i].nodeType == 1) {
                    result.push(parent.childNodes[i]);
                }
            }

            return result;
        } catch(e) {
            DLSupport.addErrorLog('DLSupport.childElements', [parent], e.message);

            return [];
        }
    },

    /*
        Method: cloneArray
            Create a copy of the given array.

        Parameters:
            data - (array) The array to be cloned.

        Returns:
            (array) A clone of the given array.
    */
    cloneArray: function(data) {
        var result = [].concat(data);

        return result;
    },

    /*
        Method: combineArray
            Create an associative array (object) by using the first given array
            as the keys and the second given array as the value.

            It is required that both arrays be of the same length.

        Parameters:
            keys - (array) The array to be used as the keys.
            values - (array) The array to be used as the values.

        Returns:
            (object) An object with the given keys as the property names and the
            given values as its corresponding value.
    */
    combineArray: function(keys, values) {
        // Create an empty object
        var result = {};

        // Combine the arrays
        for(var i = 0; (i < keys.length) && (i < values.length); ++i) {
            result[keys[i]] = values[i];
        }

        return result;
    },

    /*
        Method: curry
            Currify the given function.

            Don't know what currify means? Google it.

        Parameters:
            func - (function) The function to currify.
            scope - (object) The scope of the function, namely, the object that
                will serve as the "this".

        Returns:
            (function) A function that wraps the given function with some of its
            parameters preset.
    */
    curry: function(func, scope) {
        // Make sure the scope is given, otherwise, default to window
        scope = scope || window;

        // Get the arguments
        var args = [].slice.call(arguments,2);

        // Create the function
        return function() {
            return func.apply(scope, args.concat([].slice.call(arguments,0)));
        };
    },

    /*
        Method: defaultValues
            Using the given base values object, copy values from the source
            object if they have the same property name.

        Parameters:
            source - (object) The object being used to copy values over from.
            base - (object) The object that contains the default values.

        Returns:
            (object) The object containing values from both the source and base
            object with the source object's value replacing the base object's
            value if they have the same property name.
    */
    defaultValues: function(source, base) {
        // Create an empty object
        var result = {};

        // Get all the property names of base
        var property_names = DLSupport.propertyNames(base);

        // Traverse the property names
        var property;
        for(var i = 0; i < property_names.length; ++i) {
            property = property_names[i];

            // If source has a value, use it, otherwise use base's
            if(source[property]) {
                result[property] = source[property];
            } else {
                result[property] = base[property];
            }
        }

        return result;
    },

    /*
        Method: delayFunction
            Delay the execution of the given function by the given number of
            seconds.

        Parameters:
            func - (function) The function whose execution will be delayed.
            delay - (int) The number of seconds to wait before the given
                function is executed.
    */
    delayFunction: function(func, delay) {
        // No need to delay the function if it is 0 seconds
        if(delay == 0) {
            func.apply(window);
        } else {
            // Convert the given delay to milliseconds
            delay = delay * 1000;

            window.setTimeout(func, delay);
        }
    },

    /*
        Method: getCookie
            Get a value from the cookie.

        Parameters:
            key - (string) The key under which the cookie was stored.

        Returns:
            (mixed) Returns the value of the cookie of the given key or null if
            it does not exist.
    */
    getCookie: function(key) {
        var nameEQ = key+'=';
        var ca = document.cookie.split(';');

        for(var i = 0; i < ca.length; ++i) {
            var c = ca[i];

            while(c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }

            if(c.indexOf(nameEQ) === 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }

        return null;
    },

    /*
        Method: getElement
            If given is a string, this will try to get the object with the same
            ID as the string. Otherwise, this just returns back the given data
            if it is anything else.

        Parameters:
            data - (mixed) A string representing the ID of a DOM element or
                anything else.

        Returns:
            (mixed) The DOM element with the given ID or whatever was given.
    */
    getElement: function(data) {
        if(typeof(data) == 'string') {
            data = document.getElementById(data);
        }

        return data;
    },

    /*
        Method: hasClassName
            Determine whether or not the given DOM element has the given class
            name.

        Parameters:
            obj - (object) The DOM element that will be tested.
            class_name - (string) The class name to look for.

        Returns:
            (boolean) Whether or not the given class name exist in the given DOM
            element.
    */
    hasClassName: function(obj, class_name) {
        try {
            // First, make sure the element actually has any class names
            var result = obj.className.length > 0;

            // If it does but doesn't match the given class
            if(result && (obj.className != class_name)) {
                // Try regular expression since it can have multiple class
                var regex = new RegExp('(^|\\s)'+class_name+'(\\s|$)');

                result = regex.test(obj.className);
            }

            return result;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.hasClassName', [obj, class_name], e.message);

            return false;
        }
    },

    /*
        Method: hideLoadingImage
            Hide the loading image.
    */
    hideLoadingImage: function() {
        var loading_overlay = DLSupport.getElement('DLSupport_loading_overlay');
        var loading_div = DLSupport.getElement('DLSupport_loading_div');

        // If the elements exist, hide it
        if(loading_overlay) {
            loading_overlay.style.display = 'none';
        }
        if(loading_div) {
            loading_div.style.display = 'none';
        }
    },

    /*
        Method: humanReadable
            Retrieves the given data into human readable format. Credits goes to
            Michael White & Ben Bryan.

        Parameters:
            data - (mixed) The data to be parsed and returned.

        Returns:
            (string) The string representation of the given data.
    */
    humanReadable: function(data) {
        var result = '';
        var pad_char = ' ';
        var pad_val = 4;

        var formatArray = function(obj, cur_depth, pad_val, pad_char) {
            if(cur_depth > 0) {
                ++cur_depth;
            }

            var base_pad = repeat_char(pad_val*cur_depth, pad_char);
            var thick_pad = repeat_char(pad_val*(cur_depth+1), pad_char);
            var str = '';

            if((obj instanceof Array) || (obj instanceof Object)) {
                str += "Array\n" + base_pad + "(\n";
                var property_names = DLSupport.propertyNames(obj);
                var key;
                for(var i = 0; i < property_names.length; ++i) {
                    key = property_names[i];

                    if(DLSupport.isArray(obj[key])) {
                        str += thick_pad + "["+key+"] => "+formatArray(obj[key], cur_depth+1, pad_val, pad_char);
                    } else {
                        str += thick_pad + "["+key+"] => " + obj[key] + "\n";
                    }
                }
                str += base_pad + ")\n";
            } else {
                str = obj.toString();
            }

            return str;
        };

        var repeat_char = function(len, pad_char) {
            var str = '';
            for(var i=0; i < len; ++i) {
                str += pad_char;
            };

            return str;
        };

        result = formatArray(data, 0, pad_val, pad_char);

        return result;
    },

    /*
        Method: identify
            Retrieve the ID of the given object, creating one if it does not
            exist.

        Parameters:
            obj - (object) The object to retrieve the ID from.

        Returns:
            (string) The ID of the given object. This will return an empty
            string if the object does not exist.
    */
    identify: function(obj) {
        try {
            // If object doesn't have an ID, create one
            if(obj.id.length < 1) {
                while(DLSupport.getElement('dlsupport_id_'+DLSupport.IdCounter)) {
                    ++DLSupport.IdCounter;
                }

                obj.id = 'dlsupport_id_'+IdCounter;
            }

            return obj.id;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.identify', [obj], e.message);

            return '';
        }
    },

    /*
        Method: inArray
            Find out whether or not the given item is in the given array.

        Parameters:
            item - (mixed) The item to look for.
            haystack - (array) The array to used for the search.
            strict - (boolean) This is optional and defaults to true. If this is
                true, then the search will use === when comparing the data,
                otherwise, it will just use ==.

        Returns:
            (boolean) Returns true if the given item is in the given array,
            false otherwise.
    */
    inArray: function(item, haystack) {
        // Get the strict parameter
        var strict = arguments[2] || true;

        // Traverse the array and look for the item
        var result = false;
        for(var i = 0; (i < haystack.length) && !result; ++i) {
            if(strict) {
                result = item === haystack[i];
            } else {
                result = item == haystack[i];
            }
        }

        return result;
    },

    /*
        Method: isArray
            Determine whether or not the given item is an array.

        Parameters:
            item - (mixed) The item to check.

        Returns:
            (boolean) Returns true if the given item is an array, false
            otherwise.
    */
    isArray: function(item) {
        var result = (item instanceof Array);

        return result;
    },

    /*
        Method: isNumeric
            Determine whether or not the given item is numeric.

        Parameters:
            item - (mixed) The item to check.

        Returns:
            (boolean) Returns true if the given item is a number, false
            otherwise.
    */
    isNumeric: function(item) {
        var result = !isNaN(item);

        return result;
    },

    /*
        Method: newEvent
            Attach a new event to the given element.

        Parameters:
            element - (object) The element in which the event should be attached
                to.
            event - (string) The event by which the given function should be
                executed. This should not include the text "on".
            func - (function) The function to append to the list.
            delay - (int) The time, in seconds, to delay the execution after the
                page has loaded. This is optional & defaults to 0.

        Returns:
            (boolean) Returns true if successful, otherwise, false.
    */
    newEvent: function(element, event, func, delay) {
        // See if we have a delay param
        var delay = arguments[3] || 0;

        // Delay the function execution
        var delayed_func = DLSupport.curry(DLSupport.delayFunction, window, func, delay);

        if(element.addEventListener) {       // W3C compliant browsers
            element.addEventListener(event, delayed_func, false);
        } else if(element.attachEvent) {     // IE
            element.attachEvent('on'+event, delayed_func);
        } else {                            // Give up
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.newEvent', [element, event, func, delay], e.message);

            return false;
        }

        return true;
    },

    /*
        Method: PropertyNames
            Get the property names of the given object.

        Parameters:
            item - (object) The item to retrieve the property names.

        Returns:
            (array) An array containing all the properties belonging to the
            given object. Note that this will only contain properties that the
            given object actually own.
    */
    propertyNames: function(item) {
        // Create an empty array
        var result = new Array();

        for(var key in item) {
            if(item.hasOwnProperty(key)) {
                result.push(key);
            }
        }

        return result;
    },

    /*
        Method: removeClassName
            Remove the given class name from the given DOM element.

        Parameters:
            obj - (object) The DOM element that will have the given class name
                removed.
            class_name - (string) The class name to remove.
    */
    removeClassName: function(obj, class_name) {
        try {
            var regex = new RegExp('(^|\\s+)'+class_name+'(\\s+|$)');

            obj.className = obj.className.replace(regex, ' ');
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.removeClassName', [obj, class_name], e.message);
        }
    },

    /*
        Method: removeCookie
            Wrapper method around the method <setCookie>. This method will set
            the expire time to the current time.

        Parameters:
            key - (string) The key under which the cookie was stored.
    */
    removeCookie: function(key) {
        DLSupport.setCookie(key, '', 0);
    },

    /*
        Method: removeElement
            This will remove the given element from the DOM structure of the web
            site.

            Be careful when using this method because it is a mutation of the
            DOM structure. Removing the given element will also remove all of
            its children.

        Parameters:
            element - (object) The DOM element to remove.

        Returns:
            (boolean) Returns true if successful, false, otherwise.
    */
    removeElement: function(element) {
        try {
            element.parentNode.removeChild(element);

            return true;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.removeElement', [element], e.message);

            return false;
        }
    },

    /*
        Method: reverseArray
            Get the array that has the reverse order of the given array.

            This differs from the pre-defined reverse array function in that
            this can be set to be done recursively.

        Parameters:
            data - (array) The array to have it values reversed.
            recursive - (boolean) Optional & defaults to false. If true, recur
                into an inner arrays.

        Returns:
            (array) The given array but its order in reverse.
    */
    reverseArray: function(data) {
        // Get the recursive parameter
        var recursive = arguments[1] || false;

        // Traverse the array in descending order and add back the items
        var result = new Array();
        for(var i = data.length-1; i >= 0; --i) {
            if(recursive && DLSupport.isArray(data[i])) {
                result.push(DLSupport.reverseArray(data[i]));
            } else {
                result.push(data[i]);
            }
        }

        return result;
    },

    /*
        Method: setCookie
            Store the given value to cookie under the given key for the given
            number of days.

        Parameters:
            key - (string) The key under which the cookie's value is stored.
            value - (string) The value to be stored in the cookie.
            days - (int) The number of days until the cookie expires from the
                current date.
    */
    setCookie: function(key, value, days) {
        // Get the current date
        var date = new Date();

        // Incremenet the date by the given number of days
        date.setTime(date.getTime() + (days*24*60*60*1000));

        // Set the cookie
        document.cookie = key+'='+value+'; expires='+date.toGMTString()+
                          '; domain='+window.location.hostname+'; path=/';
    },

    /*
        Method: showLoadingImage
            Fade out the entire page and show the loading image.

        Parameters:
            color - (string) The color of the loading image. The possible values
                are: "black", "blue", "brown", "gold", "gray", "green", "navy",
                "orange", "purple", "red", "white", and "yellow".
    */
    showLoadingImage: function(color) {
        var loading_overlay = DLSupport.getElement('DLSupport_loading_overlay');
        var loading_div = DLSupport.getElement('DLSupport_loading_div');
        var loading_image = DLSupport.getElement('DLSupport_loading_image');

        // Create the divs if they don't exists
        if(!loading_overlay) {
            loading_overlay = document.createElement('div');
            loading_overlay.id = 'DLSupport_loading_overlay';
            loading_overlay.style.display = 'none';
            loading_overlay.style.position = 'absolute';
            document.body.appendChild(loading_overlay);
        }
        if(!loading_div) {
            loading_div = document.createElement('div');
            loading_div.id = 'DLSupport_loading_div';
            loading_div.style.display = 'none';
            loading_div.style.position = 'absolute';

            var loading_image = document.createElement('img');
            loading_image.id = 'DLSupport_loading_image';
            loading_image.alt = 'Loading...';

            document.body.appendChild(loading_div);
            loading_div.appendChild(loading_image);
        }

        // Set the color
        loading_image.src = DLSupport.ModuleUrl+'/images/loading_images/'+color+'.gif';

        // Get the screen dimension
        var screen_dimension = DLSupport.Viewport.getDimensions();

        // Get page offset
        var offsets = DLSupport.Viewport.getOffsets();

        // Determine position of loading image
        var offset_left = offsets.left + Math.floor((screen_dimension.width/2) - 50);
        var offset_top = offsets.top + Math.floor((screen_dimension.height/2) - 50);

        // Set the loading overlay postion and dimension
        loading_overlay.style.left = offsets.left+'px';
        loading_overlay.style.top = offsets.top+'px';
        loading_overlay.style.height = screen_dimension.height+'px';
        loading_overlay.style.width = screen_dimension.width+'px';

        // Set the loading div position
        loading_div.style.left = offset_left+'px';
        loading_div.style.top = offset_top+'px';

        // Show the divs
        loading_overlay.style.display = '';
        loading_div.style.display = '';
    },

    /*
        Method: toggleDisplay
            Toggle the display of the given DOM element. If it is currently is
            hidden, then show it. If it is currently visibile, then hdie it.

        Parameters:
            element - (object) The DOM element whose display will be toggled.
    */
    toggleDisplay: function(element) {
        if(element.style.display == 'none') {
            // Show the given element
            element.style.display = '';
        } else {
            // Hide the given element
            element.style.display = 'none';
        }
    },

    /*
        Method: toQueryString
            Convert the given object to a query string.

        Parameters:
            data - (object) The object to be converted.

        Returns:
            (string) The given data as a query string.
    */
    toQueryString: function(data) {
        // Get all the property names for the given data
        var property_names = DLSupport.propertyNames(data);

        // Traverse all the property names and create query string
        var result = new Array(), property, extra;
        for(var i = 0, j; i < property_names.length; ++i) {
            property = property_names[i];

            // If the property value is an array, combine them together first
            if(DLSupport.isArray(data[property])) {
                extra = new Array();
                for(j = 0; j < data[property].length; ++j) {
                    extra[j] = encodeURIComponent(property)+'='+encodeURIComponent(data[property][j]);
                }

                result[i] = extra.join('&');
            } else {
                result[i] = encodeURIComponent(property)+'='+encodeURIComponent(data[property]);
            }
        }

        return result.join('&');
    },

    /*
        Method: uniqueArray
            Get the array that has only the unique values of the given array.

        Parameters:
            data - (array) The array to use to create a unique array.
            strict - (boolean) This is optional and defaults to true. If this is
                true, then the search will use === when comparing the data,
                otherwise, it will just use ==.

        Returns:
            (array) Returns the given array with repeated items removed.
    */
    uniqueArray: function(data) {
        // Get the strict parameter
        var strict = arguments[1] || true;

        // Traverse the array and add only unique items
        var result = new Array();
        for(var i = 0; i < data.length; ++i) {
            if(!DLSupport.inArray(data[i], result, strict)) {
                result.push(data[i]);
            }
        }

        return result;
    },

    /*
        Method: windowOnload
            This will add a new event to the event listener that will run the
            given function after the window has finish loading (basically when
            all of the DOM structure has finish loading).

            Since this is adding to the list, you do not have to worry about
            overwriting someone else's events or theirs overwriting yours. The
            second parameter can futher delay the execution so you can pretty
            much sort the order of the execution of the function. It also allows
            you to wait for other javascript functions to finish running before
            yours can go. At worse case, it uses window.setTimeout.

        Parameters:
            func - (function) The function to append to the list.
            delay - (int) The time, in seconds, to delay the execution after the
                page has loaded. This is optional & defaults to 0.

        Returns:
            (boolean) Returns true if it was successful in adding the onload to
                the list. Returns false if it has to default to using
                window.setTimeout.
    */
    windowOnload: function(func) {
        // See if we have a delay param
        var delay = arguments[1] || 0;

        // Try to add the event
        if(DLSupport.newEvent(window, 'load', func, delay)) {
            return true;
        } else {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.windowOnload', [func], 'Failed to add event, using window.setTimeout');

            // Delay the function execution
            var delayed_func = DLSupport.curry(DLSupport.delayFunction, window, func, delay);

            // Delay it even further by 5 seconds so hopefully the page has
            // loaded
            window.setTimeout(delayed_func, 5000);

            return false;
        }
    }
};


/*
    Class: DLSupport.AJAX
        Send and retrieve information without reloading page.

        This is used when we cannot use Prototype.
*/
DLSupport.AJAX = {
    /*
        Method: monitor
            Monitor the states of the AJAX object and call the appropriate
            callback functions.

        Parameters:
            ajax - (object) The AJAX transport.
            error - (function) The function to call if there was an error while
                the request is being made.
            failure - (function) The function to call if the request was a
                failure.
            success - (function) The function to call if the request was a
                success.
    */
    monitor: function(ajax, error, failure, success) {
        // Only continue if the request is loaded (readyState is 4)
        if(ajax.readyState == 4) {
            // Make sure it was successful
            try {
                if(ajax.status == 200) {
                    // It was a success, call the success callback function
                    success.apply(this, [ajax]);
                } else {
                    // It was not a success but is not an error, call the
                    // failure callback function
                    failure.apply(this, [ajax]);
                }
            } catch(e) {
                // Add error message to error log
                DLSupport.addErrorLog('DLSupport.AJAX.monitor', [ajax, error, failure, success], e.message);

                // Call the error callback function
                error.apply(this, [ajax, e.message]);
            }
        }
    },

    /*
        Method: request
            Make a request to the given url using the given method and
            parameters.

        Parameters:
            url - (string) The URL of the page to request.
            options - (object) An object containing various options used by this
                method. Defaults to an empty object

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object(
                method: {
                    (string) The method to use to make the request. This should
                    be either "GET" or "POST". Defaults to "POST".
                },
                onError: {
                    (function) The callback function to call if at any time an
                    error occurred in the request. Defaults to a function that
                    alerts the user that an error has occurred.
                },
                onFailure: {
                    (function) The callback function for a request that has
                    failed. Defaults to an empty function.
                },
                onSuccess: {
                    (function) The callback function for a successful request.
                    Defaults to an empty function.
                },
                parameters: {
                    (object) An object containing any information to be sent to
                    the requested URL. Defaults to an empty object.
                },
                query_string: {
                    (string) If given, this will be used instead of the
                    "parameters" option. This should be a properly formed query
                    string to be sent to the server. Defaults to an empty
                    string.
                }
            )
            (end)

        Returns:
            (mixed) The AJAX transport. This can be eitehr XMLHttpRequest or
            ActiveXObject, depending on the browser.
    */
    request: function(url) {
        var options = DLSupport.defaultValues(arguments[1] || {}, {
            method: 'POST',
            parameters: {},
            query_string: '',
            onError: function(ajax, message) { window.alert(message); },
            onFailure: function() {},
            onSuccess: function() {}
        });

        // Convert the paramters to a query string
        var parameters = (options.query_string.length > 0) ?
                         options.query_string :
                         DLSupport.toQueryString(options.parameters);

        // Create the AJAX object
        var ajax;
        if(window.XMLHttpRequest) { // Mozilla, Safari, ...
            ajax = new XMLHttpRequest();
        } else if(window.ActiveXObject) { // IE
            try {
                ajax = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                try {
                    ajax = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (e) { // If we made it here, we have to just give up
                    // Add error message to error log
                    DLSupport.addErrorLog('DLSupport.AJAX.request', [url], e.message);

                    // Call the error callback function
                    options.onError.apply(this, [ajax, 'Failed to create AJAX object: '+e.message]);
                    return;
                }
            }
        }

        // Monitor the changes in the AJAX state
        ajax.onreadystatechange = DLSupport.curry(
            DLSupport.AJAX.monitor, this, ajax,
            options.onError, options.onFailure, options.onSuccess
        );

        // If method is "GET", append params to the url
        if(options.method.toUpperCase() == 'GET') {
            url = ((url.indexOf('?') == -1) ? '?' : '&')+parameters;
            parameters = '';
        }

        // Open the connection
        ajax.open(options.method.toUpperCase(), url, true);

        // If method is "POST", set the appropriate request header
        if(options.method.toUpperCase() == 'POST') {
            ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        }

        // Send the request
        ajax.send(parameters);

        return ajax;
    }
};


DLSupport.Browser = {
    Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
    IE: !!(window.attachEvent && !window.opera),
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/),
    Opera: !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1
};


/*
    Class: DLSupport.Form
        Handle form submissions.

        This is used when we cannot use Prototype.
*/
DLSupport.Form = {
    /*
        Method: addParameter
            Add the given element into the given parameters object based on some
            pre-set rules.

        Parameters:
            parameters - (object) The parameter object.
            element - (object) The element to try to add to the parameters
                object.
            is_array - (boolean) Whether or not the value should be considered
                as an array.
    */
    addParameter: function(parameters, element, is_array) {
        // If the element is a checkbox or radio and it is checked, no need to
        // do anything
        if((element.type == 'checkbox') || (element.type == 'radio')) {
            if(!element.checked) {
                return;
            }
        }

        // If the element is a select with multiple value, or the value is
        // expected to be an array, we do things differently
        if(element.type == 'select-multiple') {
            parameters[element.name] = new Array();

            for(var i = 0; i < element.options.length; ++i) {
                if(element.options[i].selected) {
                    parameters[element.name].push(element.options[i].value);
                }
            }
        } else if(is_array) {
            parameters[element.name].push(element.value);
        } else {
            parameters[element.name] = element.value;
        }
    },

    /*
        Method: disableSubmitFields
            Disable all of the submit fields of the given form.

        Parameters:
            form - (object) The form for which all of its submit fields will be
                disabled.
    */
    disableSubmitFields: function(form) {
        // Traverse all of the form elements and disable the submit fields
        var element;
        for(var i = 0; i < form.elements.length; ++i) {
            element = form.elements[i];

            if(element.type == 'submit') {
                element.disabled = true;
            }
        }
    },

    /*
        Method: enableSubmitFields
            Enable all of the submit fields for the given form.

        Parameters:
            form - (object) The form for which all of its submit fields will be
                enabled.
    */
    enableSubmitFields: function(form) {
        // Traverse all of the form elements and enable the submit fields
        var element;
        for(var i = 0; i < form.elements.length; ++i) {
            element = form.elements[i];

            if(element.type == 'submit') {
                element.disabled = false;
            }
        }
    },

    /*
        Method: fieldCount
            Retrieve an object with its property names being all the named
            fields of the given form and its value being how many fields have
            that name.

        Parameters:
            form - (object) The form for which the data is to be retrieved from.

        Returns:
            (object) An associative array with the field name as the property
            and the number of fields with the same name as the value.
    */
    fieldCount: function(form) {
        // For each element with a name, add their count to result
        var result = {}, element;
        for(var i = 0; i < form.elements.length; ++i) {
            element = form.elements[i];

            // Make sure element has a name
            if(element.name.length > 0) {
                // If element has not been added before, initialize it
                if(!result[element.name]) {
                    result[element.name] = 0;
                }

                ++result[element.name];
            }
        }

        return result;
    },

    /*
        Method: submitValue
            Set the value of the given element as the submit value for the
            element's form. This is useful for forms to be sent via AJAX that
            has multiple submit buttons.

        Parameters:
            element - (object) The element that contains the submit value. Note
                that the element must be a DOM element with the property "form"
                which points to the form for which the element belongs to.

        Returns:
            (boolean) Returns true if successful, false, otherwise.
    */
    submitValue: function(element) {
        try {
            element.form.DLSupport_SubmitValue = element.value;

            return true;
        } catch(e) {
            // Add error message to error log
            DLSupport.addErrorLog('DLSupport.Form.submitValue', [element], e.message);

            return false;
        }
    },

    /*
        Method: toQueryString
            Convert the given form data to a query string. Although this will
            handle fields that can have multiple values like checkboxes, make
            sure you name it appropriately however.

        Parameters:
            form - (object) The form for which the data is to be retrieved from.

        Returns:
            (string) The given form data as a query string.
    */
    toQueryString: function(form) {
        // Get the field's count for the given form
        var field_count = DLSupport.Form.fieldCount(form);

        // Prepare the object with the form data
        var parameters = {}, gotSubmit = false, element;
        for(var i = 0; i < form.elements.length; ++i) {
            element = form.elements[i];

            // Only continue if the element has a name
            if(element.name.length < 1) {
                continue;
            }

            // If an element has more than 1 field count, its value should be
            // store as an array unless it is a submit type
            if((field_count[element.name] > 1)) {
                // There can only be one submit button, so handle this case
                if((element.type == 'submit') || (element.type == 'image')) {
                    // If the submit value stored in the form equals the given
                    // element's value, then it is the value we want to send
                    if(form.DLSupport_SubmitValue == element.value) {
                        gotSubmit = true;
                        DLSupport.Form.addParameter(parameters, element, false);
                    } else if(!gotSubmit) {
                        // Otherwise, just add current one as later ones will
                        // overwrite this value
                        DLSupport.Form.addParameter(parameters, element, false);
                    }

                    continue;
                }

                // Create the array if needed
                if(!DLSupport.isArray(parameters[element.name])) {
                    parameters[element.name] = new Array();
                }

                DLSupport.Form.addParameter(parameters, element, true);
            } else {
                DLSupport.Form.addParameter(parameters, element, false);
            }
        }

        // Convert the parameters object to a query string
        var result = DLSupport.toQueryString(parameters);

        return result;
    }
};


/*
    Class: DLSupport.Viewport
        Contains information regarding the current viewport.

        This is used when we cannot use Prototype.
*/
DLSupport.Viewport = {
    /*
        Method: getDimensions
            Retrieve the size of the viewport.

        Returns:
            (object) An object with the following properties: height & width.
    */
    getDimensions: function() {
        var result = {};
        if(DLSupport.Browser.WebKit && !document.evaluate) {
            result.height = self.innerHeight;
            result.width = self.innerWidth;
        } else if(DLSupport.Browser.Opera) {
            result.height = document.body.clientHeight;
            result.width = document.body.clientWidth;
        } else {
            result.height = document.documentElement.clientHeight;
            result.width = document.documentElement.clientWidth;
        }

        return result;
    },

    /*
        Method: getOffsets
            Retrieve the offsets of the viewport from the top left corner.

        Returns:
            (object) An object with the following properties: left & top.
    */
    getOffsets: function() {
        return {
            left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
            top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        };
    }
};


