update to mootools 1.3

/**
 * @module File
 */

/**
 * replaces file upload fields
 *
 *
is not resetted onReset

 *
 *
Tested in:

 *

 *
Safari 4 - implicitly labelled triggers when deletin a file; reset doesnt trigger

 *
Firefox 3.6.

 *
Google Chrome 6 - implicitly labelled triggers twice, when using alias button & triggers when deletin a file.

 *
Opera 10.62.

 *
IE 7 - gaga.

 *
IE 8 - implicitly labelled triggers when deletin a file.

 *

 *
 * @class File
 * @namespace cfe.modules
 *
 * @extends cfe.module.Button
 */

cfe.module.File = new Class({

    Extends: cfe.module.Button,

    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=file]",

    options: {
        /**
         * enables the use of fileicons through a bit of markup and css
         * @config fileIcons
         * @type boolean
         * @default true
         */
        fileIcons: true,
        /**
         * show only the filename, not the whole path
         * @config trimFilePath
         * @type boolean
         * @default true
         */
        trimFilePath: true,

        innerLabel: "Choose File"
    },

    hideOriginalElement: false,

    toElement: function()
    {
        return this.wrapa
    },

    /**
     * retreive the filepath
     *
     * @method getFilePath
     * @return {HTMLElement}
     */
    getFilePath: function()
    {
        return this.v;
    },

    afterInitialize: function()
    {
        this.a = this.button;

        if(this.hideOriginalElement) this.o.hideInPlace();

        if(this.o.id) this.wrapa.addClass(cfe.prefix+this.type+this.o.id.capitalize());

        // various additions
        if(!this.o.implicitLabel && !Browser.Engine.webkit) this.wrapa.addEvent("click", this.clicked.bindWithEvent(this));

        if(this.isDisabled()) this.disable();

        if(!this.options.slidingDoors) this.wrapa.addClass(cfe.prefix+this.type)

        if(this.options.buttonStyle) this.a.addClass(cfe.prefix+"Button")

        this.a.removeClass(cfe.prefix+this.type)
    },

    build: function()
    {
        this.parent();
        this.innerlabel.set("text", $pick(this.options.innerLabel, ""));

        // make original element follow mouse
        // setup wrapper
        this.wrapa = new Element("span",{
            "class": cfe.prefix+this.type,
            styles:
            {
                overflow: "hidden",
                position: "relative"
            }
        }).cloneEvents(this.a)

        this.wrapa.inject(this.a, "after").adopt(this.a, this.o);

        // add positioning styles and events to "hidden" file input
        this.o.addEvents({
            "mouseout": this.update.bind(this),
            "change": this.update.bind(this),
            onDisable: this.disable.bind(this),
            onEnable: this.enable.bind(this)
        }).setStyles({
            opacity: "0",
            visibility: "visible",
            height: "100%",
            width: "auto",
            position: "absolute",
            top: 0,
            right: 0,
            margin: 0
        });

        this.wrapa.addEvent("mousemove", this.follow.bindWithEvent(this));

        this.button = this.a;
        this.a = this.wrapa;

        // add filepath
        this.v = new Element("span").addClass(cfe.prefix+this.type+"Path hidden");

        this.path = new Element("span").addClass("filePath");

        if(this.options.fileIcons)
        {
            this.path.addClass("fileIcon");
        }

        this.cross = $( new cfe.module.Button({delegateClick: false}).addEvent("click", this.deleteCurrentFile.bind(this)) ).addClass("delete");

        this.v.adopt(this.path, this.cross).inject(this.wrapa, "after");

        this.update();
    },

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "file"
     */
    createOriginal: function()
    {
        return new Element("input",{
            type: "file"
        });
    },

    follow: function(ev)
    {
        this.o.setStyle("left",(ev.client.x-this.a.getLeft()-(this.o.getWidth()-30)));

        /* special treatment for internet explorer as the fileinput will not be cut off by overflow:hidden */
        if(Browser.Engine.trident){
            if(ev.client.x < this.a.getLeft() || ev.client.x > this.a.getLeft()+this.a.getWidth())
                this.o.setStyle("left", -9999);
        }
    },

    update: function()
    {
        // only do stuff if original element is not disabled
        if( !$chk(this.o.disabled) )
        {
            if( this.o.value != "" )
            {
                this.o.disable().blur();

                this.oldValue = this.o.getProperty("value");
                if(this.options.trimFilePath) this.oldValue = this.oldValue.trimFilePath();
                this.path.set("html", this.oldValue);

                if(this.options.fileIcons)
                {
                    var ind = this.oldValue.lastIndexOf(".");
                    this.path.setProperty("class","filePath fileIcon " + this.oldValue.substring(++ind).toLowerCase());
                }
                this.v.removeClass("hidden");
            }
            else
            {
                this.o.enable().focus();

                this.path.set("html", "");
                this.v.addClass("hidden");
            }

            this.parent();
        }
    },

    deleteCurrentFile: function()
    {
        // we dont clone the old file input here, since the attribute "value" would be cloned, too
        // and we cannot modify the value of an file input field without user interaction
        this.o = this.createOriginal().addClass(this.o.getProperty("class")).setProperties({
            name: this.o.getProperty("name"),
            id: this.o.getProperty("id"),
            style: this.o.getProperty("style"),
            title: this.o.getProperty("title")
        }).cloneEvents(this.o).replaces(this.o);

        this.update();
    }
});

// add trimFilePath to Native String for convenience
String.implement({
    trimFilePath: function()
    {
        var ind = false;
        if(!(ind = this.lastIndexOf("\\")))
            if(!(ind = this.lastIndexOf("\/")))
                ind = 0;

        return this.substring(++ind);
    }
});

/**
 * The core of custom form elements. Includes cfe.Generic and some slight addons to the native Element object.
 *
 * @module Core
 * @namespace cfe
 */

var cfe = {
  version: "0.9.9",
  prefix: "cfe",
  module: {},
  addon: {}
};

/**
 * extend Elements with some Helper functions
 * @class Helpers
 * @namespace Element
 */
Element.Helpers = new Class({

  /**
     * cross-browser method for disabling the text selection by setting css attributes
     *
     * @method disableTextSelection
     */
  disableTextSelection: function(){
    if(Browser.Engine.trident || Browser.Engine.presto){
      this.setProperty("unselectable","on");
    }
    else if(Browser.Engine.gecko){
      this.setStyle("MozUserSelect","none");
    }
    else if(Browser.Engine.webkit){
      this.setStyle("KhtmlUserSelect","none");
    }

    return this;
  },

  /**
     * disables a HTMLElement if its a form element by setting the disabled attribute to true
     *
     * @method disable
     * @return boolean true, if element could be disabled
     */
  disable: function()
  {
    if($type(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", true);
      this.fireEvent("onDisable");
    }

    return this;
  },

  /**
     * enables a HTMLElement if its a form element by setting the disabled attribute to false
     *
     * @method enable
     * @return {boolean} true, if element could be enabled
     */
  enable: function()
  {
    if($type(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", false);
      this.fireEvent("onEnable");
    }

    return this;
  },

  /**
     * enables or disabled a HTMLElement if its a form element depending on it's current state
     *
     * @method toggleDisabled
     * @return {boolean} true, if element could be toggled
     */
  toggleDisabled: function()
  {
    if($type(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", !this.getProperty("disabled") );
      this.fireEvent(this.getProperty("disabled")?"onDisable":"onEnable");
    }
    return this;
  },

  /**
     * returns the label-element which belongs to this element
     *
     * @method getLabel
     * @return HTMLElement or NULL
     */
  getLabel: function()
  {
    var label = null;

    // get label by id/for-combo
    if(this.id) label = $$("label[for="+this.id+"]")[0];

    // no label was found for id/for, let's see if it's implicitly labelled
    if(!label)
    {
      label = this.getParent('label');

      if(label) this.implicitLabel = true;
    }

    return label;
  },

  /**
     * generates the markup used by sliding doors css technique to use with this element
     *
     * @method setSlidingDoors
     *
     * @param count
     * @param type
     * @param prefix
     *
     * @return HTMLElement or NULL the wrapped HTMLElement
     */
  setSlidingDoors: function(count, type, prefix, suffixes)
  {
    var slide = null;
    var wrapped = this;
    prefix = $pick(prefix, "sd");

    suffixes = $pick(suffixes, []);

    for(var i = count; i > 0; --i)
    {
      wrapped.addClass( prefix+"Slide"+( (i == 1 && count == i) ? "" : (suffixes[i] || i) ));
      slide = new Element(type);
      try{
        wrapped = slide.wraps(wrapped);
      }catch(e){
        wrapped = slide.grab(wrapped);
      }
    }

    wrapped = null;

    return slide;
  },

  /**
     * hide an element but keep it's vertical position and leave it tab- & screenreader-able :)
     *
     * @method hideInPlace
     *
     * @return HTMLElement
     */
  hideInPlace: function()
  {
    return this.setStyles({
      position: "absolute",
      left: -99999,
      width: 1,
      height: 1
    });
  }
});

Element.implement(new Element.Helpers);

/**
 * @module Checkable
 */

/**
 *
replaces radiobuttons

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62.

 *
IE 7.

 *
 *
IE 8
 *

 *
labels dont work for normal labelled elements

 *

 *

 *
 *

 *
 * @class Radiobutton
 * @namespace cfe.module
 *
 * @extends cfe.modules.Checkbox
 */
cfe.module.Radiobutton = new Class({

    Extends: cfe.module.Checkbox,

    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=radio]",

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "radio"
     */
    createOriginal: function()
    {
        return new Element("input",{
            "type": "radio",
            "checked": this.options.checked
        });
    },

    /**
     * @method initializeAdv
     * @protected
     */
    afterInitialize: function()
    {
        this.parent();

        if( !(Browser.Engine.trident || Browser.Engine.gecko) ) this.o.addEvent("click", this.update.bind(this));

        // on check, disable all other radiobuttons in this group
        this.addEvent("check", function(){
            $$("input[name='"+this.o.get("name")+"']").each(function(el)
            {
                if(el != this.o && el.retrieve("cfe")) el.retrieve("cfe").uncheck();
            }.bind(this));
        })
    }
});

/**
 * replacement class for automated replacment of scoped form elements
 *
 * @module replace
 * @namespace cfe
 *
 */

cfe.Replace = new Class(
{
  Implements: [new Options, new Events],

  options:{
    scope: false,

    onInit: $empty,
    onInitSingle: $empty,
    onBeforeInitSingle: $empty,
    onSetModuleOption: $empty,
    onRegisterModule: $empty,
    onUnregisterModule: $empty,
    onComplete: $empty
  },

  modules: {},
  moduleOptions: {},
  moduleOptionsAll: {},

  initialize: function()
  {
    this.registerAllModules.bind(this)();
  },

  /**
     * @method registerAllModules
	 * registeres all loaded modules onInitialize
	 */
  registerAllModules: function(){

    //console.log("Register all modules");

    $each(cfe.module, function(modObj, modType){
      //console.log("Registering type "+modType);
      if(modType != "Generic")
        this.registerModule(modType);

    }.bind(this));
  },

  /* call to register module */
  registerModule: function(mod){

    //console.log("Call to registerModule with arg:"+mod);
    modObj = cfe.module[mod];

    this.fireEvent("onRegisterModule", [mod,modObj]);
    this.modules[mod] = modObj;
    this.moduleOptions[mod] = {};

    return true;
  },

  registerModules: function(mods)
  {
    $each(mods,function(mod){
      this.registerModule(mod);
    },this);
  },

  unregisterModule: function(mod)
  {
    modObj = cfe.module[mod];

    this.fireEvent("onUnregisterModule", [mod,modObj]);

    delete this.modules[mod];
  },

  unregisterModules: function(mods)
  {
    $each(mods,function(mod){
      this.unregisterModule(mod);
    },this);
  },
  /**
	 * sets a single option for a specified module
	 * if no module was given, it sets the options for all modules
	 *
     * @method setModuleOption
     *
	 * @param {String} 	mod 	Name of the module
	 * @param {String} 	opt 	Name of the option
	 * @param {Mixed} 	val		The options value
	 */
  setModuleOption: function(mod,opt,val){

    modObj = cfe.module[mod];

    this.fireEvent("onSetModuleOption", [mod,modObj,opt,val]);

    if(!modObj){
      this.moduleOptionsAll[opt] = val;
    }
    else{
      this.moduleOptions[mod][opt] = val;
    }
  },

  setModuleOptions: function(mod,opt){

    $each(opt, function(optionValue, optionName){
      this.setModuleOption(mod,optionName,optionValue);
    }.bind(this));

  },

  engage: function(options){

    this.setOptions(this.options, options);

    if($type(this.options.scope) != "element"){
      this.options.scope = $(document.body);
    }

    this.fireEvent("onInit");

    $each(this.modules,function(module,moduleName,i){

      var selector = module.prototype.selector;

      this.options.scope.getElements(selector).each(function(el,i){

        if(el.retrieve("cfe") != null){

          var basicOptions = {
            replaces: el
          };

          this.fireEvent("onBeforeInitSingle", [el,i,basicOptions]);

          var single = new module($merge(basicOptions,$merge(this.moduleOptions[moduleName],this.moduleOptionsAll)));

          this.fireEvent("onInitSingle", single);
        }else{
          this.fireEvent("onSkippedInitSingle", [el,i,basicOptions]);
        }

      },this);
    },this);

    this.fireEvent("onComplete");
  }
});

/**
 * @module Checkable
 */

/**
 *
replaces checkboxes

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62.

 *
IE 7.

 *
IE 8.

 *

 *
 * @class Checkbox
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 */
cfe.module.Checkbox = new Class({

  Extends: cfe.module.Button,

  instance: 0,

  /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
  selector: "input[type=checkbox]",

  options:{
    /**
         * @inherit
         */
    slidingDoors: false
  /**
         * Fired when the original element gets checked
         * @event onCheck
         */
  //onCheck: Class.empty,
  /**
         * Fired when the original element's checked state is set to false
         * @event onUnCheck
         */
  //onUncheck: Class.empty
  },

  afterInitialize: function()
  {
    this.parent();

    // important for resetting dynamically created cfe
    this.o.defaultChecked = this.o.checked;

    if( Browser.Engine.presto || Browser.Engine.trident )
    {
      //console.log("fix for element update (IE and Opera)")
      this.o.addEvent("click", this.update.bind(this) );
    }

    if(Browser.Engine.trident && this.o.implicitLabel)
    {
      //console.log("fix for implicit labels (IE) element["+this.o.id+"]")
      this.l.removeEvents("click")
      this.a.removeEvents("click").addEvent("click", function(e){
        e.stop();
        this.clicked.bind(this)();
      }.bind(this))
    }

    this.update();
  },

  /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "checkbox"
     * @protected
     */
  createOriginal: function()
  {
    return new Element("input",{
      type: "checkbox",
      checked: this.options.checked
    });
  },

  clicked: function(e)
  {
    if(Browser.Engine.trident && e) e.stop();

    this.parent()
  },

  /**
     * public method to check a checkbox programatically
     *
     * @method check
     * @public
     */
  check: function()
  {
    this.a.addClass("A");
    this.fireEvent("onCheck");
  },

  /**
   * public method to uncheck a checkbox programatically
   *
   * @method uncheck
   * @public
   */
  uncheck: function()
  {
    this.a.removeClass("A");
    this.fireEvent("onUncheck");
  },

  update: function()
  {
    this.o.checked ? this.check() : this.uncheck();
    this.parent();
  }
});

/**
 * @module Addon
 */

/**
 * pretty simple integration of auto-generated tooltips (from title-attribute)
 * depends on mootools Tips
 *
 * @class Tips
 * @namespace cfe.addon
 *
 */
cfe.addon.Tips = new Class({

    options: $merge(this.parent, {
        enableTips: true,
        ttStyle: "label",
        ttClass: cfe.prefix+"Tip"
    }),

    initToolTips: function(){

        if(!window.Tips || !this.options.enableTips){
            if(this.options.debug){
                this.throwMsg.bind(this)("CustomFormElements: initialization of toolTips failed.\nReason: Mootools Plugin 'Tips' not available.");
            }

            return false;
        }

        switch(this.options.ttStyle){
            default:case 'label': this.toolTipsLabel.bind(this)();break;
        }

        return true;
    },

    toolTipsLabel: function(){

        var labels = this.options.scope.getElements('label');

        labels.each(function(lbl,i){

            forEl = lbl.getProperty("for");

            if(!forEl){
                var cl = lbl.getProperty("class");

                if( $defined(cl) ){
                    var forEl = cl.match(/for_[a-zA-Z0-9\-]+/);

                    if(forEl){
                        forEl = forEl.toString();
                        el = $( forEl.replace(/for_/,"") );
                    }
                }

                if(!el){
                    el = lbl.getElement("input");
                }
            }else{
                el = $(forEl);
            }

            if(el){
                if($chk(qmTitle = el.getProperty("title"))){

                    el.setProperty("title","").setProperty("hint", qmTitle)

                    var qm = new Element("span",{
                        "class": this.options.ttClass,
                        "title": qmTitle
                    });

                    // check if implicit label span is present
                    var impLabel = lbl.getFirst("span[class=label]");

                    qm.injectInside($chk(impLabel)?impLabel:lbl);
                }
            }
        },this);

        new Tips($$('.'+this.options.ttClass+'[title]'));
    }
});

cfe.Replace.implement(new cfe.addon.Tips);
cfe.Replace.prototype.addEvent("onComplete", function(){
    this.initToolTips();
});

/**
 * @module Addon
 */

/**
 * adds dependencies to checkboxes
 *
 * @class Dependencies
 * @namespace cfe.addon
 *
 */
cfe.addon.Dependencies = new Class({

    /**
	 * adds dependencies for an element
	 * @param {Object} el
	 * @param {Array} dep
	 */
    addDependencies: function(el, deps){
        $each(deps,function(dep){
            this.addDependency(el,dep);
        }.bind(this));
    },

    /**
	 * adds dependency for an element
	 * @param {Object} el
	 * @param {Object} dep
	 */
    addDependency: function(el, dep){

        // create an array if needed
        if($type( el.retrieve('deps') ) !== "array"){
            el.store('deps', []);
        }

        // deps may be objects or strings > if a string was given, try to interpret it as id and fetch element by $()
        if($type(dep) === "string" || $type(dep) === "element"){
            el.retrieve('deps').push( $(dep) );
            return true;
        }

        return false;
    },

    getDependencies: function(el)
    {
        return el.retrieve('deps');
    },

    /**
	 * this is called when a new item of a cfemodule gets initialized
	 * it checks, whether there are dependencies for this element and adds them to its options
	 *
	 * @param {Object} el
	 */
    attachDependencies: function(el,i,baseOptions)
    {
        var deps = this.getDependencies(el);

        if($type(deps) === "array"){
            baseOptions.deps = deps;
            return true;
        }

        return false;
    }

});
cfe.Replace.implement(new cfe.addon.Dependencies);
cfe.Replace.prototype.addEvent("onBeforeInitSingle", cfe.Replace.prototype.attachDependencies);

cfe.addon.Dependencies.Helper = new Class({
    resolveDependencies: function()
    {
        var deps = this.o.retrieve('deps');

        if(deps){
            $each(deps, function(dep,i){
                dep.checked = true;
                dep.fireEvent("change");
            }.bind(this));
        }
    }
});

// support for checkboxes
cfe.module.Checkbox.implement(new cfe.addon.Dependencies.Helper);
cfe.module.Checkbox.prototype.addEvent("onCheck", function(){
    this.resolveDependencies();
});

/**
 * @module Button
 */

/**
 *  gets extended by modules to start off with standard, button-like behaviours.
 *
 *  when focused and pressed, form gets submitted
 *
 * @class Button
 * @namespace cfe.module
 *
 */
cfe.module.Button = new Class(
{
  Implements: [new Options, new Events],

  selector: "button",

  instance: 0,

  /**
   * basic options for all cfes and always available
   * @property options
   */
  options: {
    /**
     * if > 0, it will create markup for css sliding doors tech

     * the number defines the amount of wrappers to create around this element

     * 2: standard sliding doors (x- or y-Axis)

     * 4: sliding doors in all directions (x/y-Axis)
     *
     * @config slidingDoors
     * @type int
     * @default 4
     */
    slidingDoors: 2,

    /**
     * if this element shall replace an existing html form element, pass it here
     * @config replaces
     * @type HTMLElement
     */
    replaces: null,

    /**
     * determines if a click on the decorator should be delegated to the original element
     * @config delegateClick
     * @type Boolean
     */
    delegateClick: true,

    /**
     * may pass any element as a label (toggling, hovering,..) for this cfe
     * @config label
     * @type HTMLElement
     */
    label: null,

    /**
     * if this cfe is created programatically, it's possible to set the name attribute of the generated input element
     * @config name
     * @type string
     */
    name: "",

    /**
     * setting this to true will create a disabled element
     * @config disabled
     * @type boolean
     */
    disabled: false,

    buttonStyle: true

  /**
     * Fired when the mouse is moved over the "decorator" element
     * @event onMouseOver
     */
  //onMouseOver: Class.empty,

  /**
     * Fired when the mouse is moved away from the "decorator" element
     * @event onMouseOut
     */
  //onMouseOut: Class.empty,

  /**
     * Fired when the "original" element gets focus (e.g. by tabbing)
     * @event onFocus
     */
  //onFocus: Class.empty,

  /**
     * Fired when the "original" element loses focus
     * @event onBlur
     */
  //onBlur: Class.empty,

  /**
     * Fired when "decorator" is clicked by mouse
     * @event onClick
     */
  //onClick: Class.empty,

  /**
     * Fired when pressing down with the mouse button on the "decorator"
     * Fired when pressing the space key while "original" has focus
     * @event onPress
     */
  //onPress: Class.empty,

  /**
     * Fired when "decorator" was pressed and the mouse button is released
     * Fired when "original" was pressed by space key and this key is released
     * @event onRelease
     */
  //onRelease: Class.empty,

  /**
     * Fired when "original"'s value changes
     * @event onUpdate
     */
  //onUpdate: Class.empty,

  /**
     * Fired when "original" gets disabled by HTMLElement.enable()
     * @event onEnable
     */
  //onEnable: Class.empty,

  /**
     * Fired when "original" gets disabled by HTMLElement.disable()
     * @event onDisable
     */
  //onDisable: Class.empty,

    /**
     * Fired when decorator is completely loaded and is ready to use
     * @event onLoad
     */
  //onLoad: Class.empty

  },

  hideOriginalElement: true,

  /**
   * constructor

   * building algorithm for cfe (template method)

   *

   *
setOptions: set Options

   *
buildWrapper: setup the "decorator"

   *
setupOriginal: procede the "original" element (add Events...)

   *
addLabel: add and procede the label

   *
initializeAdv: last chance for subclasses to do initialization

   *
build: various specific dom handling and "decorator" building

   *
   * @method initialize
   * @constructor
   *
   * @param {Object} options
   **/
  initialize: function(opt)
  {
    // sets instance id
    this.instance = this.constructor.prototype.instance++;

    this.setOptions(opt);

    this.type = $pick(this.options.type, $H(cfe.module).keyOf(this.constructor));

    this.buildWrapper();

    // prepares original html element for use with cfe and injects decorator into dom
    this.setupOriginal();

    // add a label, if present
    this.addLabel( $pick(this.o.getLabel(), this.setupLabel(this.options.label) ) );

    this.build();

    // add events to wrapping element
    this.setupWrapperEvents();

    // specific initialization
    this.afterInitialize();

    this.fireEvent("load")
  },

  /**
   * retreive the "decorator"
   * mootools supports the use of $(myCfe) if toElement is defined
   *
   * @method toElement
   * @return {HTMLElement}
   */
  toElement: function()
  {
    return this.a;
  },

  /**
   * retreive the label
   *
   * @method getLabel
   * @return {HTMLElement}
   */
  getLabel: function()
  {
    return this.l;
  },

  /**
   * template method STEP 1
 */
  buildWrapper: function(){
    this.a = new Element("span");
  },

  /**
     * handles the creation of the underlying original form element

     * stores a reference to the cfe object in the original form element
        * template method STEP 2
        *
     * @method setupOriginal
     * @protected
     */
  setupOriginal: function()
  {
    // get original element
    if( $defined(this.options.replaces) )
    {
      this.o = this.options.replaces;
      this.a.inject(this.o, 'before');
    }
    else // standalone
    {
      this.o = this.createOriginal();

      if(this.options.id) this.o.setProperty("id", this.options.id);

      if(this.options.disabled) this.o.disable();

      if(this.options.name)
      {
        this.o.setProperty("name", this.options.name);

        if( !$chk(this.o.id) ) this.o.setProperty("id", this.options.name+this.instance);
      }

      if(this.options.value) this.o.setProperty("value", this.options.value);

      this.a.adopt(this.o);
    }

    this.o.addEvents({
      focus: this.setFocus.bind(this),
      blur: this.removeFocus.bind(this),
      change: this.update.bind(this),
      keydown: function(e){
        if(e.key == "space") this.press();
      }.bind(this),
      keyup: function(e){
        if(e.key == "space") this.release();
      }.bind(this),
      onDisable: function(){
        this.a.fireEvent("disable");
      }.bind(this),
      onEnable: function(){
        this.a.fireEvent("enable");
      }.bind(this)
    });

    // store a reference to this cfe "in" the original form element
    this.o.store("cfe", this);
  },

  /*
   * adds a label element to this cfe
   * template method STEP 3
   *
   * @method addLabel
   * @protected
   *
   * @param {HTMLElement} the label element to set as label for this cfe
   */
  addLabel: function(label)
  {
    if( !$defined(label) ) return;

    this.l = label;

    // remove for property
    if(!this.dontRemoveForFromLabel) this.l.removeProperty("for");

    // add adequate className, add stdEvents
    this.l.addClass(cfe.prefix+this.type+"L");

    if(this.o.id || this.o.name) this.l.addClass("for_"+ (this.o.id || (this.o.name+this.o.value).replace("]","-").replace("[","") ));

    // add stdevents
    this.l.addEvents({
      mouseover: this.hover.bind(this),
      mouseout: this.unhover.bind(this),
      mousedown: this.press.bind(this),
      mouseup: this.release.bind(this),
      click: this.clicked.bindWithEvent(this)
    });

    this.addEvents({
      press: function()
      {
        this.l.addClass("P");
      },
      release: function()
      {
        this.l.removeClass("P");
      },
      mouseOver: function()
      {
        this.l.addClass("H");
      },
      mouseOut: function()
      {
        this.l.removeClass("H");
        this.l.removeClass("P");
      },
      focus: function()
      {
        this.l.addClass("F");
      },
      blur: function()
      {
        this.l.removeClass("F");
      //this.l.removeClass("P");
      },
      enable: function()
      {
        this.l.removeClass("D");
      },
      disable: function()
      {
        this.l.addClass("D");
      }
    });
  },

  /**
     * part of the main template method for building the "decorator"

     * must be extended by subclasses
     *
     * template method STEP 4
     *
     * @method build
     * @protected
     */
  build: function(){

    this.innerlabel = this.setupInnerLabel();
    this.a.adopt(this.innerlabel);

    if( $chk(this.options.slidingDoors) )
    {
      this.a = this.a.setSlidingDoors(this.options.slidingDoors-1, "span", cfe.prefix).addClass(cfe.prefix+this.type);
    }
  },

  /**
   * adds events and mousepointer style to the "decorator"
   * usually gets called by buildWrapper
   *
   * template method STEP 5
   *
   * @method setupWrapperEvents
   * @protected
   */
  setupWrapperEvents: function()
  {
    if(!this.o.implicitLabel)
    {
      this.a.addEvents({
        mouseover: this.hover.bind(this),
        mouseout: this.unhover.bind(this),
        mousedown: this.press.bind(this),
        mouseup: this.release.bind(this)
      });
    }

    this.a.addEvents({
      disable: this.disable.bind(this),
      enable: this.enable.bind(this)
    })

  },

  /**
     * part of the main template method for building the "decorator"

     * gets called immediately before the build-method

     * may be extended by subclasses
     *
     * template method STEP 6
     *
     * @method initializeAdv
     * @protected
     */
  afterInitialize: function()
  {
    if(this.hideOriginalElement) this.o.hideInPlace();

    if(this.o.id) this.a.addClass(cfe.prefix+this.type+this.o.id.capitalize());

    // various additions
    if(!this.o.implicitLabel) this.a.addEvent("click", this.clicked.bindWithEvent(this));

    if(this.isDisabled()) this.a.fireEvent("disable");

    if(!this.options.slidingDoors) this.a.addClass(cfe.prefix+this.type)

    if(this.options.buttonStyle) this.a.addClass(cfe.prefix+"Button")
  },

  setupInnerLabel: function(){
    return new Element("span").addClass("label").disableTextSelection();
  },

  /**
     * getter for retrieving the disabled state of the "original" element
     *
     * @method isDisabled
     * @return boolean
     */
  isDisabled: function()
  {
    return this.o.getProperty("disabled")
  },

  /**
     * programatically creates an "original" element

     * each subclass has to implement this
     *
     * @method createOriginal
     * @return {HTMLElement}
     */
  createOriginal: function()
  {
    return new Element("button")
  },

  /*
     * creates a label element and fills it with the contents (may be html) given by option "label"
     *
     * @method setupLabel
     * @protected
     *
     * @return {HTMLElement or NULL} if option "label" is not set
     */
  setupLabel: function()
  {
    if( $defined(this.options.label) ) return new Element("label").set("html", this.options.label).setProperty("for", this.o.id);

    return null;
  },

  /**
     * wrapper method for event onPress

     * may be extended by subclasses
     *
     * @method press
     * @protected
     */
  press: function()
  {
    if(this.isDisabled()) return

    this.a.addClass("P");
    this.fireEvent("onPress");

    //console.log(this.type + "pressed");
  },

  /**
     * wrapper method for event onRelease

     * may be extended by subclasses
     *
     * @method release
     * @protected
     */
  release: function()
  {
    if(this.isDisabled()) return

    this.a.removeClass("P");
    this.fireEvent("onRelease");

    //console.log(this.type + "released");

  },

  /**
     * wrapper method for event onMouseOver

     * may be extended by subclasses
     *
     * @method onMouseOver
     * @protected
     */
  hover: function()
  {
    if(this.isDisabled()) return

    this.a.addClass("H");
    this.fireEvent("onMouseOver");

  //console.log(this.type + "hovered");
  },

  /**
     * wrapper method for event onMouseOut

     * may be extended by subclasses
     *
     * @method unhover
     * @protected
     */
  unhover: function()
  {
    if(this.isDisabled()) return

    this.a.removeClass("H");
    this.fireEvent("onMouseOut");
    this.release();

  //console.log(this.type + "unhovered");
  },

  /**
     * wrapper method for event onFocus

     * may be extended by subclasses
     *
     * @method setFocus
     * @protected
     */
  setFocus: function()
  {
    this.a.addClass("F");
    this.fireEvent("onFocus");

  //console.log(this.type + "focused");
  },

  /**
     * wrapper method for event onBlur

     * may be extended by subclasses
     *
     * @method removeFocus
     * @protected
     */
  removeFocus: function()
  {
    //console.log("o blurred");
    this.a.removeClass("F");
    // if cfe gets blurred, also clear press state
    //this.a.removeClass("P");
    this.fireEvent("onBlur");

  //console.log(this.type + "blurred");

  },

  /**
     * wrapper method for event onClick

     * delegates the click to the "original" element

     * may be extended by subclasses
     *
     * @method clicked
     * @protected
     */
  clicked: function(e)
  {
    //causes problems in other browsers than ie - gonna took into this later; its the best approach to stop the event from bubbling right here though imho
    //e.stop();

    if(this.isDisabled()) return

    if( $chk(this.o.click) && $chk(this.options.delegateClick) ) this.o.click();
    this.o.focus();

    this.fireEvent("onClick");

    //console.log(this.type + " clicked");
  },

  /**
     * wrapper method for event onUpdate

     * may be extended by subclasses
     *
     * @method update
     * @protected
     */
  update: function()
  {
    this.fireEvent("onUpdate");

    //console.log("o was updated");
  },

  /**
     * wrapper method for event onEnable

     * may be extended by subclasses
     *
     * @method enable
     * @protected
     */
  enable: function()
  {
    this.a.removeClass("D");
    this.fireEvent("onEnable");

    //console.log(this.type+"-"+this.instance+" now enabled");
  },

  /**
     * wrapper method for event onDisable

     * may be extended by subclasses
     *
     * @method disable
     * @protected
     */
  disable: function()
  {
    this.a.addClass("D");
    this.fireEvent("onDisable");

    //console.log(this.type+"-"+this.instance+" now disabled");
  }
});

/**
 * @module Scrollable
 */

/**
 * SCROLLING functionality for select boxes;
 * TODO: refactor to standalone module
 *
 * creates handles and sets up a slider object
 *
 * @class Scrollable
 * @namespace cfe.helper
 *
 *
 */
cfe.helper = cfe.helper || {}

cfe.helper.Scrollable = {

  options: {
    size: 4,
    scrolling: true
  },

  setupScrolling: function()
  {
    // slider config
    this.scrollerWrapper = new Element("span",{
      "class": cfe.prefix+"ScrollerWrapper",
      "styles":{
        height: this.container.getHeight()
      }
    }).inject(this.container, "after");

    var scrollTimer, scrollTimeout;
    var scrollStepHeight = this.allOptions[0].getHeight();

    function scroll(by){
      clearInterval(scrollTimer);
      clearTimeout(scrollTimeout);

      slider.set(slider.step+by);

      scrollTimeout = (function(){
        scrollTimer = (function(){
          slider.set(slider.step+by);
        }.bind(this)).periodical(50)
      }.bind(this)).delay(200)
    }

    function clearScroll(){
      clearInterval(scrollTimer);
      clearTimeout(scrollTimeout);
    }

    this.scrollerTop = $(new cfe.module.Button({
      delegateClick: false
    }).addEvent("release", clearScroll).addEvent("press", scroll.pass(-1))).addClass("scrollTop");

    this.scrollerBottom = $(new cfe.module.Button({
      delegateClick: false
    }).addEvent("release", clearScroll).addEvent("press", scroll.pass(1))).addClass("scrollBottom")

    this.scrollerKnob = new Element("span").addClass("scrollKnob");
    this.scrollerBack = new Element("span").addClass("scrollRail");

    this.scrollerBack.adopt(this.scrollerKnob);
    this.scrollerWrapper.adopt(this.scrollerBack, this.scrollerTop, this.scrollerBottom);

    var slider = new Slider(this.scrollerBack, this.scrollerKnob, {
      steps: this.allOptions.length-this.options.size,
      mode: "vertical" ,
      onChange: function(step){
        this.container.scrollTo(false,step*scrollStepHeight);
      }.bind(this)
    }).set(this.selectedIndex);

    this.scrollerKnob.setStyle("position", "absolute");

    // scrolling
    function wheelListener(ev)
    {
      ev.stop();
      slider.set(slider.step-ev.wheel);
    }

    this.boundWheelListener = wheelListener.bindWithEvent(this)

    this.addEvent("containerShow", function(){
      $(document.body).addEvent("mousewheel", this.boundWheelListener)

      slider.set(this.o.selectedIndex);
      this.container.scrollTo(false,this.o.selectedIndex*scrollStepHeight);
    })

    this.addEvent("containerHide", function(){
      $(document.body).removeEvent("mousewheel", this.boundWheelListener);
    })

    this.o.addEvent("change", function(){
      slider.set(this.o.selectedIndex)
    }.bind(this))
  }
}

/**
 * @module Selectable
 */

/**
 * replaces select fields
 *
 *
disabled should also disable scrolling

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62 - key autocompletion closes box.

 *
IE 7.

 *
IE 8.

 *

 *
 * @class Select
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 *
 */
cfe.module.Select = new Class({

  Extends: cfe.module.Button,
  Implements: cfe.helper.Scrollable,

  Binds: "clickedOutsideListener",

  instance: 0,

  /**
   * CSS Selector to fetch "original" HTMLElements for replacement with this module
   * @property selector
   * @type string
   */
  selector: "select:not(select[multiple])",

  selectedIndex: 0,

  setupOriginal: function()
  {
    this.parent();

    this.o.addEvents({
      "keyup": this.keyup.bind(this),
      "keydown": this.keydown.bind(this)
    });
  },

  afterInitialize: function()
  {
    this.parent();

    // set width to widest option if no width is given to select via css
    if(this.cssWidth == "auto") this.a.setStyle("width", this.container.getWidth())

    // inject labelArrow as IE 7 doesn't support :after css selector
    if(Browser.Engine.trident5) new Element("span").addClass("labelArrow").inject(this.innerlabel, "after")

    // select default option
    if(this.selectedIndex != -1) this.selectOption(this.selectedIndex, true);

    if(this.options.scrolling)
    {
      this.container.setStyle("height", this.allOptions[0].getHeight()*this.options.size);
      this.setupScrolling();
    }

    this.hideContainer();
  },

  /**
   * Method to programmatically create an "original" HTMLElement
   *
   * @method createOriginal
   * @return {HTMLElement} a select input
   */
  createOriginal: function()
  {
    var ori = new Element("select");

    if( $chk(this.options.options) )
    {
      for(var key in this.options.options)
      {
        ori.adopt( new Element("option", {
          value: key,
          selected: this.options.options[key].selected?"selected":""
        }).set("html", this.options.options[key].label ) );
      }
    }
    return ori;
  },

  build: function()
  {
    this.origOptions = this.o.getChildren();

    this.selectedIndex = this.o.selectedIndex || this.selectedIndex;

    // integrity check
    if(this.options.size > this.origOptions.length || this.options.scrolling != true) this.options.size = this.origOptions.length;

    this.parent();

    this.a.setStyles({"position": "relative", "z-index": 100000 - this.instance});

    // build container which shows on click
    this.buildContainer();

    this.cssWidth = this.a.getStyle("width");
  },

  buildContainer: function()
  {
    this.container = new Element("span",{
      "class": cfe.prefix+this.type+"Container",
      "styles":{
        "overflow":"hidden"
      }
    });

    this.containerWrapper = this.container.setSlidingDoors(4, "span", cfe.prefix).addClass(cfe.prefix+this.type+"ContainerWrapper").setStyles({
      position: "absolute",
      "z-index": this.instance + 1
    }).inject(this.a);

    // insert option elements
    this.allOptions = [];
    this.origOptions.each(function(el,i)
    {
      this.allOptions.push(this.buildOption(el, i))
    }.bind(this));

    this.container.adopt(this.allOptions);

  },

  buildOption: function(el, i)
  {
    return new Element("span",{
      "class": cfe.prefix+"Option "+cfe.prefix+"Option"+i+(el.get('class')?" "+el.get('class'):""),
      "events":{
        "mouseover": this.highlightOption.pass([i,true],this)/*,
                "mouseout": this.highlightOption.pass([i,true],this)*/
      }
    }).set('html', el.innerHTML.replace(/\s/g, " ")).disableTextSelection().store("index", i);
  },

  selectOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    this.highlightOption(index);

    this.selectedIndex = index;

    this.innerlabel.set('html', (this.allOptions[index]).innerHTML);

    this.fireEvent("onSelectOption", index);
  },

  highlightOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    if(this.highlighted) this.highlighted.removeClass("H");

    this.highlighted = this.allOptions[index].addClass("H");

    this.highlightedIndex = index;

    this.fireEvent("onHighlightOption", index);
  },

  updateOption: function(by)
  {
    // fix for IE 7
    if(this.containerWrapper.retrieve("hidden") != true && !Browser.Engine.trident5) this.o.selectedIndex = (this.highlightedIndex+by).limit(0,this.origOptions.length-1);
    this.o.fireEvent("change");
  },

  hideContainer: function()
  {
    $(document.body).removeEvent("click", this.clickedOutsideListener);

    this.containerWrapper.setStyle("display","none").removeClass("F").store("hidden", true);

    this.fireEvent("onContainerHide", this.selectedIndex);
  },

  showContainer: function()
  {
    $(document.body).addEvent("click", this.clickedOutsideListener);

    this.containerWrapper.setStyle("display", "block").addClass("F").store("hidden", false);

    this.highlightOption(this.o.selectedIndex);

    this.fireEvent("onContainerShow", this.selectedIndex);
  },

  clicked: function(ev)
  {
    if(!this.isDisabled())
    {
      if( $defined(ev.target) )
      {
        var oTarget = $(ev.target);

        if( oTarget.getParent() == this.container )
        {
          this.selectOption(oTarget.retrieve("index"), true);
          this.hideContainer();
          this.parent();
          this.o.selectedIndex = oTarget.retrieve("index");
          this.o.fireEvent("change");
          return;
        }
        else if(this.options.scrolling && oTarget.getParent("."+this.scrollerWrapper.getProperty("class")) == this.scrollerWrapper)
        {
          //console.log("no toggle");
          return;
        }
      }

      this.toggle();
      this.parent();
    }
  },

  toggle: function()
  {
    this.containerWrapper.retrieve("hidden") == true ? this.showContainer() : this.hideContainer();
  },

  keyup: function(ev)
  {
    // toggle on alt+arrow
    if(ev.alt && (ev.key == "up" || ev.key == "down") )
    {
      this.toggle();
      return;
    }

    switch(ev.key){
      case "enter":
      case "space":
        this.toggle();
        break;

      case "up":
        this.updateOption(-1);
        break;

      case "down":
        this.updateOption(1);
        break;

      case "esc":
        this.hideContainer();
        break;

      default:
        this.o.fireEvent("change");
        break;
    }
  },

  keydown: function(ev)
  {
    if(ev.key == "tab")
    {
      this.hideContainer();
    }
  },

  clickedOutsideListener: function(ev)
  {
    if ($(ev.target).getParent("."+cfe.prefix+this.type) != this.a && !( (Browser.Engine.trident || (Browser.Engine.presto && Browser.Engine.version > 950)) && ev.target == this.o) && ( !$chk(this.l) || (this.l && $(ev.target) != this.l) ) ) this.hideContainer();
  },

  update: function()
  {
    this.parent();
    this.selectOption(this.o.selectedIndex);
  }
});

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <link rel="stylesheet" href="css/cfe.css" />

CFE Barebone Example

for mootools 1.2

      Visit homepage

    <form id="form" method="post" action="#" enctype="multipart/form-data">

     <fieldset class="chb1" id="c1"><legend class="tog">Checkboxes (input[checkbox])</legend>

<input title="One important checkbox" id="chb1" type="checkbox" value="Checkbox1:Great!" name="checkbox1" />
                           Checkbox #1 - checked by default

<input id="chb2" type="checkbox" value="Checkbox2:Wow!" name="checkbox2" />
                           Checkbox #2

<input id="chb3" type="checkbox" value="Checkbox3:Fascinating!" name="checkbox3" />
                       Checkbox #3

<input id="chb4" type="checkbox" value="Checkbox4:Oh.Disabled!" name="checkbox4" checked="checked" disabled="disabled" />
            Checkbox #4 (disabled)

trigger checkbox 4 disabled/enabled

select all Checkboxes above

deselect all Checkboxes above

    </fieldset>

      <fieldset class="chb2" id="c2"><legend class="tog">Checkboxes with implicit labelling</legend>

NOTE: 3rd Checkbox has dependencies: 1st and 2nd get automatically checked if not already

<input title="One important checkbox" id="chb21" type="checkbox" checked="checked"  value="Checkbox1:Great!" name="checkbox21" />Checkbox #1 - checked by default

<input id="chb22" type="checkbox"  value="Checkbox2:Wow!" name="checkbox22" />Checkbox #2

<input id="chb23" type="checkbox" value="Checkbox3:Fascinating!" name="checkbox23"   />Checkbox #3

      </fieldset>

      <fieldset class="rb1" id="r1"><legend class="tog">Radiobuttons (input[radio])</legend>

<input id="rb1" title="The first element has a title-Attribute" type="radio" value="1:Choice1:Hooray!" name="radio" />
            Radiobutton #1 - checked by default

<input id="rb2" type="radio" value="1:Choice2:Woot!" name="radio" />
            Radiobutton #2

              <input type="radio" title="The first element has a title-Attribute" value="2:Choice1:Hooray!" name="radio2[hallo]" />
              Radiobutton #1 - checked by default

              <input type="radio" value="2:Choice2:Woot!" name="radio2[hallo]" />
              Radiobutton #2

<input type="radio" title="The first element has a title-Attribute" value="3:Choice1:Hooray!" name="radio3[hallo]" />

<input type="radio" value="3:Choice2:Woot!" name="radio3[hallo]" />

      </fieldset>

      <fieldset class="txt1" id="i1"><legend class="tog">Textinput&-area(input[text,password],textarea)- w/ slidingDoors</legend>

This toggles a text field
            <input id="input1" class="mediumInput" type="text" value="" name="input1" />

This toggles a second text field
            <input id="input2" type="text" value="" name="input2" />

This toggles a smaller third text field
            <input id="input3" class="shortInput" title="Put your initials here" type="text" value="" name="input3" />

This textfield is disabled
            <input id="input4" class="shortInput" title="Nothing to do here. Disabled." type="text" value="...disabled" disabled="disabled" name="input4" />

trigger textfield 4 disabled/enabled

This textfield is readonly
            <input id="input5" class="shortInput" title="Feel free to read this." type="text" value="...readonly" name="input5" readonly="readonly" />

Secret password field
            <input id="password" title="Enter secret password" type="password" value=" " name="password" />

And this one a text area
            <textarea name="textarea1" id="textarea1" rows="4" cols="5"></textarea>

And this one a disabled text area
            <textarea name="textarea2" id="textarea2" rows="2" cols="5" disabled="disabled">...disabled</textarea>

trigger textarea 2 disabled/enabled

And this one a readonly text area
            <textarea name="textarea3" id="textarea3" rows="2" cols="5" readonly="readonly">...readonly</textarea>

      </fieldset>

      <fieldset class="txt2" id="i2"><legend class="tog">Textinput&-area with implicit labelling</legend>

This toggles a text field
              <input type="text" class="mediumInput" value="" name="input31" id="autotabnext" />

This toggles a second text field
              <input type="text" value="" name="input32" />

This toggles a smaller third text field
              <input class="shortInput" title="Put your initials here" type="text" value="" name="input33" />

Secret password field
              <input title="Enter secret password" type="password" value="" name="password32" />

And this one a text area
              <textarea name="textarea31" rows="4" cols="5"></textarea>

      </fieldset>

      <fieldset class="sel1" id="s1"><legend class="tog">Selectfield (select) with and without multiple option and preselected</legend>

Normal select box
            <select id="sel-norm" name="selectNormal" title="select your occupation">
              <option value="op1">Option 1</option>
              <option value="op2">znother option 2</option>
              <option value="op3">3rd option superfancywide</option>
              <option value="op4">z Option 4</option>
              <option value="op5">g Option 5</option>
              <option value="op6">jl Option 6</option>
              <option value="op7">jOption 7</option>
              <option value="op8" selected="selected">jOption 8</option>
              <option value="op9">h opt 43</option>
              <option value="op10">hui</option>
            </select>

Normal select box 2 (smaller, with icons)

            <select id="sel-norm2" name="selectNormal2" title="select your occupation">
              <option value="op1">Option 1</option>
              <option value="op2">znother option 2</option>
              <option value="op3" class="icon1">3rd (class "icon1")</option>
              <option value="op4">z Option 4</option>
              <option value="op5">g Option 5</option>
              <option value="op6">jl Option 6</option>
              <option value="op7" class="icon2">jOption 7 (class "icon2")</option>
              <option value="op8" selected="selected">jOption 8</option>
              <option value="op9">h opt 43</option>
              <option value="op10">hui</option>
            </select>

Normal select box 3 (disabled)
            <select id="sel-norm3" name="selectNormal3" title="select nothing" disabled="disabled">
              <option value="op1">Option 1</option>
              <option value="op2">znother option 2</option>
              <option value="op3">3rd option superfancywide</option>
              <option value="op4">z Option 4</option>
              <option value="op5">g Option 5</option>
              <option value="op6">jl Option 6</option>
              <option value="op7">jOption 7</option>
              <option value="op8" selected="selected">jOption 8</option>
              <option value="op9">h opt 43</option>
              <option value="op10">hui</option>
            </select>

trigger select 3 disabled/enabled

Multiple select box
            <select id="sel-mult" multiple="multiple" name="selectMultiple[]">
              <option value="op1">Option 1</option>
              <option value="op2">Option 2</option>
              <option value="op3">Option 3</option>
              <option value="op4">Option 4</option>
              <option value="op5">Option 5</option>
              <option value="op6">Option 6</option>
              <option value="op7">Option 7</option>
              <option value="op8">Option 8</option>
            </select>

Multiple select box
            <select id="sel-mult2" multiple="multiple" name="selectMultiple2[]" disabled="disabled">
              <option value="op1">Option 1</option>
              <option value="op2">Option 2</option>
              <option value="op3">Option 3</option>
              <option value="op4">Option 4</option>
              <option value="op5">Option 5</option>
              <option value="op6">Option 6</option>
              <option value="op7">Option 7</option>
              <option value="op8">Option 8</option>
            </select>

trigger multiple select 2 disabled/enabled

      </fieldset>

      <fieldset class="up1" id="u1"><legend class="tog">file upload (input[file]) -- without and with implicit labelling</legend>

Upload a file
            <input id="upload" type="file" name="file" title="Upload a nice picture of yours. Maybe jpeg,jpg,gif." />

Upload a file
              <input id="upload2" type="file" name="file" title="Upload a nice picture of yours. Maybe jpeg,jpg,gif." />

Upload no file
            <input id="upload3" type="file" name="file3" title="Upload nothing - disabled." disabled="disabled" />

      </fieldset>

      <fieldset class="btn1" id="b1">
        <legend>Check POST Data or Reset Data via Buttons (input[submit], input[reset], input[image])</legend>

            <input type="submit" id="submitNorm" name="submit[normal]" title="via submit..." value="Submit" />

            <input type="reset" id="resetty" name="none" title="Reset the form..." value="Reset" />

            <input type="submit" id="submitNorm2" name="submit[normal2]" title="disabled" value="No function" disabled="disabled" />

            <input type="image" id="submitImg"  name="submit[image]" src="gfx/cfeImage.gif" title="via input type=image button" value="Image" />

            <input type="image" id="submitImg2" disabled="disabled" name="submit[image2]" src="gfx/cfeImage.gif" title="via input type=image button" value="Image" />

      </fieldset>

      <fieldset class="txt1" id="searchfield"><legend class="tog">Advanced example: Search field</legend>

            <input id="searchBox" size="2" type="text" value="" name="inputSearch" />
            <input id="searchBoxSubmit" type="submit" value="Go >" name="inputSearchSubmit" />

      </fieldset>
    </form>

/**
 * @module Selectable
 */

/**
 * replaces select fields with attribute multiple set
 *
 * bug:
 * mouseWheel support needed
 *
 * @class SelectMultiple
 * @namespace cfe.module
 *
 * @extends cfe.module.Select
 */
cfe.module.SelectMultiple = new Class({

  Extends: cfe.module.Select,
  instance: 0,

  /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
  selector: "select[multiple]",

  options: {
    buttonStyle: false
  },

  afterInitialize: function()
  {
    this.containerWrapper.addClass(cfe.prefix+"SelectContainerWrapper").setStyles({
      "position": "relative",
      "z-index": "auto"
    });
    this.container.addClass(cfe.prefix+"SelectContainer");

    this.o.addEvents({
      onDisable: function(){
        this.containerWrapper.getElements("input, button").each(function(el){
          el.disable();
        });
      }.bind(this),
      onEnable: function(){
        this.containerWrapper.getElements("input, button").each(function(el){
          el.enable();
        });
      }.bind(this)
    });

    this.parent();
  },

  setupWrapperEvents: function()
  {
    this.a = this.containerWrapper;
    this.parent();
  },

  buildOption: function(el, index)
  {
    var oOpt = new cfe.module.Checkbox({
      label: el.innerHTML,
      checked: $chk(el.selected),
      disabled: this.isDisabled()
    });
    oOpt.index = index;

    oOpt.addEvents({
      "check": function(index){
        this.origOptions[index].selected = true;
        this.o.fireEvent("change")
      }.pass(index, this),
      "uncheck": function(index){
        this.origOptions[index].selected = false;
        this.o.fireEvent("change")
      }.pass(index, this)
    });

    $(oOpt).addClass(cfe.prefix+"Option "+cfe.prefix+"Option"+index+(el.get('class')?" ".el.get('class'):"")).disableTextSelection();
    oOpt.getLabel().removeEvents().inject( $(oOpt) );

    return $(oOpt);
  },

  selectOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    this.highlightOption(index);
  },

  clicked: function()
  {
    if(!this.isDisabled())
    {
      this.o.focus();
      this.fireEvent("onClick");
    }
  },

  update: function()
  {
    this.fireEvent("onUpdate");
  },

  toggle: function(){},
  keydown: function(){},
  hideContainer: function(){},
  showContainer: function(){}
});

/**
 * @module File
 */

/**
 * replaces file upload fields
 *
 *
is not resetted onReset

 *
 *
Tested in:

 *

 *
Safari 4 - implicitly labelled triggers when deletin a file; reset doesnt trigger

 *
Firefox 3.6.

 *
Google Chrome 6 - implicitly labelled triggers twice, when using alias button & triggers when deletin a file.

 *
Opera 10.62.

 *
IE 7 - gaga.

 *
IE 8 - implicitly labelled triggers when deletin a file.

 *

 *
 * @class File
 * @namespace cfe.modules
 *
 * @extends cfe.module.Button
 */

cfe.module.File = new Class({

    Extends: cfe.module.Button,

    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=file]",

    options: {
        /**
         * enables the use of fileicons through a bit of markup and css
         * @config fileIcons
         * @type boolean
         * @default true
         */
        fileIcons: true,
        /**
         * show only the filename, not the whole path
         * @config trimFilePath
         * @type boolean
         * @default true
         */
        trimFilePath: true,

        innerLabel: "Choose File"
    },

    hideOriginalElement: false,

    toElement: function()
    {
        return this.wrapa
    },

    /**
     * retreive the filepath
     *
     * @method getFilePath
     * @return {HTMLElement}
     */
    getFilePath: function()
    {
        return this.v;
    },

    afterInitialize: function()
    {
        this.a = this.button;

        if(this.hideOriginalElement) this.o.hideInPlace();

        if(this.o.id) this.wrapa.addClass(cfe.prefix+this.type+this.o.id.capitalize());

        // various additions
        if(!this.o.implicitLabel && !Browser.safari && !Browser.chrome) this.wrapa.addEvent("click", this.clicked.pass([e],this));

        if(this.isDisabled()) this.disable();

        if(!this.options.slidingDoors) this.wrapa.addClass(cfe.prefix+this.type)

        if(this.options.buttonStyle) this.a.addClass(cfe.prefix+"Button")

        this.a.removeClass(cfe.prefix+this.type)
    },

    build: function()
    {
        this.parent();
        this.innerlabel.set("text", Array.pick(this.options.innerLabel, ""));

        // make original element follow mouse
        // setup wrapper
        this.wrapa = new Element("span",{
            "class": cfe.prefix+this.type,
            styles:
            {
                overflow: "hidden",
                position: "relative"
            }
        }).cloneEvents(this.a)

        this.wrapa.inject(this.a, "after").adopt(this.a, this.o);

        // add positioning styles and events to "hidden" file input
        this.o.addEvents({
            "mouseout": this.update.bind(this),
            "change": this.update.bind(this),
            onDisable: this.disable.bind(this),
            onEnable: this.enable.bind(this)
        }).setStyles({
            opacity: "0",
            visibility: "visible",
            height: "100%",
            width: "auto",
            position: "absolute",
            top: 0,
            right: 0,
            margin: 0
        });

        this.wrapa.addEvent("mousemove", this.follow.pass([e],this));

        this.button = this.a;
        this.a = this.wrapa;

        // add filepath
        this.v = new Element("span").addClass(cfe.prefix+this.type+"Path hidden");

        this.path = new Element("span").addClass("filePath");

        if(this.options.fileIcons)
        {
            this.path.addClass("fileIcon");
        }

        this.cross = $( new cfe.module.Button({delegateClick: false}).addEvent("click", this.deleteCurrentFile.bind(this)) ).addClass("delete");

        this.v.adopt(this.path, this.cross).inject(this.wrapa, "after");

        this.update();
    },

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "file"
     */
    createOriginal: function()
    {
        return new Element("input",{
            type: "file"
        });
    },

    follow: function(ev)
    {
        this.o.setStyle("left",(ev.client.x-this.a.getLeft()-(this.o.getWidth()-30)));

        /* special treatment for internet explorer as the fileinput will not be cut off by overflow:hidden */
        if(Browser.ie){
            if(ev.client.x < this.a.getLeft() || ev.client.x > this.a.getLeft()+this.a.getWidth())
                this.o.setStyle("left", -9999);
        }
    },

    update: function()
    {
        // only do stuff if original element is not disabled
        if( this.o.disabled == null )
        {
            if( this.o.value != "" )
            {
                this.o.disable().blur();

                this.oldValue = this.o.getProperty("value");
                if(this.options.trimFilePath) this.oldValue = this.oldValue.trimFilePath();
                this.path.set("html", this.oldValue);

                if(this.options.fileIcons)
                {
                    var ind = this.oldValue.lastIndexOf(".");
                    this.path.setProperty("class","filePath fileIcon " + this.oldValue.substring(++ind).toLowerCase());
                }
                this.v.removeClass("hidden");
            }
            else
            {
                this.o.enable().focus();

                this.path.set("html", "");
                this.v.addClass("hidden");
            }

            this.parent();
        }
    },

    deleteCurrentFile: function()
    {
        // we dont clone the old file input here, since the attribute "value" would be cloned, too
        // and we cannot modify the value of an file input field without user interaction
        this.o = this.createOriginal().addClass(this.o.getProperty("class")).setProperties({
            name: this.o.getProperty("name"),
            id: this.o.getProperty("id"),
            style: this.o.getProperty("style"),
            title: this.o.getProperty("title")
        }).cloneEvents(this.o).replaces(this.o);

        this.update();
    }
});

// add trimFilePath to Native String for convenience
String.implement({
    trimFilePath: function()
    {
        var ind = false;
        if(!(ind = this.lastIndexOf("\\")))
            if(!(ind = this.lastIndexOf("\/")))
                ind = 0;

        return this.substring(++ind);
    }
});

/**
 * The core of custom form elements. Includes cfe.Generic and some slight addons to the native Element object.
 *
 * @module Core
 * @namespace cfe
 */

var cfe = {
  version: "0.9.9",
  prefix: "cfe",
  module: {},
  addon: {}
};

/**
 * extend Elements with some Helper functions
 * @class Helpers
 * @namespace Element
 */
Element.Helpers = new Class({

  /**
     * cross-browser method for disabling the text selection by setting css attributes
     *
     * @method disableTextSelection
     */
  disableTextSelection: function(){
    if(Browser.ie || Browser.opera){
      this.setProperty("unselectable","on");
    }
    else if(Browser.firefox){
      this.setStyle("MozUserSelect","none");
    }
    else if(Browser.safari || Browser.chrome){
      this.setStyle("KhtmlUserSelect","none");
    }

    return this;
  },

  /**
     * disables a HTMLElement if its a form element by setting the disabled attribute to true
     *
     * @method disable
     * @return boolean true, if element could be disabled
     */
  disable: function()
  {
    if(typeOf(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", true);
      this.fireEvent("onDisable");
    }

    return this;
  },

  /**
     * enables a HTMLElement if its a form element by setting the disabled attribute to false
     *
     * @method enable
     * @return {boolean} true, if element could be enabled
     */
  enable: function()
  {
    if(typeOf(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", false);
      this.fireEvent("onEnable");
    }

    return this;
  },

  /**
     * enables or disabled a HTMLElement if its a form element depending on it's current state
     *
     * @method toggleDisabled
     * @return {boolean} true, if element could be toggled
     */
  toggleDisabled: function()
  {
    if(typeOf(this) === "element" && ["button", "input", "option", "optgroup", "select", "textarea"].contains( this.get("tag") )            )
    {
      this.setProperty("disabled", !this.getProperty("disabled") );
      this.fireEvent(this.getProperty("disabled")?"onDisable":"onEnable");
    }
    return this;
  },

  /**
     * returns the label-element which belongs to this element
     *
     * @method getLabel
     * @return HTMLElement or NULL
     */
  getLabel: function()
  {
    var label = null;

    // get label by id/for-combo
    if(this.id) label = $$("label[for="+this.id+"]")[0];

    // no label was found for id/for, let's see if it's implicitly labelled
    if(!label)
    {
      label = this.getParent('label');

      if(label) this.implicitLabel = true;
    }

    return label;
  },

  /**
     * generates the markup used by sliding doors css technique to use with this element
     *
     * @method setSlidingDoors
     *
     * @param count
     * @param type
     * @param prefix
     *
     * @return HTMLElement or NULL the wrapped HTMLElement
     */
  setSlidingDoors: function(count, type, prefix, suffixes)
  {
    var slide = null;
    var wrapped = this;
    prefix = Array.pick(prefix, "sd");

    suffixes = Array.pick(suffixes, []);

    for(var i = count; i > 0; --i)
    {
      wrapped.addClass( prefix+"Slide"+( (i == 1 && count == i) ? "" : (suffixes[i] || i) ));
      slide = new Element(type);
      try{
        wrapped = slide.wraps(wrapped);
      }catch(e){
        wrapped = slide.grab(wrapped);
      }
    }

    wrapped = null;

    return slide;
  },

  /**
     * hide an element but keep it's vertical position and leave it tab- & screenreader-able :)
     *
     * @method hideInPlace
     *
     * @return HTMLElement
     */
  hideInPlace: function()
  {
    return this.setStyles({
      position: "absolute",
      left: -99999,
      width: 1,
      height: 1
    });
  }
});

Element.implement(new Element.Helpers);

/**
 * @module Checkable
 */

/**
 *
replaces radiobuttons

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62.

 *
IE 7.

 *
 *
IE 8
 *

 *
labels dont work for normal labelled elements

 *

 *

 *
 *

 *
 * @class Radiobutton
 * @namespace cfe.module
 *
 * @extends cfe.modules.Checkbox
 */
cfe.module.Radiobutton = new Class({

    Extends: cfe.module.Checkbox,

    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=radio]",

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "radio"
     */
    createOriginal: function()
    {
        return new Element("input",{
            "type": "radio",
            "checked": this.options.checked
        });
    },

    /**
     * @method initializeAdv
     * @protected
     */
    afterInitialize: function()
    {
        this.parent();

        if( !(Browser.ie || Browser.firefox) ) this.o.addEvent("click", this.update.bind(this));

        // on check, disable all other radiobuttons in this group
        this.addEvent("check", function(){
            $$("input[name='"+this.o.get("name")+"']").each(function(el)
            {
                if(el != this.o && el.retrieve("cfe")) el.retrieve("cfe").uncheck();
            }.bind(this));
        })
    }
});

/**
 * replacement class for automated replacment of scoped form elements
 *
 * @module replace
 * @namespace cfe
 *
 */

cfe.Replace = new Class(
{
  Implements: [new Options, new Events],

  options:{
    scope: false,

    onInit: function(){},
    onInitSingle: function(){},
    onBeforeInitSingle: function(){},
    onSetModuleOption: function(){},
    onRegisterModule: function(){},
    onUnregisterModule: function(){},
    onComplete: function(){}
  },

  modules: {},
  moduleOptions: {},
  moduleOptionsAll: {},

  initialize: function()
  {
    this.registerAllModules.bind(this)();
  },

  /**
     * @method registerAllModules
	 * registeres all loaded modules onInitialize
	 */
  registerAllModules: function(){

    //console.log("Register all modules");

    Object.each(cfe.module, function(modObj, modType){
      //console.log("Registering type "+modType);
      if(modType != "Generic")
        this.registerModule(modType);

    },this);
  },

  /* call to register module */
  registerModule: function(mod){

    //console.log("Call to registerModule with arg:"+mod);
    modObj = cfe.module[mod];

    this.fireEvent("onRegisterModule", [mod,modObj]);
    this.modules[mod] = modObj;
    this.moduleOptions[mod] = {};

    return true;
  },

  registerModules: function(mods)
  {
    Object.each(mods,function(mod){
      this.registerModule(mod);
    },this);
  },

  unregisterModule: function(mod)
  {
    modObj = cfe.module[mod];

    this.fireEvent("onUnregisterModule", [mod,modObj]);

    delete this.modules[mod];
  },

  unregisterModules: function(mods)
  {
    Object.each(mods,function(mod){
      this.unregisterModule(mod);
    },this);
  },
  /**
	 * sets a single option for a specified module
	 * if no module was given, it sets the options for all modules
	 *
     * @method setModuleOption
     *
	 * @param {String} 	mod 	Name of the module
	 * @param {String} 	opt 	Name of the option
	 * @param {Mixed} 	val		The options value
	 */
  setModuleOption: function(mod,opt,val){

    modObj = cfe.module[mod];

    this.fireEvent("onSetModuleOption", [mod,modObj,opt,val]);

    if(!modObj){
      this.moduleOptionsAll[opt] = val;
    }
    else{
      this.moduleOptions[mod][opt] = val;
    }
  },

  setModuleOptions: function(mod,opt){

    Object.each(opt, function(optionValue, optionName){
      this.setModuleOption(mod,optionName,optionValue);
    },this);

  },

  engage: function(options){

    this.setOptions(this.options, options);

    if(typeOf(this.options.scope) != "element"){
      this.options.scope = $(document.body);
    }

    this.fireEvent("onInit");

    Object.each(this.modules,function(module,moduleName,i){

      var selector = module.prototype.selector;

      this.options.scope.getElements(selector).each(function(el,i){

        if(el.retrieve("cfe") != null){

          var basicOptions = {
            replaces: el
          };

          this.fireEvent("onBeforeInitSingle", [el,i,basicOptions]);

          var single = new module(Object.merge({},basicOptions,Object.merge({},this.moduleOptions[moduleName],this.moduleOptionsAll)));

          this.fireEvent("onInitSingle", single);
        }else{
          this.fireEvent("onSkippedInitSingle", [el,i,basicOptions]);
        }

      },this);
    },this);

    this.fireEvent("onComplete");
  }
});

/**
 * @module Checkable
 */

/**
 *
replaces checkboxes

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62.

 *
IE 7.

 *
IE 8.

 *

 *
 * @class Checkbox
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 */
cfe.module.Checkbox = new Class({

  Extends: cfe.module.Button,

  instance: 0,

  /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
  selector: "input[type=checkbox]",

  options:{
    /**
         * @inherit
         */
    slidingDoors: false
  /**
         * Fired when the original element gets checked
         * @event onCheck
         */
  //onCheck: Class.empty,
  /**
         * Fired when the original element's checked state is set to false
         * @event onUnCheck
         */
  //onUncheck: Class.empty
  },

  afterInitialize: function()
  {
    this.parent();

    // important for resetting dynamically created cfe
    this.o.defaultChecked = this.o.checked;

    if( Browser.ie || Browser.opera )
    {
      //console.log("fix for element update (IE and Opera)")
      this.o.addEvent("click", this.update.bind(this) );
    }

    if(Browser.ie && this.o.implicitLabel)
    {
      //console.log("fix for implicit labels (IE) element["+this.o.id+"]")
      this.l.removeEvents("click")
      this.a.removeEvents("click").addEvent("click", function(e){
        e.stop();
        this.clicked.bind(this)();
      }.bind(this))
    }

    this.update();
  },

  /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "checkbox"
     * @protected
     */
  createOriginal: function()
  {
    return new Element("input",{
      type: "checkbox",
      checked: this.options.checked
    });
  },

  clicked: function(e)
  {
    if(Browser.ie && e) e.stop();

    this.parent()
  },

  /**
     * public method to check a checkbox programatically
     *
     * @method check
     * @public
     */
  check: function()
  {
    this.a.addClass("A");
    this.fireEvent("onCheck");
  },

  /**
   * public method to uncheck a checkbox programatically
   *
   * @method uncheck
   * @public
   */
  uncheck: function()
  {
    this.a.removeClass("A");
    this.fireEvent("onUncheck");
  },

  update: function()
  {
    this.o.checked ? this.check() : this.uncheck();
    this.parent();
  }
});

/**
 * @module Addon
 */

/**
 * pretty simple integration of auto-generated tooltips (from title-attribute)
 * depends on mootools Tips
 *
 * @class Tips
 * @namespace cfe.addon
 *
 */
cfe.addon.Tips = new Class({

    options: Object.merge({},this.parent, {
        enableTips: true,
        ttStyle: "label",
        ttClass: cfe.prefix+"Tip"
    }),

    initToolTips: function(){

        if(!window.Tips || !this.options.enableTips){
            if(this.options.debug){
                this.throwMsg.bind(this)("CustomFormElements: initialization of toolTips failed.\nReason: Mootools Plugin 'Tips' not available.");
            }

            return false;
        }

        switch(this.options.ttStyle){
            default:case 'label': this.toolTipsLabel.bind(this)();break;
        }

        return true;
    },

    toolTipsLabel: function(){

        var labels = this.options.scope.getElements('label');

        labels.each(function(lbl,i){

            forEl = lbl.getProperty("for");

            if(!forEl){
                var cl = lbl.getProperty("class");

                if( cl != null ){
                    var forEl = cl.match(/for_[a-zA-Z0-9\-]+/);

                    if(forEl){
                        forEl = forEl.toString();
                        el = $( forEl.replace(/for_/,"") );
                    }
                }

                if(!el){
                    el = lbl.getElement("input");
                }
            }else{
                el = $(forEl);
            }

            if(el){
                if((qmTitle = el.getProperty("title")) != null){

                    el.setProperty("title","").setProperty("hint", qmTitle)

                    var qm = new Element("span",{
                        "class": this.options.ttClass,
                        "title": qmTitle
                    });

                    // check if implicit label span is present
                    var impLabel = lbl.getFirst("span[class=label]");

                    qm.inject((impLabel != null)?impLabel:lbl,'inside');
                }
            }
        },this);

        new Tips($$('.'+this.options.ttClass+'[title]'));
    }
});

cfe.Replace.implement(new cfe.addon.Tips);
cfe.Replace.prototype.addEvent("onComplete", function(){
    this.initToolTips();
});

/**
 * @module Addon
 */

/**
 * adds dependencies to checkboxes
 *
 * @class Dependencies
 * @namespace cfe.addon
 *
 */
cfe.addon.Dependencies = new Class({

    /**
	 * adds dependencies for an element
	 * @param {Object} el
	 * @param {Array} dep
	 */
    addDependencies: function(el, deps){
        Array.each(deps,function(dep){
            this.addDependency(el,dep);
        },this);
    },

    /**
	 * adds dependency for an element
	 * @param {Object} el
	 * @param {Object} dep
	 */
    addDependency: function(el, dep){

        // create an array if needed
        if(typeOf( el.retrieve('deps') ) !== "array"){
            el.store('deps', []);
        }

        // deps may be objects or strings > if a string was given, try to interpret it as id and fetch element by $()
        if(typeOf(dep) === "string" || typeOf(dep) === "element"){
            el.retrieve('deps').push( $(dep) );
            return true;
        }

        return false;
    },

    getDependencies: function(el)
    {
        return el.retrieve('deps');
    },

    /**
	 * this is called when a new item of a cfemodule gets initialized
	 * it checks, whether there are dependencies for this element and adds them to its options
	 *
	 * @param {Object} el
	 */
    attachDependencies: function(el,i,baseOptions)
    {
        var deps = this.getDependencies(el);

        if(typeOf(deps) === "array"){
            baseOptions.deps = deps;
            return true;
        }

        return false;
    }

});
cfe.Replace.implement(new cfe.addon.Dependencies);
cfe.Replace.prototype.addEvent("onBeforeInitSingle", cfe.Replace.prototype.attachDependencies);

cfe.addon.Dependencies.Helper = new Class({
    resolveDependencies: function()
    {
        var deps = this.o.retrieve('deps');

        if(deps){
            Array.each(deps, function(dep,i){
                dep.checked = true;
                dep.fireEvent("change");
            },this);
        }
    }
});

// support for checkboxes
cfe.module.Checkbox.implement(new cfe.addon.Dependencies.Helper);
cfe.module.Checkbox.prototype.addEvent("onCheck", function(){
    this.resolveDependencies();
});

/**
 * @module Button
 */

/**
 *  gets extended by modules to start off with standard, button-like behaviours.
 *
 *  when focused and pressed, form gets submitted
 *
 * @class Button
 * @namespace cfe.module
 *
 */
cfe.module.Button = new Class(
{
  Implements: [new Options, new Events],

  selector: "button",

  instance: 0,

  /**
   * basic options for all cfes and always available
   * @property options
   */
  options: {
    /**
     * if > 0, it will create markup for css sliding doors tech

     * the number defines the amount of wrappers to create around this element

     * 2: standard sliding doors (x- or y-Axis)

     * 4: sliding doors in all directions (x/y-Axis)
     *
     * @config slidingDoors
     * @type int
     * @default 4
     */
    slidingDoors: 2,

    /**
     * if this element shall replace an existing html form element, pass it here
     * @config replaces
     * @type HTMLElement
     */
    replaces: null,

    /**
     * determines if a click on the decorator should be delegated to the original element
     * @config delegateClick
     * @type Boolean
     */
    delegateClick: true,

    /**
     * may pass any element as a label (toggling, hovering,..) for this cfe
     * @config label
     * @type HTMLElement
     */
    label: null,

    /**
     * if this cfe is created programatically, it's possible to set the name attribute of the generated input element
     * @config name
     * @type string
     */
    name: "",

    /**
     * setting this to true will create a disabled element
     * @config disabled
     * @type boolean
     */
    disabled: false,

    buttonStyle: true

  /**
     * Fired when the mouse is moved over the "decorator" element
     * @event onMouseOver
     */
  //onMouseOver: Class.empty,

  /**
     * Fired when the mouse is moved away from the "decorator" element
     * @event onMouseOut
     */
  //onMouseOut: Class.empty,

  /**
     * Fired when the "original" element gets focus (e.g. by tabbing)
     * @event onFocus
     */
  //onFocus: Class.empty,

  /**
     * Fired when the "original" element loses focus
     * @event onBlur
     */
  //onBlur: Class.empty,

  /**
     * Fired when "decorator" is clicked by mouse
     * @event onClick
     */
  //onClick: Class.empty,

  /**
     * Fired when pressing down with the mouse button on the "decorator"
     * Fired when pressing the space key while "original" has focus
     * @event onPress
     */
  //onPress: Class.empty,

  /**
     * Fired when "decorator" was pressed and the mouse button is released
     * Fired when "original" was pressed by space key and this key is released
     * @event onRelease
     */
  //onRelease: Class.empty,

  /**
     * Fired when "original"'s value changes
     * @event onUpdate
     */
  //onUpdate: Class.empty,

  /**
     * Fired when "original" gets disabled by HTMLElement.enable()
     * @event onEnable
     */
  //onEnable: Class.empty,

  /**
     * Fired when "original" gets disabled by HTMLElement.disable()
     * @event onDisable
     */
  //onDisable: Class.empty,

    /**
     * Fired when decorator is completely loaded and is ready to use
     * @event onLoad
     */
  //onLoad: Class.empty

  },

  hideOriginalElement: true,

  /**
   * constructor

   * building algorithm for cfe (template method)

   *

   *
setOptions: set Options

   *
buildWrapper: setup the "decorator"

   *
setupOriginal: procede the "original" element (add Events...)

   *
addLabel: add and procede the label

   *
initializeAdv: last chance for subclasses to do initialization

   *
build: various specific dom handling and "decorator" building

   *
   * @method initialize
   * @constructor
   *
   * @param {Object} options
   **/
  initialize: function(opt)
  {
    // sets instance id
    this.instance = this.constructor.prototype.instance++;

    this.setOptions(opt);

    this.type = Array.pick(this.options.type, cfe.module.keyOf(this.constructor));

    this.buildWrapper();

    // prepares original html element for use with cfe and injects decorator into dom
    this.setupOriginal();

    // add a label, if present
    this.addLabel( Array.pick(this.o.getLabel(), this.setupLabel(this.options.label) ) );

    this.build();

    // add events to wrapping element
    this.setupWrapperEvents();

    // specific initialization
    this.afterInitialize();

    this.fireEvent("load")
  },

  /**
   * retreive the "decorator"
   * mootools supports the use of $(myCfe) if toElement is defined
   *
   * @method toElement
   * @return {HTMLElement}
   */
  toElement: function()
  {
    return this.a;
  },

  /**
   * retreive the label
   *
   * @method getLabel
   * @return {HTMLElement}
   */
  getLabel: function()
  {
    return this.l;
  },

  /**
   * template method STEP 1
 */
  buildWrapper: function(){
    this.a = new Element("span");
  },

  /**
     * handles the creation of the underlying original form element

     * stores a reference to the cfe object in the original form element
        * template method STEP 2
        *
     * @method setupOriginal
     * @protected
     */
  setupOriginal: function()
  {
    // get original element
    if( this.options.replaces != null )
    {
      this.o = this.options.replaces;
      this.a.inject(this.o, 'before');
    }
    else // standalone
    {
      this.o = this.createOriginal();

      if(this.options.id) this.o.setProperty("id", this.options.id);

      if(this.options.disabled) this.o.disable();

      if(this.options.name)
      {
        this.o.setProperty("name", this.options.name);

        if( this.o.id == null ) this.o.setProperty("id", this.options.name+this.instance);
      }

      if(this.options.value) this.o.setProperty("value", this.options.value);

      this.a.adopt(this.o);
    }

    this.o.addEvents({
      focus: this.setFocus.bind(this),
      blur: this.removeFocus.bind(this),
      change: this.update.bind(this),
      keydown: function(e){
        if(e.key == "space") this.press();
      }.bind(this),
      keyup: function(e){
        if(e.key == "space") this.release();
      }.bind(this),
      onDisable: function(){
        this.a.fireEvent("disable");
      }.bind(this),
      onEnable: function(){
        this.a.fireEvent("enable");
      }.bind(this)
    });

    // store a reference to this cfe "in" the original form element
    this.o.store("cfe", this);
  },

  /*
   * adds a label element to this cfe
   * template method STEP 3
   *
   * @method addLabel
   * @protected
   *
   * @param {HTMLElement} the label element to set as label for this cfe
   */
  addLabel: function(label)
  {
    if( label == null ) return;

    this.l = label;

    // remove for property
    if(!this.dontRemoveForFromLabel) this.l.removeProperty("for");

    // add adequate className, add stdEvents
    this.l.addClass(cfe.prefix+this.type+"L");

    if(this.o.id || this.o.name) this.l.addClass("for_"+ (this.o.id || (this.o.name+this.o.value).replace("]","-").replace("[","") ));

    // add stdevents
    this.l.addEvents({
      mouseover: this.hover.bind(this),
      mouseout: this.unhover.bind(this),
      mousedown: this.press.bind(this),
      mouseup: this.release.bind(this),
      click: this.clicked.bind(this,[e])
    });

    this.addEvents({
      press: function()
      {
        this.l.addClass("P");
      },
      release: function()
      {
        this.l.removeClass("P");
      },
      mouseOver: function()
      {
        this.l.addClass("H");
      },
      mouseOut: function()
      {
        this.l.removeClass("H");
        this.l.removeClass("P");
      },
      focus: function()
      {
        this.l.addClass("F");
      },
      blur: function()
      {
        this.l.removeClass("F");
      //this.l.removeClass("P");
      },
      enable: function()
      {
        this.l.removeClass("D");
      },
      disable: function()
      {
        this.l.addClass("D");
      }
    });
  },

  /**
     * part of the main template method for building the "decorator"

     * must be extended by subclasses
     *
     * template method STEP 4
     *
     * @method build
     * @protected
     */
  build: function(){

    this.innerlabel = this.setupInnerLabel();
    this.a.adopt(this.innerlabel);

    if( this.options.slidingDoors != null )
    {
      this.a = this.a.setSlidingDoors(this.options.slidingDoors-1, "span", cfe.prefix).addClass(cfe.prefix+this.type);
    }
  },

  /**
   * adds events and mousepointer style to the "decorator"
   * usually gets called by buildWrapper
   *
   * template method STEP 5
   *
   * @method setupWrapperEvents
   * @protected
   */
  setupWrapperEvents: function()
  {
    if(!this.o.implicitLabel)
    {
      this.a.addEvents({
        mouseover: this.hover.bind(this),
        mouseout: this.unhover.bind(this),
        mousedown: this.press.bind(this),
        mouseup: this.release.bind(this)
      });
    }

    this.a.addEvents({
      disable: this.disable.bind(this),
      enable: this.enable.bind(this)
    })

  },

  /**
     * part of the main template method for building the "decorator"

     * gets called immediately before the build-method

     * may be extended by subclasses
     *
     * template method STEP 6
     *
     * @method initializeAdv
     * @protected
     */
  afterInitialize: function()
  {
    if(this.hideOriginalElement) this.o.hideInPlace();

    if(this.o.id) this.a.addClass(cfe.prefix+this.type+this.o.id.capitalize());

    // various additions
    if(!this.o.implicitLabel) this.a.addEvent("click", this.clicked.pass([e],this));

    if(this.isDisabled()) this.a.fireEvent("disable");

    if(!this.options.slidingDoors) this.a.addClass(cfe.prefix+this.type)

    if(this.options.buttonStyle) this.a.addClass(cfe.prefix+"Button")
  },

  setupInnerLabel: function(){
    return new Element("span").addClass("label").disableTextSelection();
  },

  /**
     * getter for retrieving the disabled state of the "original" element
     *
     * @method isDisabled
     * @return boolean
     */
  isDisabled: function()
  {
    return this.o.getProperty("disabled")
  },

  /**
     * programatically creates an "original" element

     * each subclass has to implement this
     *
     * @method createOriginal
     * @return {HTMLElement}
     */
  createOriginal: function()
  {
    return new Element("button")
  },

  /*
     * creates a label element and fills it with the contents (may be html) given by option "label"
     *
     * @method setupLabel
     * @protected
     *
     * @return {HTMLElement or NULL} if option "label" is not set
     */
  setupLabel: function()
  {
    if( this.options.label != null ) return new Element("label").set("html", this.options.label).setProperty("for", this.o.id);

    return null;
  },

  /**
     * wrapper method for event onPress

     * may be extended by subclasses
     *
     * @method press
     * @protected
     */
  press: function()
  {
    if(this.isDisabled()) return

    this.a.addClass("P");
    this.fireEvent("onPress");

    //console.log(this.type + "pressed");
  },

  /**
     * wrapper method for event onRelease

     * may be extended by subclasses
     *
     * @method release
     * @protected
     */
  release: function()
  {
    if(this.isDisabled()) return

    this.a.removeClass("P");
    this.fireEvent("onRelease");

    //console.log(this.type + "released");

  },

  /**
     * wrapper method for event onMouseOver

     * may be extended by subclasses
     *
     * @method onMouseOver
     * @protected
     */
  hover: function()
  {
    if(this.isDisabled()) return

    this.a.addClass("H");
    this.fireEvent("onMouseOver");

  //console.log(this.type + "hovered");
  },

  /**
     * wrapper method for event onMouseOut

     * may be extended by subclasses
     *
     * @method unhover
     * @protected
     */
  unhover: function()
  {
    if(this.isDisabled()) return

    this.a.removeClass("H");
    this.fireEvent("onMouseOut");
    this.release();

  //console.log(this.type + "unhovered");
  },

  /**
     * wrapper method for event onFocus

     * may be extended by subclasses
     *
     * @method setFocus
     * @protected
     */
  setFocus: function()
  {
    this.a.addClass("F");
    this.fireEvent("onFocus");

  //console.log(this.type + "focused");
  },

  /**
     * wrapper method for event onBlur

     * may be extended by subclasses
     *
     * @method removeFocus
     * @protected
     */
  removeFocus: function()
  {
    //console.log("o blurred");
    this.a.removeClass("F");
    // if cfe gets blurred, also clear press state
    //this.a.removeClass("P");
    this.fireEvent("onBlur");

  //console.log(this.type + "blurred");

  },

  /**
     * wrapper method for event onClick

     * delegates the click to the "original" element

     * may be extended by subclasses
     *
     * @method clicked
     * @protected
     */
  clicked: function(e)
  {
    //causes problems in other browsers than ie - gonna took into this later; its the best approach to stop the event from bubbling right here though imho
    //e.stop();

    if(this.isDisabled()) return

    if( $chk(this.o.click) && this.options.delegateClick != null ) this.o.click();
    this.o.focus();

    this.fireEvent("onClick");

    //console.log(this.type + " clicked");
  },

  /**
     * wrapper method for event onUpdate

     * may be extended by subclasses
     *
     * @method update
     * @protected
     */
  update: function()
  {
    this.fireEvent("onUpdate");

    //console.log("o was updated");
  },

  /**
     * wrapper method for event onEnable

     * may be extended by subclasses
     *
     * @method enable
     * @protected
     */
  enable: function()
  {
    this.a.removeClass("D");
    this.fireEvent("onEnable");

    //console.log(this.type+"-"+this.instance+" now enabled");
  },

  /**
     * wrapper method for event onDisable

     * may be extended by subclasses
     *
     * @method disable
     * @protected
     */
  disable: function()
  {
    this.a.addClass("D");
    this.fireEvent("onDisable");

    //console.log(this.type+"-"+this.instance+" now disabled");
  }
});

/**
 * @module Scrollable
 */

/**
 * SCROLLING functionality for select boxes;
 * TODO: refactor to standalone module
 *
 * creates handles and sets up a slider object
 *
 * @class Scrollable
 * @namespace cfe.helper
 *
 *
 */
cfe.helper = cfe.helper || {}

cfe.helper.Scrollable = {

  options: {
    size: 4,
    scrolling: true
  },

  setupScrolling: function()
  {
    // slider config
    this.scrollerWrapper = new Element("span",{
      "class": cfe.prefix+"ScrollerWrapper",
      "styles":{
        height: this.container.getHeight()
      }
    }).inject(this.container, "after");

    var scrollTimer, scrollTimeout;
    var scrollStepHeight = this.allOptions[0].getHeight();

    function scroll(by){
      clearInterval(scrollTimer);
      clearTimeout(scrollTimeout);

      slider.set(slider.step+by);

      scrollTimeout = (function(){
        scrollTimer = (function(){
          slider.set(slider.step+by);
        }.bind(this)).periodical(50)
      }.bind(this)).delay(200)
    }

    function clearScroll(){
      clearInterval(scrollTimer);
      clearTimeout(scrollTimeout);
    }

    this.scrollerTop = $(new cfe.module.Button({
      delegateClick: false
    }).addEvent("release", clearScroll).addEvent("press", scroll.pass(-1))).addClass("scrollTop");

    this.scrollerBottom = $(new cfe.module.Button({
      delegateClick: false
    }).addEvent("release", clearScroll).addEvent("press", scroll.pass(1))).addClass("scrollBottom")

    this.scrollerKnob = new Element("span").addClass("scrollKnob");
    this.scrollerBack = new Element("span").addClass("scrollRail");

    this.scrollerBack.adopt(this.scrollerKnob);
    this.scrollerWrapper.adopt(this.scrollerBack, this.scrollerTop, this.scrollerBottom);

    var slider = new Slider(this.scrollerBack, this.scrollerKnob, {
      steps: this.allOptions.length-this.options.size,
      mode: "vertical" ,
      onChange: function(step){
        this.container.scrollTo(false,step*scrollStepHeight);
      }.bind(this)
    }).set(this.selectedIndex);

    this.scrollerKnob.setStyle("position", "absolute");

    // scrolling
    function wheelListener(ev)
    {
      ev.stop();
      slider.set(slider.step-ev.wheel);
    }

    this.boundWheelListener = wheelListener.pass([e],this)

    this.addEvent("containerShow", function(){
      $(document.body).addEvent("mousewheel", this.boundWheelListener)

      slider.set(this.o.selectedIndex);
      this.container.scrollTo(false,this.o.selectedIndex*scrollStepHeight);
    })

    this.addEvent("containerHide", function(){
      $(document.body).removeEvent("mousewheel", this.boundWheelListener);
    })

    this.o.addEvent("change", function(){
      slider.set(this.o.selectedIndex)
    }.bind(this))
  }
}

/**
 * @module Selectable
 */

/**
 * replaces select fields
 *
 *
disabled should also disable scrolling

 *
 *
Tested in:

 *

 *
Safari 4.

 *
Firefox 3.6.

 *
Google Chrome 6.

 *
Opera 10.62 - key autocompletion closes box.

 *
IE 7.

 *
IE 8.

 *

 *
 * @class Select
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 *
 */
cfe.module.Select = new Class({

  Extends: cfe.module.Button,
  Implements: cfe.helper.Scrollable,

  Binds: "clickedOutsideListener",

  instance: 0,

  /**
   * CSS Selector to fetch "original" HTMLElements for replacement with this module
   * @property selector
   * @type string
   */
  selector: "select:not(select[multiple])",

  selectedIndex: 0,

  setupOriginal: function()
  {
    this.parent();

    this.o.addEvents({
      "keyup": this.keyup.bind(this),
      "keydown": this.keydown.bind(this)
    });
  },

  afterInitialize: function()
  {
    this.parent();

    // set width to widest option if no width is given to select via css
    if(this.cssWidth == "auto") this.a.setStyle("width", this.container.getWidth())

    // inject labelArrow as IE 7 doesn't support :after css selector
    if(Browser.ie) new Element("span").addClass("labelArrow").inject(this.innerlabel, "after")

    // select default option
    if(this.selectedIndex != -1) this.selectOption(this.selectedIndex, true);

    if(this.options.scrolling)
    {
      this.container.setStyle("height", this.allOptions[0].getHeight()*this.options.size);
      this.setupScrolling();
    }

    this.hideContainer();
  },

  /**
   * Method to programmatically create an "original" HTMLElement
   *
   * @method createOriginal
   * @return {HTMLElement} a select input
   */
  createOriginal: function()
  {
    var ori = new Element("select");

    if( this.options.options != null )
    {
      for(var key in this.options.options)
      {
        ori.adopt( new Element("option", {
          value: key,
          selected: this.options.options[key].selected?"selected":""
        }).set("html", this.options.options[key].label ) );
      }
    }
    return ori;
  },

  build: function()
  {
    this.origOptions = this.o.getChildren();

    this.selectedIndex = this.o.selectedIndex || this.selectedIndex;

    // integrity check
    if(this.options.size > this.origOptions.length || this.options.scrolling != true) this.options.size = this.origOptions.length;

    this.parent();

    this.a.setStyles({"position": "relative", "z-index": 100000 - this.instance});

    // build container which shows on click
    this.buildContainer();

    this.cssWidth = this.a.getStyle("width");
  },

  buildContainer: function()
  {
    this.container = new Element("span",{
      "class": cfe.prefix+this.type+"Container",
      "styles":{
        "overflow":"hidden"
      }
    });

    this.containerWrapper = this.container.setSlidingDoors(4, "span", cfe.prefix).addClass(cfe.prefix+this.type+"ContainerWrapper").setStyles({
      position: "absolute",
      "z-index": this.instance + 1
    }).inject(this.a);

    // insert option elements
    this.allOptions = [];
    this.origOptions.each(function(el,i)
    {
      this.allOptions.push(this.buildOption(el, i))
    }.bind(this));

    this.container.adopt(this.allOptions);

  },

  buildOption: function(el, i)
  {
    return new Element("span",{
      "class": cfe.prefix+"Option "+cfe.prefix+"Option"+i+(el.get('class')?" "+el.get('class'):""),
      "events":{
        "mouseover": this.highlightOption.pass([i,true],this)/*,
                "mouseout": this.highlightOption.pass([i,true],this)*/
      }
    }).set('html', el.innerHTML.replace(/\s/g, " ")).disableTextSelection().store("index", i);
  },

  selectOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    this.highlightOption(index);

    this.selectedIndex = index;

    this.innerlabel.set('html', (this.allOptions[index]).innerHTML);

    this.fireEvent("onSelectOption", index);
  },

  highlightOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    if(this.highlighted) this.highlighted.removeClass("H");

    this.highlighted = this.allOptions[index].addClass("H");

    this.highlightedIndex = index;

    this.fireEvent("onHighlightOption", index);
  },

  updateOption: function(by)
  {
    // fix for IE 7
    if(this.containerWrapper.retrieve("hidden") != true && !Browser.ie) this.o.selectedIndex = (this.highlightedIndex+by).limit(0,this.origOptions.length-1);
    this.o.fireEvent("change");
  },

  hideContainer: function()
  {
    $(document.body).removeEvent("click", this.clickedOutsideListener);

    this.containerWrapper.setStyle("display","none").removeClass("F").store("hidden", true);

    this.fireEvent("onContainerHide", this.selectedIndex);
  },

  showContainer: function()
  {
    $(document.body).addEvent("click", this.clickedOutsideListener);

    this.containerWrapper.setStyle("display", "block").addClass("F").store("hidden", false);

    this.highlightOption(this.o.selectedIndex);

    this.fireEvent("onContainerShow", this.selectedIndex);
  },

  clicked: function(ev)
  {
    if(!this.isDisabled())
    {
      if( ev.target != null )
      {
        var oTarget = $(ev.target);

        if( oTarget.getParent() == this.container )
        {
          this.selectOption(oTarget.retrieve("index"), true);
          this.hideContainer();
          this.parent();
          this.o.selectedIndex = oTarget.retrieve("index");
          this.o.fireEvent("change");
          return;
        }
        else if(this.options.scrolling && oTarget.getParent("."+this.scrollerWrapper.getProperty("class")) == this.scrollerWrapper)
        {
          //console.log("no toggle");
          return;
        }
      }

      this.toggle();
      this.parent();
    }
  },

  toggle: function()
  {
    this.containerWrapper.retrieve("hidden") == true ? this.showContainer() : this.hideContainer();
  },

  keyup: function(ev)
  {
    // toggle on alt+arrow
    if(ev.alt && (ev.key == "up" || ev.key == "down") )
    {
      this.toggle();
      return;
    }

    switch(ev.key){
      case "enter":
      case "space":
        this.toggle();
        break;

      case "up":
        this.updateOption(-1);
        break;

      case "down":
        this.updateOption(1);
        break;

      case "esc":
        this.hideContainer();
        break;

      default:
        this.o.fireEvent("change");
        break;
    }
  },

  keydown: function(ev)
  {
    if(ev.key == "tab")
    {
      this.hideContainer();
    }
  },

  clickedOutsideListener: function(ev)
  {
    if ($(ev.target).getParent("."+cfe.prefix+this.type) != this.a && !( (Browser.ie || Browser.opera) && ev.target == this.o) && ( this.l == null || (this.l && $(ev.target) != this.l) ) ) this.hideContainer();
  },

  update: function()
  {
    this.parent();
    this.selectOption(this.o.selectedIndex);
  }
});

/**
 * @module Selectable
 */

/**
 * replaces select fields with attribute multiple set
 *
 * bug:
 * mouseWheel support needed
 *
 * @class SelectMultiple
 * @namespace cfe.module
 *
 * @extends cfe.module.Select
 */
cfe.module.SelectMultiple = new Class({

  Extends: cfe.module.Select,
  instance: 0,

  /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
  selector: "select[multiple]",

  options: {
    buttonStyle: false
  },

  afterInitialize: function()
  {
    this.containerWrapper.addClass(cfe.prefix+"SelectContainerWrapper").setStyles({
      "position": "relative",
      "z-index": "auto"
    });
    this.container.addClass(cfe.prefix+"SelectContainer");

    this.o.addEvents({
      onDisable: function(){
        this.containerWrapper.getElements("input, button").each(function(el){
          el.disable();
        });
      }.bind(this),
      onEnable: function(){
        this.containerWrapper.getElements("input, button").each(function(el){
          el.enable();
        });
      }.bind(this)
    });

    this.parent();
  },

  setupWrapperEvents: function()
  {
    this.a = this.containerWrapper;
    this.parent();
  },

  buildOption: function(el, index)
  {
    var oOpt = new cfe.module.Checkbox({
      label: el.innerHTML,
      checked: (el.selected != null),
      disabled: this.isDisabled()
    });
    oOpt.index = index;

    oOpt.addEvents({
      "check": function(index){
        this.origOptions[index].selected = true;
        this.o.fireEvent("change")
      }.pass(index, this),
      "uncheck": function(index){
        this.origOptions[index].selected = false;
        this.o.fireEvent("change")
      }.pass(index, this)
    });

    $(oOpt).addClass(cfe.prefix+"Option "+cfe.prefix+"Option"+index+(el.get('class')?" ".el.get('class'):"")).disableTextSelection();
    oOpt.getLabel().removeEvents().inject( $(oOpt) );

    return $(oOpt);
  },

  selectOption: function(index)
  {
    index = index.limit(0,this.origOptions.length-1);

    this.highlightOption(index);
  },

  clicked: function()
  {
    if(!this.isDisabled())
    {
      this.o.focus();
      this.fireEvent("onClick");
    }
  },

  update: function()
  {
    this.fireEvent("onUpdate");
  },

  toggle: function(){},
  keydown: function(){},
  hideContainer: function(){},
  showContainer: function(){}
});