﻿/*!
* Sexy jQuery Plugin
*/
(function($) {
    $.fn.sexy = function(a1, a2, a3) {

        if (a1 == "option") {
            var ret = undefined;
            if (a2) {
                this.each(function() {
                    var sexyObject = jQuery(this).data("sexy");
                    if (sexyObject) {
                        if (a3 !== undefined) {
                            sexyObject.setProperty(a2, a3);
                        } else {
                            ret = sexyObject.getProperty(a2);
                            return false;
                        }
                    }
                });
            }
            return ret;
        } else if (a1 == "addOptions") {
            if (a2) {
                this.each(function() {
                    var sexyObject = jQuery(this).data("sexy");
                    if (sexyObject.isSelect) {
                        if (a2 instanceof Array) {
                            sexyObject.sexySelect.addOptions(a2);
                        }
                        else
                            if (a2.text && a2.value) {
                            sexyObject.sexySelect.addOption(a2);
                        }
                    }
                });
            }
            return this;
        } else if (a1 == "removeOptions") {
            if (a2) {
                this.each(function() {
                    var sexyObject = jQuery(this).data("sexy");
                    if (sexyObject.isSelect) {
                        if (a2 instanceof Array) {
                            sexyObject.sexySelect.removeOptions(a2);
                        }
                        else {
                            sexyObject.sexySelect.removeOption(a2);
                        }
                    }
                });
            }
            return this;
        } else {
            this.each(function() {
                var x = jQuery(this);
                x.isSexy = true;
                //create and store the sexy object
                x.data("sexy", new Sexy(x, a1));
            });
            return this;
        }
    }
    $.fn.sexyVal = function(value) {

        if (value) {
            this.each(function() {
                var sexy = jQuery(this).data("sexy");
                if (sexy) {
                    if (sexy.isSelect) {
                        sexy.sexySelect.selectValue(value);
                    }
                }
            });
        } else {
            if (this.length > 0) {
                var sexy = this.eq(0).data("sexy");
                if (sexy) {
                    if (sexy.isSelect) {
                        var option = sexy.sexySelect.selectedOption;
                        return { text: option.text, value: option.value };
                    }
                } else {
                    var _this = this.eq(0);
                    if (_this.hasClass("ui-sexyList")) {
                        var result = [];
                        $("div.ui-sexyList-canvas .ui-sexyList-item", _this).each(function() {
                            result.push({ text: $(this).html(), value: $(this).attr("valueId") });
                        });
                        return result;
                    }
                }
            }
        }
    }
})(jQuery);

function Sexy(element, options) {

    if (options) {
        options = jQuery.extend(new Sexy.DefaultSettings(), options);
    } else {
        options = jQuery.extend(new Sexy.DefaultSettings(), {});
    }

    this.settings = options;
    this.initialElement = element instanceof jQuery ? element : jQuery(element);
    this.fieldState = Sexy.FieldState.NONE;
    var tagName = this.initialElement.get(0).tagName;
    this.isSelect = tagName == "select" || tagName == "SELECT";
    this.isInput = tagName == "input" || tagName == "INPUT";
    this.sexySelect = null;
    this.tf = this.isInput ? this.initialElement.addClass("sexy-tf").attr("autocomplete", "off") : jQuery(document.createElement("input")).addClass("sexy-tf").attr("autocomplete", "off");
    var fieldType = this.isSelect ? "sexy-select" : "sexy-text";
    if (this.initialElement.attr("type") == "password") {
        if (!this.isInput) {
            this.tf.attr("type", "password");
        }
        fieldType = "sexy-password";
    } else {
        if (!this.isInput) {
            this.tf.attr("text");
        }
    }

    this.hasFocus = false;
    //check if a focus event occured or not. if it occured, then 
    //stop the default behaviour of mousedown so the text in text field remains selected
    this._focused = false;

    var sThis = this;
    this.tf.focus(function() {
        sThis.hasFocus = true;
    }).blur(function() {
        sThis.hasFocus = false;
        sThis._focused = false;
    });

    this.tf.mousedown(function(event) {
        event.stopPropagation();
    }).mouseup(function(event) {
        event.stopPropagation();
        if (sThis.hasFocus && !sThis._focused) {
            event.preventDefault();
            sThis._focused = true;
        }
    });

    this.bt = jQuery(document.createElement("div")).addClass("sexy-bt").css("width", options.buttonWidth + "px");
    this.container = jQuery(document.createElement("div")).addClass("sexy-field-container " + fieldType);
    this.initialElement.after(this.container);
    this.container.append(this.tf).append(this.bt);

    //add button tooltip
    this.setButtonTooltip(this.settings.buttonTooltip);

    if (!this.isInput) {
        //do this only if we are not using the initial element itself to get input
        transferAttributes(this.initialElement, this.tf, "class, id", "_sexyf"); //may add name as well, but sends the content of the form element to the server side on submit
        transferAttributes(this.initialElement, this.tf, "disabled, readonly");
        if (this.initialElement.attr("readonly") || this.initialElement.hasClass("readonly")) { this.settings.autocomplete = false; }
        if (this.initialElement.hasClass("autocomplete")) { this.settings.autocomplete = true; }
        this.initialElement.css("display", "none");
    }

    var h = this.tf.outerHeight();
    var ih = this.tf.innerHeight();
    var w = this.tf.outerWidth();
    if( w == 0 ) { //if the element has display "none" or is not visible, outerWidth won't work so have to do manually
        w = parseInt( this.tf.css( "width" ) ) + 
            parseInt( this.tf.css( "padding-left" ) ) + 
            parseInt( this.tf.css( "padding-right" ) ) + 
            parseInt( this.tf.css( "border-left-width" )) + 
            parseInt( this.tf.css( "border-right-width" ) );
    }
    
    if (h == 0) {
        h = this.settings.defaultTFSize.height;
    }
    if (w == 0) {
        w = this.settings.defaultTFSize.width;
    }
    var buttonWidth = 0;
    if (typeof options.buttonWidth == "string") {
        var r = parseFloat(options.buttonWidth);
        buttonWidth = h * r;
    } else {
        buttonWidth = options.buttonWidth;
    }

    this.bt.width(buttonWidth).height(h);
    this.container.width(w + (this.bt.outerWidth() == 0 ? (buttonWidth + 1) : this.bt.outerWidth())).height(h);

    if (this.isSelect) {
        this.sexySelect = new Sexy.Select(this);
        this.bt.addClass("sexy-select-bt").addClass("sexy-bt-dropdown-closed");
        var _this = this;
        this.bt.click(function(event) {
            if (!_this.sexySelect.isDropdownAnimInProgress) {
                if (_this.sexySelect.isDropdownShowing) {
                    _this.sexySelect._blurHandler();
                    _this.sexySelect.hideDropdown();
                    _this.bt.removeClass("sexy-bt-dropdown-open").addClass("sexy-bt-dropdown-closed");
                } else {
                    if (_this.sexySelect.useDataSource()) {
                        _this.sexySelect.getAjaxOptions("", function() {
                            _this.sexySelect.insertAllOptions();
                            _this.sexySelect.displayDropdown();
                            _this.bt.removeClass("sexy-bt-dropdown-closed").addClass("sexy-bt-dropdown-open");
                        });
                    } else {
                        _this.sexySelect.insertAllOptions();
                        _this.sexySelect.displayDropdown();
                        _this.bt.removeClass("sexy-bt-dropdown-closed").addClass("sexy-bt-dropdown-open");
                    }
                }
            }
            event.stopPropagation();
        });
    } else {
        if (this.settings.validateOnBlur) {
            this.tf.blur(function() { sThis.validate(); });
        }
        if (this.settings.validateDuringTyping) {
            var _vTimeoutId = null;
            this.tf.keyup(function() {
                if (_vTimeoutId) {
                    clearTimeout(_vTimeoutId);
                }
                _vTimeoutId = setTimeout(function() { sThis.validate(); }, sThis.settings.validationDelay);
            });
        }
    }

    //allow access to the sexy object from the text field
    this.tf.data("sexyBase", this);
    if (this.sexySelect) {
        this.tf.data("sexySelect", this.sexySelect);
    }

    //after the new textfield is inserted in the DOM attach all event handlers (if we are not using the initial element itself)

    if (!this.isInput) {
        var events = jQuery.data(this.initialElement.get(0)).events;
        for (var type in events) {
            if (type != undefined) {
                for (var i in events[type]) {
                    if (i != undefined) {
                        if (type == "change" && this.isSelect) {
                            //this.initialElement.bind( type, events[type][i].handler);
                            //do nothing cause the event is already bound to the initial element
                            //and that's exactly where we want it to be
                        }
                        else {
                            this.tf.bind(type, events[type][i].handler);
                        }
                    }
                }
            }
        }
    }

    this.validate();
}

//getter or setter: based on the params passed
Sexy.prototype.getProperty = function(propertyName) {
    for (var s in this.settings) {
        if (s == propertyName && s != "undefined") {
            return this.settings[s];
        }
    }
}

Sexy.prototype.setProperty = function(propertyName, propertyValue) {
    for (var s in this.settings) {
        if (s == propertyName && s != "undefined") {
            this.settings[s] = propertyValue;
        }
    }
}

Sexy.prototype.setButtonTooltip = function(tooltip) {
    this.settings.buttonTooltip = tooltip;
    if (tooltip && tooltip != "") {
        this.bt.attr("title", tooltip);
    } else {
        this.bt.removeAttr("title");
    }
}

Sexy.prototype.validate = function() {
    if (this.settings.validator) {
        var result = this.settings.validator(this.initialElement);
        if (result == 1) {
            this.fieldState = Sexy.FieldState.VALID;
        } else if (result == 0) {
            this.fieldState = Sexy.FieldState.INVALID;
        } else if (result == 2) {
            this.fieldState = Sexy.FieldState.REQUIRED;
        } else if (result == -1) {
            this.fieldState = Sexy.FieldState.NONE;
        }
    }

    this.applyFieldState();
}

Sexy.prototype.applyFieldState = function() {
    this.bt.removeAttr("class").addClass("sexy-bt");
    if (this.isSelect) {
        this.bt.addClass("sexy-select-bt");
        if (this.sexySelect) {
            if (this.sexySelect.isDropdownShowing) {
                this.bt.addClass("sexy-bt-dropdown-open");
            } else {
                this.bt.addClass("sexy-bt-dropdown-closed");
            }
        }
    }
    switch (this.fieldState) {
        case Sexy.FieldState.VALID: this.bt.addClass("valid"); break;
        case Sexy.FieldState.INVALID: this.bt.addClass("invalid"); break;
        case Sexy.FieldState.REQUIRED: this.bt.addClass("required"); break;
        default: break;
    }
}

Sexy.FieldState = { VALID: 1, INVALID: 2, REQUIRED: 3, NONE: 0 };

Sexy.DefaultSettings = function() {
    this.buttonWidth = "1";
    this.buttonTooltip = "";
    this.defaultTFSize = { width: 226, height: 21 };
    this.openAnimProperties = { height: "show" };
    this.closeAnimProperties = { height: "hide" };
    this.openAnimOptions = { duration: 200 };
    this.closeAnimOptions = { duration: 200 };

    this.dropdownOpened = null;
    this.dropdownClosed = null;

    this.validator = function(element) { return -1; };
    this.validateDuringTyping = true;
    this.validateOnBlur = true;
    this.validationDelay = 400;
    this.dataMode = "auto";
    this.dataSource = null;
    this.dataType = "json";

    this.selectOptionOnClick = true;
    this.optionRenderer = "default";
    this.zIndex = "auto";

    this.defaultOption = "auto";
    this.defaultOptionSelectable = true;
    this.autocomplete = false;
    this.autocompleteOnMultiselect = true;
    this.autcompleteMinLength = 2;
    this.autocompleteDelay = 200;
    this.autocompleteConstrain = true;
    this.clearOnDefaultOption = true;
    this.clearOnSelect = false;
    this.autocompleteOptionComparer = "default";
    this.optionAcceptThreshold = 1.0;
    this.highlightMatchSegments = true;
    
    this.multiOptionContainer = null;
    this.multiOptionContainerParent = null;
    this.multiOptionAddHandler = "auto";
}

//this will basically inherit all properties from the sexy object
Sexy.Select = function(sexyObject) {
    this.dropdown = jQuery(document.createElement("div")).addClass("sexy-dropdown")
		.css("display", "none").css("position", "absolute").insertAfter(sexyObject.container);
    this.sexy = sexyObject;
    this.isDropdownShowing = false;
    this.isDropdownAnimInProgress = false;

    this.localOptions = [];
    this.ajaxOptions = [];
    this.dOpenAnimOptions = {};
    this.dCloseAnimOptions = {};
    this.selectedOption = { text: "", value: "", element: null };
    this.isMultiselect = sexyObject.initialElement.attr( "multiple" );
    if( this.isMultiselect ) {
        if( this.sexy.settings.multiOptionContainer == null ) {
            this.sexy.settings.multiOptionContainer = this.createOptionContainer();
            if( sexyObject.multiOptionContainerParent != null ) {
                sexyObject.multiOptionContainerParent.append( this.sexy.settings.multiOptionContainer );    
            } else {
                sexyObject.container.after( this.sexy.settings.multiOptionContainer );
            }
        }
    }
    //helper flag
    this.isFirstSelection = true;
    if( this.sexy.initialElement.attr( "clearOnSelect" ) ) {
        this.sexy.settings.clearOnSelect = true;
    }
    
    //will be initialized after the _getLocalOptions is called
    this.defaultOption = {};
    if ((typeof this.sexy.settings.defaultOption) != "string") {
        jQuery.extend(this.sexy.settings.defaultOption, {});
        this.defaultOption.element = (this.getOptionRenderer())(this.defaultOption);
    }

    var sThis = this;
    if (!this.sexy.settings.autocomplete && !(this.sexy.settings.autocompleteOnMultiselect && this.isMultiselect)) {
        this.sexy.tf.attr("readonly", "true").addClass("readonly").noSelect().css("cursor", "pointer");
        this.sexy.tf.click(function() {
            if (!sThis.isDropdownShowing) {
                sThis.displayDropdown();
            } else {
                sThis.hideDropdown();
            }
        });
    } else {
        if (this.sexy.settings.clearOnDefaultOption) {
            //set up the clear text when the default option is selected
            this.sexy.tf.focus(function(event) {
                if (sThis.sexy.tf.attr("value") == sThis.defaultOption.text) {
                    sThis.sexy.tf.attr("value", "");
                } else {
                    sThis.sexy.tf.select();
                    event.preventDefault();
                    event.stopPropagation();
                }

                if (_sexySelectRegister) {
                    for (var i = 0; i < _sexySelectRegister.length; i++) {
                        if (_sexySelectRegister[i] !== sThis) {
                            _sexySelectRegister[i]._blurHandler();
                        }
                    }
                }
            });
        }

        this._keyupTimeoutId = null;
        this.sexy.tf.keyup(function(event) {
            if (sThis._preventKeyUp) {
                sThis._preventKeyUp = false;
                event.stopPropagation();
                event.preventDefault();
            } else {
                if (sThis._keyupTimeoutId) {
                    clearTimeout(sThis._keyupTimeoutId);
                }
                sThis._keyupTimeoutId = setTimeout(function() {
                    if (sThis.useDataSource()) {
                        sThis.getAjaxOptions(sThis.sexy.tf.val(), function() { sThis._keyTypedHandler(); });
                    } else {
                        sThis._keyTypedHandler();
                    }
                }, sThis.sexy.settings.autocompleteDelay);
            }
        });
        this.sexy.tf.keydown(function(event) {
            sThis.functionalKeyHandler(event);
        });
    }

    //this handler will update the sexy side if the 
    //initial select element changes outside of the sexy environment
    this.sexy.initialElement.change(function() {
        if (sThis.sexy.initialElement.data("sexyChanged")) {
            sThis.sexy.initialElement.data("sexyChanged", false);
            return;
        }
        var optVal = sThis.sexy.initialElement.val();
        //Select the value, ut also prevent changing the initial element again,
        //otherwise we end up with infinite circular calls
        sThis.selectValue(optVal, true);
    });

    this._bodyClickHandler = function(event) {
        if (!sThis._pointWithinBounds(event.pageX, event.pageY)) {
            sThis.hideDropdown();
            sThis._blurHandler();
        }
    }
    this._repositionHandler = function() {

        //always update left component to keep alignment
        var left = sThis.sexy.container.position().left + parseInt(sThis.sexy.container.css("margin-left"));
        sThis.dropdown.css("left", left);

        window.clearTimeout(_resizeTimeoutId);
        _resizeTimeoutId = setTimeout(function() { sThis.positionDropdown(); }, 500);
    }
    this._blur = function() {
        sThis._blurHandler();
    }
    this._windowKeyHandler = function(event) {
        sThis.functionalKeyHandler(event);
    }

    //other stuff
    this._posDecisionBias = 10;

    //init methods
    if (!this.sexy.settings.dataSource) {
        //if there is no data source declared, then check if a dataSource
        //attribute is declared on the initial select
        var ds = this.sexy.initialElement.attr("dataSource");
        if (ds) {
            this.sexy.settings.dataSource = ds;
        }
    }
    this._getLocalOptions();
    this.selectDefaultOption();
    this.highlightedOption = null;
    this.allOptionsInserted = true;

    //misc
    this._isChanged = false;
    //after a functional key is pressed we don't want it to trigger a 
    //key up event in some cases
    this._preventKeyUp = false;
    _sexySelectRegister.push(this);
    this.enableBlurHandlerOnBlur(true);
}

function getTotalScrollTop(startElement) {
    var scroll = 0;
    if (!startElement) {
        return 0;
    }

    var elem = startElement.parent();
    while (elem) {
        scroll += elem.scrollTop();
        elem = elem.parent();
        if (elem.get(0) === document.body) {
            //end iteration
            elem = null;
        }
    }

    return scroll;
}

function getScrollContainer(dropdown) {
    var elem = dropdown.parent();
    while (elem) {
        if (elem.css("overflow-x") == "auto" || elem.css("overflow-y") == "auto") {
            break;
        }
        elem = elem.parent();
        if (elem.get(0) === document.body) {
            //end iteration
            elem = null;
        }
    }
    return elem;
}


Sexy.Select.prototype.positionDropdown = function() {
    //first resest the dropdown height to auto so we can detect
    //the necessary height to fully render it
    var height = "auto";
    this.dropdown.css("height", height);

    var wh = jQuery(window).height();
    var mTop = this.sexy.container.css("margin-top");
    if (mTop == "auto") { mTop = 0; } else {
        mTop = parseInt(mTop);
    }
    var mLeft = this.sexy.container.css("margin-left");
    if (mLeft == "auto") { mLeft = 0; } else {
        var mLeft = parseInt(mLeft);
    }
    var cHeight = this.sexy.container.outerHeight();
    var top = this.sexy.container.position().top + cHeight + mTop;
    var otop = this.sexy.container.offset().top;
    var ootop = otop; //original otop (offset top)
    //var scrollTop = getTotalScrollTop( this.dropdown );
    var scrollContainer = getScrollContainer(this.dropdown);
    /* only enable this if scroll needs to be taken nto consideration
    if (scrollContainer) {
        top = top + scrollContainer.scrollTop();
        otop -= scrollContainer.offset().top;
        //possible add for left the same
    }*/

    var dropdownHeight = this.dropdown.outerHeight();
    if (otop + cHeight + dropdownHeight + this._posDecisionBias > wh) {
        if (dropdownHeight > otop - this._posDecisionBias) {
            //in this case, the dropdown does not fit neither below the container nor above
            //so it must be shrunk
            var d1 = wh - ootop - cHeight - dropdownHeight; //diference between bottom & dropdown
            var d2 = otop - dropdownHeight; //difference between top & dropdown
            if (d1 >= d2) {
                height = dropdownHeight + Math.min((d1 - this._posDecisionBias), 0);
            } else {
                height = dropdownHeight + Math.min((d2 - this._posDecisionBias), 0);
                top = top - cHeight - height - 1;
            }
        } else {
            //if there is no space below but there is above, then place the dropdown above the select container
            top = top - cHeight - dropdownHeight;
        }
    }

    var zIndex = this.sexy.settings.zIndex;
    if (zIndex == "auto") {
        zIndex = this.sexy.container.css("z-index");
        if (zIndex != "auto") {
            zIndex = parseInt(zIndex) + 1;
        } else {
            zIndex = 1;
        }
    }

    this.dropdown.css("z-index", zIndex);
    this.dropdown.css("top", top).width(this.sexy.container.innerWidth()).css("height", height);

    //after height and top components are updated, only then update the left component
    var left = this.sexy.container.position().left + mLeft;
    this.dropdown.css("left", left);
}

Sexy.Select.prototype.displayDropdown = function() {

    if (_currentlyVisibleDropdown && _currentlyVisibleDropdown.isDropdownShowing && !_currentlyVisibleDropdown.isDropdownAnimInProgress) {
        _currentlyVisibleDropdown.hideDropdown();
    }

    this.isDropdownAnimInProgress = true;
    this.enableBlurHandlerOnBlur(false);
    this.positionDropdown();

    //remember to compute dimensions of dropdown and see if it makes the window

    var _this = this;
    //highlight first option
    this.highlightOption(this.getNextHighlightOption());

    //use a new object each time to avoid infinite recursion by jquery
    var animOptions = jQuery.extend({}, this.sexy.settings.openAnimOptions);
    animOptions.complete = function() {
        _this.isDropdownAnimInProgress = false;
        _this.isDropdownShowing = true;
        _currentlyVisibleDropdown = _this;
        jQuery(window).bind("resize", _this._repositionHandler)
						.bind("mousedown", _this._bodyClickHandler)
						.bind("keydown", _this._windowKeyHandler);
    }
    this.dropdown.animate(this.sexy.settings.openAnimProperties, animOptions);
}

Sexy.Select.prototype.hideDropdown = function() {
    this.isDropdownAnimInProgress = true;
    var _this = this;
    jQuery(window).unbind("mousedown", _this._bodyClickHandler).unbind("keydown", _this._windowKeyHandler);
    var animOptions = jQuery.extend({}, this.sexy.settings.closeAnimOptions);
    animOptions.complete = function() {
        _this.isDropdownAnimInProgress = false;
        _this.isDropdownShowing = false;
        jQuery(window).unbind("resize", _this._repositionHandler);
        _this.enableBlurHandlerOnBlur(true);
        _this.highlightOption(null);
    }
    this.dropdown.animate(this.sexy.settings.closeAnimProperties, animOptions);
}

Sexy.Select.prototype.removeOption = function(option, ajaxOption) {
    var eqTest = option.value ? function(opt) { return opt.value == option.value } : function(opt) { return opt.value == option };
    if (ajaxOption) {
        arrRemove(this.ajaxOptions, eqTest);
    } else {
        arrRemove(this.localOptions, eqTest);
    }

    //also remove from dropdown Menu
    jQuery(".sexy-dropdown-option[value=" + (option.value ? option.value : option) + "]", this.dropdown).remove();
}

//insert all options based on the dataMode specified in settings
Sexy.Select.prototype.insertAllOptions = function() {
    if (this.allOptionsInserted) {
        return;
    }

    //first clear dropdown
    this.dropdown.empty();
    var sThis = this;

    if (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "local") {
        for (var i = 0; i < this.localOptions.length; i++) {
            var opt = this.localOptions[i].element;
            this.highlightOptionSegment(opt);
            opt.data("optionBase", this.localOptions[i]);
            this.dropdown.append(opt);
            if (this.sexy.settings.selectOptionOnClick) {
                opt.click(function(event) {
                    var _this = jQuery(this);
                    sThis._optionClickHandler(_this, event);
                });
            }
        }
    }

    if (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "dataSource") {
        for (var i = 0; i < this.ajaxOptions.length; i++) {
            var opt = this.ajaxOptions[i].element;
            opt.data("optionBase", this.ajaxOptions[i]);
            this.dropdown.append(opt);
            if (this.sexy.settings.selectOptionOnClick) {
                opt.click(function(event) {
                    var _this = jQuery(this);
                    sThis._optionClickHandler(_this, event);
                });
            }
        }
    }

    this.allOptionsInserted = true;
}

Sexy.Select.prototype.addOption = function(option, ajaxOption) {
    var sThis = this;
    var opt = (this.getOptionRenderer())(option);
    option.element = opt;
    opt.data("optionBase", option);
    if (this.sexy.settings.selectOptionOnClick) {
        opt.click(function(event) {
            var _this = jQuery(this);
            sThis._optionClickHandler(_this, event);
        });
    }

    if (ajaxOption) {
        this.ajaxOptions.push(option);
        if (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "dataSource") {
            this.dropdown.append(opt);
        }
    } else {
        this.localOptions.push(option);
        if (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "local") {
            this.dropdown.append(opt);
        }
    }

    return option;
}

Sexy.Select.prototype.addOptions = function(options) {
    for (var i = 0; i < options.length; i++) {
        this.addOption(options[i]);
    }
}

Sexy.Select.prototype.removeOptions = function(options) {
    for (var i = 0; i < options.length; i++) {
        this.removeOption(options[i]);
    }
}

Sexy.Select.prototype.selectValue = function(value, preventInitialChange) {

    var option = null;

    for (var i = 0; i < this.localOptions.length; i++) {
        if (value == this.localOptions[i].value) {
            option = this.localOptions[i];
            break;
        }
    }
    if (!option) {
        for (var i = 0; i < this.ajaxOptions.length; i++) {
            if (value == this.ajaxOptions[i].value) {
                option = this.ajaxOptions[i];
                break;
            }
        }
    }

    if (!preventInitialChange) {
        preventInitialChange = false;
    }

    if (option) {
        this.selectOption(option, preventInitialChange);
    }
}

Sexy.Select.prototype.selectOption = function(option, preventInitialChange, isKeySelect) {
    var oldOption = this.selectedOption;
    this.selectedOption = option;
    this.sexy.tf.attr("value", option.text);
    this._isChanged = false;
    this.sexy.initialElement.data("value", option.value);
    this.sexy.initialElement.data("sexyChanged", true);

    if (!preventInitialChange) {
        //trigger change event here
        if (oldOption.value != option.value) {

            //if the option does not exist in the initial element, then added it so the val method will return the correct value
            if (jQuery("option[value=" + option.value + "]", this.sexy.initialElement).length == 0) {
                var newSelectOption = jQuery(document.createElement("option")).attr("value", option.value).html(option.text);
                this.sexy.initialElement.append( newSelectOption );
                if( this.isMultiselect ) {
                    newSelectOption.attr( "selected", "selected" );
                } else {
                    this.sexy.initialElement.attr( "value", option.value );  
                }
            } else {
                if( this.isMultiselect ) {
                    $( "option[value=" + option.value + "]", this.sexy.initialElement ).attr( "selected", "selected" );
                } else {
                    this.sexy.initialElement.attr( "value", option.value );  
                }
            }
            
            this.sexy.initialElement.change();
            
            if( this.isFirstSelection ) {
                this.isFirstSelection = false;
            } else {
                if( !option.isNeutral ) {
                    //for multiselects, need to add the value to a box:
                    if( this.isMultiselect ) {
                        var mo_handler = this.sexy.settings.multiOptionAddHandler;
                        if( mo_handler == "auto" ) {
                            this.defaultMultiOptionAddHandler( this.selectedOption );
                        } else if( jQuery.isFunction( mo_handler ) ) {
                            mo_handler( this.selectedOption );
                        }
                    }
                }
            }
        }
    }

    this.sexy.validate();
}

//mark an option for selection when the enter key is pressed
Sexy.Select.prototype.highlightOption = function(option) {
    if (this.highlightedOption) {
        this.highlightedOption.element.removeClass("highlighted");
    }

    this.highlightedOption = option;
    if (option) {
        option.element.addClass("highlighted");
    }
}

Sexy.Select.prototype.getNextHighlightOption = function() {
    if (this.highlightedOption) {
        var x = this.highlightedOption.element.next();
        if (x.length > 0) {
            return x.data("optionBase");
        } else {
            return this.highlightedOption;
        }
    } else {
        return jQuery(".sexy-dropdown-option:first", this.dropdown).data("optionBase");
    }
}

Sexy.Select.prototype.getPreviousHighlightOption = function() {
    if (this.highlightedOption) {
        var x = this.highlightedOption.element.prev();
        if (x.length > 0) {
            return x.data("optionBase");
        } else {
            return this.highlightedOption;
        }
    } else {
        return jQuery(".sexy-dropdown-oprion:last", this.dropdown).data("optionBase");
    }
}

Sexy.Select.prototype.selectDefaultOption = function() {
    this.selectOption(this.defaultOption);
}

Sexy.Select.prototype._getLocalOptions = function() {
    var sThis = this;
    //will be useful for later if there is no default option
    var firstOption = null;
    jQuery("option", this.sexy.initialElement).each(function(i) {
        var _this = jQuery(this);
        var option = {};
        option.value = _this.attr("value");
        option.text = _this.html();
        option.isNeutral = ( _this.attr( "neutral" ) && _this.attr( "neutral" ) != false ) ? true : false;
        if (i == 0) {
            firstOption = option;
        }
        if (_this.attr("selected")) {
            sThis.defaultOption.text = option.text;
            sThis.defaultOption.value = option.value;
            if (sThis.sexy.settings.defaultOptionSelectable) {
                sThis.element = sThis.addOption(option);
            }
        } else {
            sThis.addOption(option);
        }
    });

    if ((typeof sThis.defaultOption.value == "undefined")) {
        if (firstOption) {
            sThis.defaultOption.text = firstOption.text;
            sThis.defaultOption.value = firstOption.value;
            if (!sThis.sexy.settings.defaultOptionSelectable) {
                arrRemove(sThis.localOptions, function(item, i) { return i == 0; });
                jQuery(".sexy-dropdown-option:first", sThis.dropdown).remove();
            }
        } else {
            sThis.defaultOption.text = "";
            sThis.defaultOption.value = "";
        }
    }
}

Sexy.Select.prototype.defaultOptionRenderer = function(option) {
return jQuery(document.createElement("div")).addClass("sexy-dropdown-option").attr("value", option.value/*(option.value ? option.value : option.text)*/).html(option.text);
}

Sexy.Select.prototype.getOptionRenderer = function() {
    if (this.sexy.settings.optionRenderer && ((typeof this.sexy.settings.optionRedenderer) == "function")) {
        return this.sexy.setings.optionRenderer;
    } else {
        return this.defaultOptionRenderer;
    }
}

Sexy.Select.prototype.getOptionComparer = function() {
    if (typeof this.sexy.settings.autocompleteOptionComparer == "function") {
        return this.sexy.settings.autocompleteOptionComparer;
    } else {
        return this.defaultOptionComparer;
    }
}

Sexy.Select.prototype.getOptionAcceptThreshold = function() {
    if ((typeof this.sexy.settings.optionAcceptThreshold) == "number") {
        return this.sexy.settings.optionAcceptThreshold;
    } else {
        return 1.0;
    }
}

Sexy.Select.prototype.defaultOptionComparer = function(optionText, option) {

    if (optionText == option.text) {
        //return a value greater than 1 (100%) because we consider this to be 
        //the perfect match, i.e. a better match than case-insensitive comparisson
        //so that when we order the results this will show up first and the list
        return 1.1;
    }

    var optionText_lc = optionText.toLowerCase();
    var ot_lc = option.text.toLowerCase();

    if (optionText_lc == ot_lc) {
        return 1.0;
    }

    var pos = ot_lc.indexOf(optionText_lc);
    if (pos != -1) {
        var matchFactor = 0.0;
        if (pos == 0) {
            //shift match factor up a bit, meaning that options which start with this sequence
            //have a higher priority than those in which the sequence is encountered in the middle
            //note that the loss of precission will occur only if we match a string with an option text of 10000 characters 
            //which is extremely unlikely
            matchFactor = 0.00001;
        }

        matchFactor += optionText.length / option.text.length;
        return matchFactor;
    }


    return 0.0;
}

Sexy.Select.prototype.functionalKeyHandler = function(keyEvent) {
    var keyPressed = keyEvent.keyCode;
    if (keyPressed == 27) {
        //on escape blur the field and make default selection
        this._blurHandler();
        this._preventKeyUp = true;
        keyEvent.stopPropagation();
        keyEvent.preventDefault();
        this.sexy.tf.blur();
    } else if (keyPressed == 13) {
        if (!this.isDropdownShowing) {
            this.displayDropdown();
        }
        else
        //on enter select the currently highlighted option
            if (this.highlightedOption) {
            this.selectOption(this.highlightedOption, false, true );
            this._blurHandler( false, true );
        }

        this._preventKeyUp = true;
        keyEvent.stopPropagation();
        keyEvent.preventDefault();
    } else if (keyPressed == 38) {
        if (this.isDropdownShowing) {
            this.highlightOption(this.getPreviousHighlightOption());
            keyEvent.preventDefault();
            keyEvent.stopPropagation();
        } else {
            this.displayDropdown();
        }
        this._preventKeyUp = true;
    } else if (keyPressed == 40) {
        if (this.isDropdownShowing) {
            this.highlightOption(this.getNextHighlightOption());
            keyEvent.preventDefault();
            keyEvent.stopPropagation();
        } else {
            this._keyTypedHandler( null, true );
            //this.displayDropdown();
        }
        this._preventKeyUp = true;
    }
}

//run this code each time a char is typed
//use the persist parameter to skip the check on the number of chars entered
Sexy.Select.prototype._keyTypedHandler = function(event, persist) {

    //mark this sexy select as changed so the blur handler can do it's job.
    this._isChanged = true;

    var tfVal = this.sexy.tf.attr("value");
    if (tfVal.length < this.sexy.settings.autcompleteMinLength && !persist) {
        if (this.isDropdownShowing) {
            this.hideDropdown();
        }
        return;
    }

    var results = [];
    if (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "local") {
        for (var i = 0; i < this.localOptions.length; i++) {
            var factor = (this.getOptionComparer())(tfVal, this.localOptions[i]);
            if (factor > 0.0) {
                results.push({
                    option: this.localOptions[i],
                    factor: factor
                });
            }
        }
    }

    if (this.useDataSource()) {
        for (var i = 0; i < this.ajaxOptions.length; i++) {
            var factor = (this.getOptionComparer())(tfVal, this.ajaxOptions[i]);
            if (factor > 0.0) {
                results.push({ option: this.ajaxOptions[i], factor: factor });
            }
        }
    }

    if (results.length > 0) {
        results.sort(function(x, y) { return y.factor - x.factor });
        this.dropdown.empty();
        this.allOptionsInserted = false;
        for (var i = 0; i < results.length; i++) {
            if (results[i].option.element) {
                var opt = results[i].option.element;
                this.highlightOptionSegment(opt, tfVal);
                opt.data("optionBase", results[i].option);
                this.dropdown.append(opt);
                var sThis = this;
                if (this.sexy.settings.selectOptionOnClick) {
                    opt.click(function(event) {
                        var _this = jQuery(this);
                        sThis._optionClickHandler(_this, event);
                    });
                }
            }
        }
        this.highlightOption(null);

        if (!this.isDropdownShowing) {
            this.displayDropdown();
        } else {
            this.highlightOption(this.getNextHighlightOption());
            this.positionDropdown();
        }
    } else {
        if (this.isDropdownShowing) {
            this.hideDropdown();
        }
    }
}

Sexy.Select.prototype.useDataSource = function() {
    return (this.sexy.settings.dataMode == "auto" || this.sexy.settings.dataMode == "dataSource") && (this.sexy.settings.dataSource != undefined)
}

Sexy.Select.prototype.getAjaxOptions = function(autocompleteText, callback) {

    //empty the ajax options array; we want to replace the options we new ones
    this.ajaxOptions.splice(0, this.ajaxOptions.length);

    var sThis = this;
    var dataSource = this.sexy.settings.dataSource;
    if (!dataSource) {
        return;
    }
    if (typeof dataSource == "function") {
        var dataReceived = dataSource(autocompleteText);
        if (dataReceived) {
            for (var i = 0; i < dataReceived.length; i++) {
                this.addOption(dataReceived[i], true);
            }
        }
        return;
    }

    var url = null;
    var successCallback = null;
    var errorCallback = null;
    var dataType = "json";

    if (typeof dataSource == "string") {
        url = dataSource;
    } else if (typeof dataSource == "object") {
        url = dataSource.url;
        successCallback = dateSource.success ? dataSource.success : null;
        if (dataSource.success) {
            successCallback = dataSource.success;
        }
        if (dataSource.error) {
            errorCallback = dataSource.error;
        }
        if (dataSource.dataType) {
            dataType = dataSource.dataType;
        }
    }

    jQuery.ajax({
        url: url,
        data: { autocompleteText: autocompleteText },
        dataType: dataType,
        success: function(dataReceived) {
            var newData = successCallback ? successCallback(dataReceived) : dataReceived;
            if (newData) {
                for (var i = 0; i < newData.length; i++) {
                    sThis.addOption(newData[i], true);
                }
            }
            sThis.allOptionsInserted = false;
            if (callback) {
                callback();
            }
        },
        error: function(error) {
            if (errorCallback) {
                errorCallback(error);
            }
        }
    });
}

Sexy.Select.prototype.highlightOptionSegment = function(optionElement, optionText) {
    var s = optionElement.html();
    //first remove any previous text highlighting
    s = s.replace(/<span class="sexy-dropdown-option-highlight">([^<]*)<\/span>/g, "$1");

    if (optionText) {
        s = s.replace(new RegExp("(" + optionText + ")", "gi"), "<span class=\"sexy-dropdown-option-highlight\">$1</span>");
    }

    optionElement.html(s);
}

Sexy.Select.prototype._optionClickHandler = function(optionElement, event) {

    this.selectOption(optionElement.data("optionBase"));
    this.hideDropdown();

    event.stopPropagation();
}

Sexy.Select.prototype._blurHandler = function(event, isKeySelect) {
    clearTimeout(this._keyupTimeoutId);

    var tfValue = this.sexy.tf.attr("value");
    var option = this.checkValidOptionTyped(tfValue);
    if (option) {
        if( isKeySelect && this.sexy.settings.clearOnSelect ) {
            this.sexy.tf.attr( "value", "" );
            //this.selectOption({ text: "", value: "", element: null }, true, true);        
        } else {
            this.selectOption(option);
        }
    } else {
        if( isKeySelect && this.sexy.settings.clearOnSelect ) {
            return;
        } else {
            if (this.sexy.settings.autocompleteConstrain) {
                this.selectOption(this.selectedOption);
            } else {
                var sValue = tfValue;
                if (tfValue == this.defaultOption.text) {
                    sValue = this.defaultOption.value;
                }
                this.selectOption({ text: tfValue, value: sValue, element: null });
            }
        }
    }

    this.sexy.validate();

    if (this.isDropdownShowing && !this.isDropdownAnimInProgress) {
        this.hideDropdown();
    }

    this.isChanged = false;
}

Sexy.Select.prototype.enableBlurHandlerOnBlur = function(enable) {
    if (enable) {
        this.sexy.tf.bind("blur", this._blur);
    } else {
        this.sexy.tf.unbind("blur", this._blur);
    }
}

//performs a case insensitive string comparison of the text in the parameter with 
//the text of the options stored until a match is found. Return false if no match is found
Sexy.Select.prototype.checkValidOptionTyped = function(optionText) {
    for (var i = 0; i < this.localOptions.length; i++) {
        if ((this.getOptionComparer())(optionText, this.localOptions[i]) >= this.getOptionAcceptThreshold()) {
            return this.localOptions[i];
        }
    }

    for (var i = 0; i < this.ajaxOptions.length; i++) {
        if ((this.getOptionComparer())(optionText, this.ajaxOptions[i]) >= this.getOptionAcceptThreshold()) {
            return this.ajaxOptions[i];
        }
    }

    return false;
}

Sexy.Select.prototype._pointWithinBounds = function(x, y) {
    var bx = this.sexy.container.offset().left;
    var by = this.sexy.container.offset().top;
    var dy = this.dropdown.offset().top;
    var bxx = bx + this.sexy.container.outerWidth();
    var byy = by + this.sexy.container.outerHeight();
    if (dy > by) {
        byy += this.dropdown.outerHeight();
    } else {
        by -= this.dropdown.outerHeight();
    }

    return x >= bx && x <= bxx && y >= by && y <= byy;

    return
}

Sexy.Select.prototype.createOptionContainer = function() {
    return jQuery(document.createElement( "div" )).addClass( "sexy-select-option-container" ).append( $( "<br class=\"clearfloat\" />" ) );
}

Sexy.Select.prototype.defaultMultiOptionAddHandler = function( option ) {
    var cThis = this;
    jQuery(document.createElement( "span" )).addClass( "sexy-selected-option" )
        .attr( "value", option.value ).html( option.text )
        .append( jQuery(document.createElement("span")).html( "&nbsp;" ).addClass("ui-icon").addClass( "ui-icon-close" ) )
        .prependTo( this.sexy.settings.multiOptionContainer ).click( function() {
            var _this = $(this);
            //deselect this in the initial select element which will be sent as part of the form
            $( "option[value=" + option.value + "]", cThis.sexy.initialElement ).removeAttr( "selected" );
            _this.remove();
        });
}

Sexy.Select.prototype._setInitialMultiSelectOptions = function() {
    if( this.sexy.settings.multiOptionContainer ) {
        var values = [];
        $( ".sexy-selected-option", this.sexy.settings.multiOptionContainer ).each( function() {
            values.push( $(this).attr( "value" ) );
        });
        this.sexy.initialElement.val( values );
    }
}

/******** Helper Functions **************************************/

function transferAttributes(source, target, attrNames, suffix) {
    if (!attrNames) {
        attrNames = "id, name, class, value, readonly, disabled";
    }
    if (!suffix) {
        suffix = "";
    }

    var attrs = attrNames.split(",");
    for (var i = 0; i < attrs.length; i++) {
        var att = trim(attrs[i]);
        if (att == "class") {
            var sClassesString = source.attr("className");
            if (sClassesString) {
                var sClasses = sClassesString.split(" ");
                for (var j = 0; j < sClasses.length; j++) {
                    target.addClass(sClasses[j]);
                }
            }
        } else {
            var attValue = source.attr(att);
            if (attValue) {
                target.attr(att, attValue + suffix);
            }
        }
    }

    return target;
}

/* A more sophisticated function for removing an element
from an array. It takes as paramters the array and 
either an equality predicate (a filter function which
determines which elements should be removed from the 
array) or the element itself for direct equality 
testing. The predicate function receives as parameters
both the value and the index of each element in the enumeration.
The function is inherently efficient - 
does not create any auxiliary arrays and it traverses
the array once only.
*/
function arrRemove(arr, eqPredicate) {
    if (!arr || !eqPredicate) {
        return;
    }
    //first we filter out the elements of the array
    var j = 0;
    for (var i = 0; i < arr.length; i++) {
        var eqTest = isFunction(eqPredicate) ? eqPredicate(arr[i], i) : arr[i] == eqPredicate;
        //leave all the elements which do not pass the equality test in the array
        if (!eqTest) {
            arr[j++] = arr[i];
        }
    }

    //and then truncate the array using the length property
    arr.length = j;

    return arr;
}

//util method
function isFunction(f) {
    return typeof f == "function";
}

//global utility vars
var _resizeTimeoutId;
var _currentlyVisibleDropdown;
var _sexySelectRegister = [];

/*!
* noSelect jQuery Plugin
* @link http://github.com/mathiasbynens/noSelect-jQuery-Plugin
* @author Mathias Bynens <http://mathiasbynens.be/>
*/
(function(a) { a.fn.noSelect = function() { function b() { return !1 } return a(this).each(function() { this.onselectstart = this.ondragstart = b; a(this).mousedown(b).css({ MozUserSelect: 'none' }) }) } })(jQuery);