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: DLSupportJS
        JavaScript framework using Prototype (<http://www.prototypejs.org>),
        Scriptaculous (<http://script.aculo.us>), and JS.Class
        (<http://jsclass.jcoglan.com>).

    About: Author
        Duc Tri Le <cmsmadesimple---at---email.tiger-inc.com>

    About: License
        This class is released under the GNU General Public License.
*/
var DLSupportJS = {
    /*
        Object: UploadFileOptions
            (object) An object containing a uploaded file options. Mainly used
            to hide the loading image once the file has been uploaded.
    */
    UploadFileOptions: {},

    /*
        Method: HideLoadingImage
            Hide the loading image if it is currently visible.

        Parameters:
            options - (object) An object containing various options used in the
                execution of this method. Refer to the method <HideOverlay> for
                more information on some of these options. Defaults to an empty
                object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                finish: {
                    (function) The function that should be run after the overlay
                    is hidden. Defaults to an empty function.
                },
                keep_overlay: {
                    (boolean) Whether or not the overlay div should be kept
                    visible. Defaults to false.
                },
                remove: {
                    (boolean) Whether or not the overlay div should be removed
                    from the DOM structure as well. Defaults to false.
                },

                duration: {
                    (int) Refer to the "duration" attribute of the parameter
                    "options" from the method HideOverlay.
                },
                remove_overlay: {
                    (boolean) Refer to the "remove" attribute of the parameter
                    "options" from the method HideOverlay.
                }
            )
            (end)

        Returns:
            (boolean) Returns true if successful, false, otherwise.
    */
    HideLoadingImage: function() {
        // Get the options
        var options = Object.extend({
            // Options specifically for this method
            finish:         Prototype.emptyFunction,
            keep_overlay:   false,
            remove:         false,

            // Options for HideOverlay
            duration:       1,
            remove_overlay: false
        }, arguments[0] || {});

        // Get the loading image
        var loading_image = $('DLSupportJS_LoadingImage');

        // Make sure the loading image exists
        if(!loading_image) {
            return false;
        }

        // We need to create a wrapper function if removal is wanted
        if(options.remove) {
            options.finish = function(loading_image, funct) {
                // Remove the loading image
                loading_image.remove();

                // Run the given function
                funct.apply(this);
            }.curry(loading_image, options.finish);
        }

        // Hide the loading image
        loading_image.hide();

        // Hide the overlay if we want to
        if(options.keep_overlay) {
            options.finish.apply(this);
        } else {
            DLSupportJS.HideOverlay({
                duration:   options.duration,
                finish:     options.finish,
                remove:     options.remove_overlay
            });
        }
    },

    /*
        Method: HideOverlay
            Hide the overlay div if it is currently visible.

        Parameters:
            options - (object) An object containing various options used in the
                execution of this method. Defaults to an empty object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                duration: {
                    (int) The time, in seconds, of how long the effect should
                    last. Defaults to 1.
                },
                finish: {
                    (function) The function that should be run after the overlay
                    is hidden. Defaults to an empty function.
                },
                remove: {
                    (boolean) Whether or not the overlay div should be removed
                    from the DOM structure as well. Defaults to false.
                }
            )
            (end)

        Returns:
            (boolean) Returns true if successful, false, otherwise.
    */
    HideOverlay: function() {
        // Get the options
        var options = Object.extend({
            duration:   1,
            finish:     Prototype.emptyFunction,
            remove:     false
        }, arguments[0] || {});

        // Get the overlay div
        var overlay_div = $('DLSupportJS_OverlayDiv');

        // Make sure the overlay div exists
        if(!overlay_div) {
            return false;
        }

        // Set flag that overlay is hidden
        overlay_div.is_hidden = true;

        // We need to create the wrapper function if removal is wanted
        if(options.remove) {
            options.finish = function(overlay_div, funct) {
                // Remove the overlay div
                overlay_div.remove();

                // Run the given function
                funct.apply(this);
            }.curry(overlay_div, options.finish);
        }

        // Hide the overlay
        Effect.Fade(overlay_div, {
            duration:       options.duration,
            from:           overlay_div.getStyle('opacity'),
            afterFinish:    options.finish
        });
    },

    /*
        Method: PageLoading
            Show the loading image when this method is called and hide it when
            the body "onload" event is triggered.

        Parameters:
            options - (object) An object containing various options used in the
                execution of this method. Refer to the methods
                <HideLoadingImage>, <HideOverlay>, <ShowOverlay>, and
                <ShowLoadingImage> for more information on some of these
                attributes. Defaults to an empty object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                wait_time: {
                    (int) The time, in seconds, to wait before the loading image
                    is hidden. Defaults to 0.
                },

                color: {
                    (string) Refer to the "color" attribute of the parameter
                    "options" from the method ShowLoadingImage.
                },
                loading_finish: {
                    (function) Refer to the "finish" attribute of the parameter
                    "options" from the method ShowLoadingImage.
                },
                opacity: {
                    (int) Refer to the "opacity" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
                overlay_color: {
                    (string) Refer to the "color" attribute of the parameter
                    "options" from the method ShowOverlay.
                },

                duration: {
                    (int) Refer to the "duration" attribute of the parameter
                    "options" from the method HideOverlay.
                },
                hiding_finish: {
                    (function) Refer to the "finish" attribute of the parameter
                    "options" from the method HideLoadingImage.
                },
                remove: {
                    (boolean) Refer to the "remove" attribute of the parameter
                    "options" from the method HideLoadingImage.
                }
                remove_overlay: {
                    (boolean) Refer to the "remove" attribute of the parameter
                    "options" from the method HideOverlay.
                }
            )
            (end)

        Returns:
            (boolean) This method will always return true.
    */
    PageLoading: function() {
        // Get the options
        var options = Object.extend({
            // Options specifically for this method
            wait_time: 0,

            // Options for ShowLoadingImage
            color:          "red",
            loading_finish: Prototype.emptyFunction,
            opacity:        75,
            overlay_color:  '#FFFFFF',

            // Options for HideLoadingImage
            duration:       1,
            hiding_finish:  Prototype.emptyFunction,
            remove:         false,
            remove_overlay: false
        }, arguments[0] || {});

        // Show the loading image
        DLSupportJS.ShowLoadingImage({
            duration:       0,

            color:          options.color,
            finish:         options.loading_finish,
            opacity:        options.opacity,
            overlay_color:  options.overlay_color
        });

        // Set an event for hiding the overlay once the page finish loading
        Event.observe(window, 'load', function(options) {
            DLSupportJS.HideLoadingImage.curry({
                duration:       options.duration,
                finish:         options.hiding_finish,
                remove:         options.remove,
                remove_overlay: options.remove_overlay
            }).delay(options.wait_time);
        }.curry(options));
    },

    /*
        Method: ShowLoadingImage
            Show the loading image.

        Parameters:
            options - (object) An object containing various options used in the
                execution of this method. Refer to the method <ShowOverlay> for
                more information on some of the attributes. Defaults to an empty
                object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                color: {
                    (string) The color of the loading image. The following are
                    the supported values: "black", "blue", "brown", "gold",
                    "gray", "green", "navy", "orange", "purple", "red", "white",
                    and "yellow". Defaults to "red".
                },
                finish: {
                    (function) The function that should be run after the loading
                    image is showned. Defaults to an empty function.
                },

                duration: {
                    (int) Refer to the "duration" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
                opacity: {
                    (int) Refer to the "opacity" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
                overlay_color: {
                    (string) Refer to the "color" attribute of the parameter
                    "options" from the method ShowOverlay.
                }
            )
            (end)

        Returns:
            (boolean) The method always returns true.
    */
    ShowLoadingImage: function() {
        // Get the options
        var options = Object.extend({
            // Options specifically for this method
            color:  'red',
            finish: Prototype.emptyFunction,

            // Options for ShowOverlay
            duration:       1,
            opacity:        75,
            overlay_color:  '#FFFFFF'
        }, arguments[0] || {});

        // Create the image if it doesn't already exists
        var loading_image = $('DLSupportJS_LoadingImage');
        if(!loading_image) {
            loading_image = new Element('img', {id: 'DLSupportJS_LoadingImage'});
            loading_image.hide();
            document.body.appendChild(loading_image);
        }

        // Set the image src
        loading_image.writeAttribute(
            'src',
            '/modules/DLSupport/images/loading_images/'+options.color+'.gif'
        );

        // Wrap the given finish function
        options.finish = DLSupportJS._ShowLoadingImage.curry(options.finish);

        // Show the overlay and then show the loading image
        DLSupportJS.ShowOverlay({
            color:      options.overlay_color,
            duration:   options.duration,
            finish:     options.finish,
            opacity:    options.opacity
        });

        return true;
    },
    _ShowLoadingImage: function(funct) {
        // Get the loading image
        var loading_image = $('DLSupportJS_LoadingImage');

        // Get the dimensions of the overlay div
        var overlay_dimensions = $('DLSupportJS_OverlayDiv').getDimensions();

        // Set the position for the loading image
        loading_image.setStyle({
            left:   (overlay_dimensions.width/2 - 50)+'px',
            top:    (overlay_dimensions.height/2 - 50)+'px'
        });

        // Show the loading image
        loading_image.show();

        // Run the given function
        funct.apply(this);
    },

    /*
        Method: ShowOverlay
            Create and show the overlay div if it is currently hidden.

        Parameters:
            options - (object) An object containing various options used in the
                execution of this method. Defaults to an empty object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                color: {
                    (string) The background color of the overlay. Defaults to
                    "#FFFFFF".
                },
                duration: {
                    (int) The time, in seconds, of how long the effect should
                    last. Defaults to 1.
                },
                finish: {
                    (function) The function that should be run after the overlay
                    is showned. Defaults to an empty function.
                },
                opacity: {
                    (int) The opacity percentage for the overlay. This value
                    should be between 0 & 100. Defaults to 75.
                }
            )
            (end)

        Returns:
            (boolean) The method will always return true.
    */
    ShowOverlay: function() {
        // Get the options
        var options = Object.extend({
            color:      '#FFFFFF',
            duration:   1,
            finish:     Prototype.emptyFunction,
            opacity:    75
        }, arguments[0] || {});

        // Create the overlay div if it doesn't already exist
        var overlay_div = $('DLSupportJS_OverlayDiv');
        if(!overlay_div) {
            overlay_div = new Element('div', {id: 'DLSupportJS_OverlayDiv'});
            overlay_div.is_hidden = true;
            overlay_div.hide();
            document.body.appendChild(overlay_div);
        }

        // We use different effects if div is already visible
        if(overlay_div.is_hidden) {
            // Set the background color
            overlay_div.setStyle({backgroundColor: options.color});

            Effect.Appear(overlay_div, {
                duration:       options.duration,
                to:             options.opacity/100,
                afterFinish:    options.finish
            });
        } else {
            // Set the duration to 0 if the colors are the same
            if((overlay_div.current_bg_color == options.color) &&
               (overlay_div.current_opacity == options.opacity)) {
                options.duration = 0;
            }

            // Change the overlay color
            new Effect.Morph(overlay_div, {
                style: {
                    backgroundColor:    options.color,
                    opacity:            ''+(options.opacity/100)
                },
                duration:       options.duration,
                afterFinish:    options.finish
            });
        }

        // Set flag some flags for the overlay div
        overlay_div.is_hidden = false;
        overlay_div.current_bg_color = options.color;
        overlay_div.current_opacity = options.opacity;

        return true;
    },

    /*
        Method: UploadComplete
            This method is called after an upload has been completed.

        Parameters:
            data - (string) JSON data sent by server.
    */
    UploadComplete: function(data) {
        // Evaluate the JSON
        data = data.evalJSON();

        // Call user function
        DLSupportJS.UploadFileOptions.finish.apply(window, [data]);

        // Hide the loading image if we want to
        if(DLSupportJS.UploadFileOptions.hide_loading_image) {
            DLSupportJS.HideLoadingImage({
                duration:       DLSupportJS.UploadFileOptions.hiding_duration,
                keep_overlay:   DLSupportJS.UploadFileOptions.keep_overlay,
                remove:         DLSupportJS.UploadFileOptions.remove,
                remove_overlay: DLSupportJS.UploadFileOptions.remove_overlay
            });
        }
    },

    /*
        Method: UploadFile
            Submit the provided form whose sole purpose is to upload files.

        Parameters:
            form - (mixed) Either the ID of the form or the DOM element for the
                form itself that will be uploading the file.
            options - (object) An object containing various options used by this
                method. Refer to the method <HideLoadingImage>, <HideOverlay>,
                <ShowLoadingImage>, and <ShowOverlay> for more information on
                some of the attributes. Defaults to an empty object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object (
                finish: {
                    (function) The function to run once the upload is complete.
                    Defaults to an empty function.
                },
                hide_loading_image: {
                    (boolean) Whether or not the function HideLoadingImage
                    should be called. Defaults to true.
                }

                keep_overlay: {
                    (boolean) Refer to the "keep_overlay" attribute of the
                    parameter "options" from the method HideLoadingImage.
                },
                remove: {
                    (boolean) Refer to the "remove" attribute of the parameter
                    "options" from the method HideLoadingImage.
                },

                hiding_duration: {
                    (int) Refer to the "duration" attribute of the parameter
                    "options" from the method HideOverlay.
                },
                remove_overlay: {
                    (boolean) Refer to the "remove" attribute of the parameter
                    "options" from the method HideOverlay.
                },

                color: {
                    (string) Refer to the "color" attribute of the parameter
                    "options" from the method ShowLoadingImage.
                },

                loading_duration: {
                    (int) Refer to the "duration" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
                opacity: {
                    (int) Refer to the "opacity" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
                overlay_color: {
                    (string) Refer to the "color" attribute of the parameter
                    "options" from the method ShowOverlay.
                },
            )
            (end)

        Returns:
            (boolean) This method will always return true.
    */
    UploadFile: function(form) {
        // Get the options
        var options = Object.extend({
            // Attributes specifically for this method
            finish:             Prototype.emptyFunction,
            hide_loading_image: true,

            // Attributes for HideLoadingImage
            hiding_duration:    1,
            keep_overlay:       false,
            remove:             false,
            remove_overlay:     false,

            // Attributes for ShowLoadingImage
            color:              'red',
            loading_duration:   1,
            opacity:            75,
            overlay_color:      '#FFFFFF'
        }, arguments[1] || {});

        // Make sure it is an object
        form = $(form);

        // Create the iframe if needed
        var iframe = $('DLSupportJS_UploadIFrame');
        if(!iframe) {
            iframe = new Element('iframe', {
                id:     'DLSupportJS_UploadIFrame',
                name:   'DLSupportJS_UploadIFrame'
            });
            iframe.hide();

            document.body.appendChild(iframe);
        }

        // Set the target of the form to the iframe
        form.target = 'DLSupportJS_UploadIFrame';

        // Store the upload options
        DLSupportJS.UploadFileOptions = options;

        // Show the loading image
        DLSupportJS.ShowLoadingImage({
            color:          options.color,
            duration:       options.loading_duration,
            opacity:        options.opacity,
            overlay_color:  options.overlay_color
        });

        return true;
    }
};


/*
    Class: DLSupportJS.CleanAdmin
        Sub class of <DLSupportJS> providing support for the clean admin.
*/
DLSupportJS.CleanAdmin = {
    /*
        Method: AlertError
            Alert an error message.

        Parameters:
            type - (string) The type of error to alert. Possible values are type
                are: *freeform* ~ The message to be alerted will be whatever is
                the value of the _message_ attribute of the parameter *options*.
                *invalid_data* ~ The alerted message will let the user know that
                the response from the server results in an invalid data.
                *openlink* ~ The alerted message will let the user know that the
                requested page could not be reached. *submitform* ~ The alerted
                message will let the user know the form could not be submitted.
            options - (object) An object containing various options used in the
                execution of this method. Defaults to an empty object.

            The structure of the *options* parameter, if given, should look like
            the following:
            (code)
            Object(
                message: {
                    (string) The message to be alerted to the user. Defaults to
                    an empty string.
                }
            )
            (end)

        Returns:
            (boolean) This method will always return true.
    */
    AlertError: function(type) {
        // Get the options
        var options = Object.extend({
            hide_loading:   false,
        	message:        ''
        }, arguments[1] || {});

        // Process appropriately depending on the type
        var message;
        switch(type) {
            case 'freeform':
                message = options.message;
                break;
            case 'invalid_data':
                message = 'The response from the server could not be processed.'
                        + ' Please contact site administrator for help.';
                break;
            case 'submitform':
                message = 'The form could not be submitted. Please try again.';
                break;
        }

        // Get the alert message box
        var container = $('AlertMessageData');

        // Update the error message
        container.update(message);

        // Make sure it is ready for alerting an error message
        container.addClassName('error_message');

        // Show the alert message box
        $('AlertMessage').show();
        
        // Hide the loading image if necessary
        if(options.hide_loading) {
        	DLSupportJS.HideLoadingImage();
        }

        return true;
    },

    /*
        Method: Initialize
            Initialize the script.
    */
    Initialize: function() {
        Event.observe(window, 'load', DLSupportJS.CleanAdmin._Initialize);
    },
    _Initialize: function() {
        // First update links for the menu
        var links = $$('.CleanAdminPages a');

        for(var i = 0; i < links.length; ++i) {
            if(links[i].href != 'javascript:void(0);') {
                Event.observe(
                    links[i], 'click',
                    DLSupportJS.CleanAdmin.OpenPage.curry(links[i].href, true)
                );

                links[i].writeAttribute('TheHREF', links[i].href);
                links[i].href = 'javascript:void(0);';
            }
        }

        // Now do it for other links that needs to be changed
        links = $$('.ChangeToCleanAdminLink a');

        for(var i = 0; i < links.length; ++i) {
            if(links[i].href != 'javascript:void(0);') {
                Event.observe(
                    links[i], 'click',
                    DLSupportJS.CleanAdmin.OpenPage.curry(links[i].href, false)
                );

                links[i].writeAttribute('TheHREF', links[i].href);
                links[i].href = 'javascript:void(0);';
            }
        }
    },

    /*
        Method: OpenPage
            Open up a new page.

        Parameters:
            href - (string) The URL of the page to open.
            is_menu - (boolean) Whether or not the link is a menu link
    */
    OpenPage: function(href, is_menu) {
        if(is_menu) {
            // Get all the pages links
            var pages = $$('.CleanAdminPages a');

            // Set to the "selected" page
            for(var i = 0; i < pages.length; ++i) {
                if(pages[i].readAttribute('TheHREF') == href) {
                    pages[i].addClassName('selected');
                } else {
                    pages[i].removeClassName('selected');
                }
            }
        }

        // Hide the alert error box
        $('AlertMessage').hide();

        // Show the loading image
        DLSupportJS.ShowLoadingImage({
            finish: function(href) {
                // Open the page
                new Ajax.Request(href, {
                    onSuccess: DLSupportJS.CleanAdmin.ServerResponse,
                    onFailure: DLSupportJS.CleanAdmin._ServerResponse.curry({
                    	action: 'alert_error',
                    	message: 'The request could not be completed for some reason, please contact the webmaster.',
                    	hide_loading: true
                    })
                });
            }.curry(href)
        });
    },

    /*
        Method: ServerResponse
            Process the server response.

        Parameters:
            transport - (object) The AJAX transport.
    */
    ServerResponse: function(transport) {
        // Process the json
        try {
            var data = transport.responseText.evalJSON();

            DLSupportJS.CleanAdmin._ServerResponse(data);
        } catch(e) {
            DLSupportJS.CleanAdmin.AlertError('invalid_data', {hide_loading: true});
        }
    },
    _ServerResponse: function(data) {
        // Process appropriately depending on the action
        switch(data.action) {
            case 'alert_error':
                DLSupportJS.CleanAdmin.AlertError(
                    'freeform', {message: data.message}
                );
                break;
            case 'function':
                var funct = eval(data.funct);
                funct.apply(window, [data.parameters]);
                break;
            case 'new_page':
                DLSupportJS.CleanAdmin.UpdatePage(data.content);
                DLSupportJS.CleanAdmin._Initialize();
                break;
        }

        // Hide the loading image
        DLSupportJS.HideLoadingImage();
    },

    /*
        Method: SetAction
            Set the action for the form of the given button.

        Parameters:
            button - (mixed) Either the ID of the button or the DOM element for
                the button itself.

        Returns:
            (boolean) This method will always return true.
    */
    SetAction: function(button) {
        // Make sure it is an object
        button = $(button);

        // Get the form
        var form = button.form;

        // Set the action to the value of the button
        form.writeAttribute('TheAction', button.value);

        return true;
    },

    /*
        Method: SubmitForm
            Submit a form.

        Parameters:
            form - (object) The form object.

        Returns:
            (boolean) This method will always return false.
    */
    SubmitForm: function(form) {
        form = $(form);

        // Hide the alert error box
        $('AlertMessage').hide();

        // Show the loading image
        DLSupportJS.ShowLoadingImage({
            finish: function(form) {
                // Get the parameters
                var parameters = form.serialize(true);

                // Set the action if the form has it
                if(form.readAttribute('TheAction')) {
                    parameters.the_action = form.readAttribute('TheAction');
                }

                // Make the request
                new Ajax.Request(form.action, {
                    parameters: parameters,
                    onSuccess: DLSupportJS.CleanAdmin.ServerResponse
                });
            }.curry(form)
        });

        return false;
    },

    /*
        Method: UpdatePage
            Update a page.

        Parameters:
            content - (string) The HTML of the page.
    */
    UpdatePage: function(content) {
        // Get the page object
        var page = $('Data');

        // Update the content of the page surrounded by a span
        page.update('<span>'+content+'</span>');
    }
};


/*
    Class: DLSupportJS.Tab
        Sub class of <DLSupportJS> providing support for the tabs.
*/
DLSupportJS.Tab = new JS.Class({
    /*
        Array: Contents
            (array) An array of contents.
    */
    Contents: [],

    /*
        Object: ContentWrapper
            (object) The wrapper element for the contents.
    */
    ContentWrapper: null,

    /*
        Object: CurrentlyOpenTab
            (object) The currently opened tab.
    */
    CurrentlyOpenTab: null,

    /*
        Array: Tabs
            (array) An array of tabs.
    */
    Tabs: [],

    /*
        Object: TabWrapper
            (object) The wrapper element for the tabs.
    */
    TabWrapper: null,

    /*
        Method: initialize
            Initialize the script.

        Parameters:
            tab - (string) The ID or the object that should be the currently
                opened tab. Note that this should not include the wrapper ID.
            tabs - (mixed) The ID or the object that is the wrapper for the
                tabs.
            contents - (mixed) The ID or the object that is the wrapper for the
                contents.
    */
    initialize: function(tab, tabs, contents) {
        tabs = $(tabs);
        contents = $(contents);

        // Make sure given objects exist
        if(!(tabs && contents)) { return; }

        // Set the tabs wrapper data
        tabs.identify();
        this.TabWrapper = tabs;
        this.Tabs = tabs.childElements();

        // Set the contents wrapper data
        contents.identify();
        this.ContentWrapper = contents;
        this.Contents = contents.childElements();

        // Add events to the current tabs
        for(var i = 0; i < this.Tabs.length; ++i) {
            this.addEvents(this.Tabs[i]);
        }

        // Show the selected table
        tab = $(this.getId(tab));
        this.showTab(tab);
    },

    /*
        Method: addEvents
            Add events to the given tab.

        Parameters:
            tab - (object) The tab that will observe various events.
    */
    addEvents: function(tab) {
        Event.observe(tab, 'click', function(tab) {
            this.showTab(tab);
        }.bind(this, tab));

        Event.observe(tab, 'mouseover', function(tab) {
            tab.addClassName('hover');
        }.bind(this, tab));

        Event.observe(tab, 'mouseout', function(tab) {
            tab.removeClassName('hover');
        }.bind(this, tab));
    },

    /*
        Method: addTab
            Add in a new tab with the given ID. If the ID already exist, this
            will just show that tab.

        Parameters:
            id - (string) The ID of the tab.
            name - (string) The title of the tab.
            data - (string) The data for the content of the tab.
            options - (object) An object containing various options used by this
                method. Defaults to an empty object.

        options Attributes:
            link - (boolean) If this is true, then the given "data" should be
                the URL for which the content should be retrieved. Defaults to
                false.
            once - (boolean) If this is true and if *link* is true, then the
                content will only be fetched one time. Defaults to false.
    */
    addTab: function(id, name, data) {
        // Get the options
        var options = Object.extend({
            link:   false,
            once:   false
        }, arguments[3] || {});

        // Get the full ID
        var tabID = this.getId(id);
        var contentID = this.getId(id, 'content');

        // Make sure both the tab ID and content ID doesn't exist
        var tab = $(tabID);
        var content = $(contentID);
        if(tab && content) {
            // Since it already exist, just show the tab
            this.showTab(tab); return;
        } else if(tab || content) {
            // Since only one exist, we cannot add
            return;
        }

        // Create the tab
        var tabLi = new Element('li', {id: tabID});
        tabLi.update(name);

        // Set link information
        if(options.link) {
            tabLi.writeAttribute({
                TabURL:     data,
                TabLoaded:  'false',
                TabOnce:    options.once ? 'true' : 'false'
            });
            data = '&nbsp;';
        }

        // Create the content
        var contentDiv = new Element('div', {id: contentID});
        contentDiv.update(data);

        // Append the new tab and its content
        this.TabWrapper.insert(tabLi);
        this.ContentWrapper.insert(contentDiv);

        // Update stored data
        this.Tabs = this.TabWrapper.childElements();
        this.Contents = this.ContentWrapper.childElements();

        // Add events for the tab
        this.addEvents(tabLi);

        // Show the tab
        this.showTab(tabLi);
    },

    /*
        Method: getId
            Get the true ID of the given data.

        Parameters:
            data - (mixed) The current ID or the object with the ID.
            type - (string) The type of ID to get. Possible values are "tab" and
                "content". This is optional and defaults to "tab".

        Returns:
            (string) The ID.
    */
    getId: function(data) {
        var type = arguments[1] || 'tab';

        // If it is an object with the id property, use other, otherwise,
        // just use the given data
        var result = data.id || data;

        // First, remove the wrapper portion
        result = result.replace(this.TabWrapper.id+'_', '');
        result = result.replace(this.ContentWrapper.id+'_', '');

        // Add back in the appropriate type
        if(type == 'tab') {
            result = this.TabWrapper.id+'_'+result;
        } else {
            result = this.ContentWrapper.id+'_'+result;
        }

        return result;
    },

    /*
        Method: loadPage
            Load the page of the given object.

        Parameters:
            obj - (object) The object for which the page is to be loaded.
    */
    loadPage: function(obj) {
        new Ajax.Request(obj.readAttribute('TabURL'), {
            onFailuire: function() {
                window.alert('Sorry, but the content of this tab could not be loaded');
            },
            onSuccess: function(obj, transport) {
                var content = $(this.getId(obj, 'content'));
                content.update(transport.responseText);
                obj.writeAttribute('TabLoaded', 'true');
            }.bind(this, obj)
        });
    },

    /*
        Method: removeTab
            Remove the given tab and its contents.

            Note that if the tab to be removed is the currently opened tab, the
            new currently opened tab will be the first tab in the tabs list.

        Parameters:
            tab - (string) The ID of the tab to remove. Note that this should
                not include the wrapper ID.
    */
    removeTab: function(tab) {
        tab = $(this.getId(tab));
        var content = $(this.getId(tab, 'content'));

        // Make sure they exist
        if(!(tab && content)) {
            return;
        }

        // See if the tab to be removed is the currently opened tab
        if(content == this.CurrentlyOpenTab) {
            // Show the first tab that is not the tab to be removed
            var tabToShow = null;
            for(var i = 0; i < this.Contents.length; ++i) {
                tabToShow = this.Contents[i];
                if(tabToShow != this.CurrentlyOpenTab) { break; }
            }
            this.showTab(tabToShow);
        }

        // Remove the tab
        tab.remove();

        // Remove the content
        content.remove();

        // Update stored data
        this.Tabs = this.TabWrapper.childElements();
        this.Contents = this.ContentWrapper.childElements();
    },

    /*
        Method: showTab
            Show the given tab while hiding all the other tabs.

        Parameters:
            tab - (object) The tab to show.
    */
    showTab: function(tab) {
        // Get the ID of the tab
        tab = this.getId(tab);

        // Hide all the other tabs while showing the given tab
        var currentTabId;
        for(var i = 0 ; i < this.Tabs.length; ++i) {
            currentTabId = this.getId(this.Tabs[i]);

            if(tab == currentTabId) {
                this.Tabs[i].addClassName('selected');
                this.Contents[i].show();
                this.CurrentlyOpenTab = this.Contents[i];

                // If it is a link, retrieve its content
                if(this.Tabs[i].readAttribute('TabURL')) {
                    if(!(this.Tabs[i].readAttribute('TabOnce') == 'true') ||
                       !(this.Tabs[i].readAttribute('TabLoaded') == 'true')) {
                        this.loadPage(this.Tabs[i]);
                        continue;
                    }
                }
            } else {
                this.Tabs[i].removeClassName('selected');
                this.Contents[i].hide();
            }
        }
    }
});


