/*
LICENCE INFORMATION ON Custom Form Elements
****************************************************

Custom Form Elements (CFE) for mootools 1.2 - style form elements on your own
by Maik Vlcek (http://www.mediavrog.net)

Copyright (c) Maik Vlcek (mediavrog.net)

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

The file GNU.txt contains the complete license text.
If this package didn't come with an GNU.txt, you may
get the full text on http://www.gnu.org/licenses/gpl.html
*/
/**
 * 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);/**
 * 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) return

                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);
				
            },this);
        },this);
		
        this.fireEvent("onComplete");
    }
});/**
 * @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<br />
     * the number defines the amount of wrappers to create around this element<br />
     * 2: standard sliding doors (x- or y-Axis)<br />
     * 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<br />
   * building algorithm for cfe (template method)<br />
   * <ol>
   * <li>setOptions: set Options</li>
   * <li>buildWrapper: setup the "decorator"</li>
   * <li>setupOriginal: procede the "original" element (add Events...)</li>
   * <li>addLabel: add and procede the label</li>
   * <li>initializeAdv: last chance for subclasses to do initialization</li>
   * <li>build: various specific dom handling and "decorator" building</li>
   *
   * @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 <br />
     * 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"<br />
     * 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"<br />
     * gets called immediately before the build-method<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * delegates the click to the "original" element<br />
     * 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<br />
     * may be extended by subclasses
     *
     * @method update
     * @protected
     */
  update: function()
  {
    this.fireEvent("onUpdate");

    //console.log("o was updated");
  },

  /**
     * wrapper method for event onEnable<br />
     * 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<br />
     * 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 Checkable
 */

/**
 * <p><strong>replaces checkboxes</strong></p>
 * 
 * <h6>Tested in:</h6>
 * <ul>
 * <li>Safari 4.</li>
 * <li>Firefox 3.6.</li>
 * <li>Google Chrome 6.</li>
 * <li>Opera 10.62.</li>
 * <li>IE 7.</li>
 * <li>IE 8.</li>
 * </ul>
 *
 * @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 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
 *
 * <p>Box wont show on click on alias</p>
 * <p>scolling bugs</p>
 * <p>highlighting > scoll to bottom bug</p>
 * <p>disabled should also disable scrolling</p>
 * 
 * <h6>Tested in:</h6>
 * <ul>
 * <li>Safari 4.</li>
 * <li>Firefox 3.6.</li>
 * <li>Google Chrome 6.</li>
 * <li>Opera 10.62 - key autocompletion closes box.</li>
 * <li>IE 7 - no function</li>
 * <li>IE 8.</li>
 *  </ul>
 *
 * @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, "&nbsp;")).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);
  }
});/**
 * @module Button
 */

/**
 * Extends the generic module to replace inputs of type 'submit'
 *
 * <h6>Tested in:</h6>
 * <ul>
 *   <li>Safari 4.</li>
 *   <li>Firefox 3.6.</li>
 *   <li>Google Chrome 6.</li>
 *   <li>Opera 10.62.</li>
 *   <li>IE 7.</li>
 *   <li>IE 8.</li>
 * </ul>
 *
 *
 * @class Submit
 * @namespace cfe.module
 * 
 * @extends cfe.module.Button
 *
 * @constructor
 *
 * bug: - press then click outside > press state doesn't clear
 */
cfe.module.Submit = new Class({
	
    Extends: cfe.module.Button,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=submit]",

    build: function(){
        this.parent();
        this.innerlabel.set("text", this.o.value);
    },

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "submit"
     */
    createOriginal: function()
    {
        return new Element("input",{
            type: "submit"
        });
    }
});/**
 * @module Text
 */

/**
 * replaces input[type=text]
 *
 * <h6>Tested in:</h6>
 * <ul>
 * <li>Safari 4.</li>
 * <li>Firefox 3.6.</li>
 * <li>Google Chrome 6.</li>
 * <li>Opera 10.62.</li>
 * <li>IE 7
 *    <ul>
 *      <li>problems with inline-block?</li>
 *      <li>implicit labels - no trigger</li>
 *    </ul>
 *  </li>
 * <li>IE 8.</li>
 *  </ul>
 *
 * @class Text
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 *
 */
cfe.module.Text = 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=text]",

    options: {
        buttonStyle: false
    },
    
    /**
     * flag to prevent deleting the for attribute from the label<br />
     * for text fields this is important, since the "original" element doesn't get hidden
     * @property dontRemoveForFromLabel
     * @type boolean
     * @protected
     */
    dontRemoveForFromLabel: true,

    hideOriginalElement: false,
    
    /**
     * since there's no real "decorator" (just wrapping for sliding doors) for textfields, it won't fetch events
     * instead, the original input field will solely handle events
     *
     * @method setupWrapper
     * @protected
     */
    setupWrapperEvents: function()
    {
        this.a.addEvents({
            disable: this.disable.bind(this),
            enable: this.enable.bind(this)
        });
    },

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "text"
     */
    createOriginal: function()
    {
        return new Element("input").set("type", "text");
    },

    setupInnerLabel: function(){
        return this.o
    }
});/**
 * @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<br />
     * the number defines the amount of wrappers to create around this element<br />
     * 2: standard sliding doors (x- or y-Axis)<br />
     * 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<br />
   * building algorithm for cfe (template method)<br />
   * <ol>
   * <li>setOptions: set Options</li>
   * <li>buildWrapper: setup the "decorator"</li>
   * <li>setupOriginal: procede the "original" element (add Events...)</li>
   * <li>addLabel: add and procede the label</li>
   * <li>initializeAdv: last chance for subclasses to do initialization</li>
   * <li>build: various specific dom handling and "decorator" building</li>
   *
   * @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 <br />
     * 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"<br />
     * 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"<br />
     * gets called immediately before the build-method<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * 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<br />
     * delegates the click to the "original" element<br />
     * 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<br />
     * may be extended by subclasses
     *
     * @method update
     * @protected
     */
  update: function()
  {
    this.fireEvent("onUpdate");

    //console.log("o was updated");
  },

  /**
     * wrapper method for event onEnable<br />
     * 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<br />
     * 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 File
 */

/**
 * replaces file upload fields
 *
 * <p>is not resetted onReset</p>
 * 
 * <h6>Tested in:</h6>
 * <ul>
 * <li>Safari 4 - implicitly labelled triggers when deletin a file; reset doesnt trigger</li>
 * <li>Firefox 3.6.</li>
 * <li>Google Chrome 6 - implicitly labelled triggers twice, when using alias button & triggers when deletin a file.</li>
 * <li>Opera 10.62.</li>
 * <li>IE 7 - gaga.</li>
 * <li>IE 8 - implicitly labelled triggers when deletin a file.</li>
 *  </ul>
 *
 * @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);
    }
});/**
 * @module Button
 */

/**
 * Provides replacement for input[type=image]<br />
 * This module dynamically appends the current state (hover, press) to the image filename given in the src attribute
 *
 * <h6>Tested in:</h6>
 * <ul>
 *   <li>Safari 4.</li>
 *   <li>Firefox 3.6.</li>
 *   <li>Google Chrome 6.</li>
 *   <li>Opera 10.62.</li>
 *   <li>IE 7.</li>
 *   <li>IE 8.</li>
 * </ul>
 *
 * @class Image
 * @namespace cfe.module
 *
 * @extends cfe.module.Button
 *
 * @constructor
 */
cfe.module.Image = new Class({
    
  Extends: cfe.module.Button,
    
  /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
  selector: "input[type=image]",

  hideOriginalElement: false,

  options: {
    slidingDoors: false,
    buttonStyle: false,
    statePrefix: "-state-",
    states: ["H", "F", "P", "D"]
  },

  /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "image"
     */
  createOriginal: function()
  {
    return new Element("input", {
      type: "image"
    });
  },

  setupInnerLabel: function(){
    return this.o;
  },

  afterInitialize: function()
  {
    this.stateRegEx = new RegExp(this.options.statePrefix+"(["+this.options.states.join()+"])");

    // preloading of state images
    var ind = this.o.src.lastIndexOf(".");
    var part1 = this.o.src.substring(0,ind) + this.options.statePrefix;
    var part2 = this.o.src.substring(ind);

    if(Asset) new Asset.images( this.options.states.map(function(state){
      return  part1 + state + part2;
    }));

    this.parent();

  },

  /**
     * sets a given state
     *
     * @method setState
     * @protected
     */
  setState: function(state)
  {
    if(this.options.states.indexOf(state) != -1)
    {
      this.clearState();
      var ind = this.o.src.lastIndexOf(".");
      this.o.src = this.o.src.substring(0,ind) + this.options.statePrefix + state + this.o.src.substring(ind);
    }
  },

  /**
     * clears all states
     *
     * @method clearState
     * @protected
     */
  clearState: function()
  {
    this.o.src = this.o.src.replace(this.stateRegEx, "");
  },

  /**
     * wrapper method for event onMouseOver<br />
     * sets the "hover" state of the image button
     *
     * @method hover
     * @protected
     */
  hover: function()
  {
    if(this.isDisabled()) return

    this.parent();
    this.setState("H");
  },

  /**
     * wrapper method for event onMouseOut<br />
     * clears the "hover" state of the image button
     *
     * @method unhover
     * @protected
     */
  unhover: function()
  {
    if(this.isDisabled()) return
    
    this.parent();
    this.clearState();
    if(this.a.hasClass("F")) this.setState("F");
  },

  /**
     * wrapper method for event onFocus<br />
     * sets the "focus" state of the image button
     *
     * @method setFocus
     * @protected
     */
  setFocus: function()
  {
    if(this.isDisabled()) return

    this.parent();
    if(!this.a.hasClass("P")) this.setState("F");
  },

  /**
     * wrapper method for event onBlur<br />
     * clears the "focus" state of the image button
     *
     * @method removeFocus
     * @protected
     */
  removeFocus: function()
  {
    if(this.isDisabled()) return

    this.parent();
    this.clearState();
  },

  /**
     * wrapper method for event onPress<br />
     * sets the "pressed" state of the image button
     *
     * @method press
     * @protected
     */
  press: function()
  {
    if(this.isDisabled()) return

    this.parent();
    this.setState("P");
  },

  /**
     * wrapper method for event onRelease<br />
     * clears the "pressed" state of the image button
     *
     * @method release
     * @protected
     */
  release: function()
  {
    if(this.isDisabled()) return

    this.parent();
        this.clearState();
    if(this.a.hasClass("F")) this.setState("F");
  },

  /**
     * wrapper method for event onEnable<br />
     *
     * @method enable
     * @protected
     */
  enable: function()
  {
    this.parent();
    this.clearState();
  },

  /**
     * wrapper method for event onDisable<br />
     *
     * @method disable
     * @protected
     */
  disable: function()
  {
    this.parent();
    this.setState("D");
  }
});/**
 * @module Text
 */

/**
 * Replacement for elements of type: input[type=password]
 *
 * <h6>Tested in:</h6>
 * <p>See cfe.module.Text</p>
 *
 * @class Password
 * @namespace cfe.module
 *
 * @extends cfe.module.Text
 *
 */
cfe.module.Password = new Class({
    
    Extends: cfe.module.Text,
    
    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=password]",

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "password"
     */
    createOriginal: function()
    {
        return new Element("input").set("type", "password");
    }
});/**
 * @module Checkable
 */

/**
 * <p><strong>replaces radiobuttons</strong></p>
 *
 * <h6>Tested in:</h6>
 * <ul>
 * <li>Safari 4.</li>
 * <li>Firefox 3.6.</li>
 * <li>Google Chrome 6.</li>
 * <li>Opera 10.62.</li>
 * <li>IE 7.</li>
 *
 *  <li>IE 8
 *    <ul>
 *      <li>labels dont work for normal labelled elements</li>
 *    </ul>
 *  </li>
 *
 *  </ul>
 *
 * @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));
        })
    }
});/**
 * @module Button
 */

/**
 * Provides replacement for input[type=reset]
 *
 * <h6>Tested in:</h6>
 * <p>See cfe.module.Submit PLUS</p>
 * <ul>
 *   <li>Safari 4</li>
 *   <li>Firefox 3.6.</li>
 *   <li>Google Chrome 6.</li>
 *   <li>Opera 10.62.</li>
 *   <li>IE 7.</li>
 *   <li>IE 8.</li>
 * </ul>
 *
 * @class Reset
 * @namespace cfe.module
 *
 * @extends cfe.module.Submit
 *
 * @constructor
 *
 * bug: - press then click outside > press state doesn't clear
 */
cfe.module.Reset = new Class({

    Extends: cfe.module.Submit,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "input[type=reset]",

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} an input field of type "reset"
     */
    createOriginal: function()
    {
        return new Element("input",{
            type: "reset"
        });
    },

    /**
     * adds an additional click event to the button to procede a form's reset
     *
     * @method setupOriginal
     */
    setupOriginal: function()
    {
        this.parent();
        this.o.form.addEvent("reset", function(){
            this.getElements("input, textarea, select").fireEvent("change", [], 40);
        });
    }
});/**
 * @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 Text
 */

/**
 * replaces textarea
 *
 * <h6>Tested in:</h6>
 * <p>See cfe.module.Text PLUS</p>
 * <ul>
 * <li>IE 8
 *    <ul>
 *      <li>original textarea overlaps bottom of sldiding doors by a pixel</li>
 *    </ul>
 *  </li>
 *
 *
 *  </ul>
 * @class Teaxtarea
 * @namespace cfe.module
 *
 * @extends cfe.module.Text
 *
 */
cfe.module.Textarea = new Class({

    Extends: cfe.module.Text,
    
    instance: 0,

    /**
     * CSS Selector to fetch "original" HTMLElements for replacement with this module
     * @property selector
     * @type string
     */
    selector: "textarea",

    /**
     * @inherit
     */
    options: {
        /**
         * if > 0, it will create markup for css sliding doors tech<br />
         * the number defines the amount of wrappers to create around this element<br />
         * 2: standard sliding doors (x- or y-Axis)<br />
         * 4: sliding doors in all directions (x/y-Axis)
         *
         * @config slidingDoors
         * @type int
         * @default 4
         */
        slidingDoors: 4
    },

    /**
     * Method to programmatically create an "original" HTMLElement
     *
     * @method createOriginal
     * @return {HTMLElement} a textarea element
     */
    createOriginal: function()
    {
        return new Element("textarea");
    }
});
