/*
*
* Copyright (c) 2006-2009 Sam Collett (http://www.texotela.co.uk)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Version 2.2.4
* Demo: http://www.texotela.co.uk/code/jquery/select/
*
* $LastChangedDate$
* $Rev$
*
*/

; (function($) {

    /**
    * Adds (single/multiple) options to a select box (or series of select boxes)
    *
    * @name     addOption
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @example  $("#myselect").addOption("Value", "Text"); // add single value (will be selected)
    * @example  $("#myselect").addOption("Value 2", "Text 2", false); // add single value (won't be selected)
    * @example  $("#myselect").addOption({"foo":"bar","bar":"baz"}, false); // add multiple values, but don't select
    *
    */
    $.fn.addOption = function() {
        var add = function(el, v, t, sO) {
            var option = document.createElement("option");
            option.value = v, option.text = t;
            // get options
            var o = el.options;
            // get number of options
            var oL = o.length;
            if (!el.cache) {
                el.cache = {};
                // loop through existing options, adding to cache
                for (var i = 0; i < oL; i++) {
                    el.cache[o[i].value] = i;
                }
            }
            // add to cache if it isn't already
            if (typeof el.cache[v] == "undefined") el.cache[v] = oL;
            el.options[el.cache[v]] = option;
            if (sO) {
                option.selected = true;
            }
        };

        var a = arguments;
        if (a.length == 0) return this;
        // select option when added? default is true
        var sO = true;
        // multiple items
        var m = false;
        // other variables
        var items, v, t;
        if (typeof (a[0]) == "object") {
            m = true;
            items = a[0];
        }
        if (a.length >= 2) {
            if (typeof (a[1]) == "boolean") sO = a[1];
            else if (typeof (a[2]) == "boolean") sO = a[2];
            if (!m) {
                v = a[0];
                t = a[1];
            }
        }
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return;
		    if (m) {
		        for (var item in items) {
		            add(this, item, items[item], sO);
		        }
		    }
		    else {
		        add(this, v, t, sO);
		    }
		}
	);
        return this;
    };

    /**
    * Add options via ajax
    *
    * @name     ajaxAddOption
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @param    String url      Page to get options from (must be valid JSON)
    * @param    Object params   (optional) Any parameters to send with the request
    * @param    Boolean select  (optional) Select the added options, default true
    * @param    Function fn     (optional) Call this function with the select object as param after completion
    * @param    Array args      (optional) Array with params to pass to the function afterwards
    * @example  $("#myselect").ajaxAddOption("myoptions.php");
    * @example  $("#myselect").ajaxAddOption("myoptions.php", {"code" : "007"});
    * @example  $("#myselect").ajaxAddOption("myoptions.php", {"code" : "007"}, false, sortoptions, [{"dir": "desc"}]);
    *
    */
    $.fn.ajaxAddOption = function(url, params, select, fn, args) {
        if (typeof (url) != "string") return this;
        if (typeof (params) != "object") params = {};
        if (typeof (select) != "boolean") select = true;
        this.each(
		function() {
		    var el = this;
		    $.getJSON(url,
				params,
				function(r) {
				    $(el).addOption(r, select);
				    if (typeof fn == "function") {
				        if (typeof args == "object") {
				            fn.apply(el, args);
				        }
				        else {
				            fn.call(el);
				        }
				    }
				}
			);
		}
	);
        return this;
    };

    /**
    * Removes an option (by value or index) from a select box (or series of select boxes)
    *
    * @name     removeOption
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @param    String|RegExp|Number what  Option to remove
    * @param    Boolean selectedOnly       (optional) Remove only if it has been selected (default false)   
    * @example  $("#myselect").removeOption("Value"); // remove by value
    * @example  $("#myselect").removeOption(/^val/i); // remove options with a value starting with 'val'
    * @example  $("#myselect").removeOption(/./); // remove all options
    * @example  $("#myselect").removeOption(/./, true); // remove all options that have been selected
    * @example  $("#myselect").removeOption(0); // remove by index
    * @example  $("#myselect").removeOption(["myselect_1","myselect_2"]); // values contained in passed array
    *
    */
    $.fn.removeOption = function() {
        var a = arguments;
        if (a.length == 0) return this;
        var ta = typeof (a[0]);
        var v, index;
        // has to be a string or regular expression (object in IE, function in Firefox)
        if (ta == "string" || ta == "object" || ta == "function") {
            v = a[0];
            // if an array, remove items
            if (v.constructor == Array) {
                var l = v.length;
                for (var i = 0; i < l; i++) {
                    this.removeOption(v[i], a[1]);
                }
                return this;
            }
        }
        else if (ta == "number") index = a[0];
        else return this;
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return;
		    // clear cache
		    if (this.cache) this.cache = null;
		    // does the option need to be removed?
		    var remove = false;
		    // get options
		    var o = this.options;
		    if (!!v) {
		        // get number of options
		        var oL = o.length;
		        for (var i = oL - 1; i >= 0; i--) {
		            if (v.constructor == RegExp) {
		                if (o[i].value.match(v)) {
		                    remove = true;
		                }
		            }
		            else if (o[i].value == v) {
		                remove = true;
		            }
		            // if the option is only to be removed if selected
		            if (remove && a[1] === true) remove = o[i].selected;
		            if (remove) {
		                o[i] = null;
		            }
		            remove = false;
		        }
		    }
		    else {
		        // only remove if selected?
		        if (a[1] === true) {
		            remove = o[index].selected;
		        }
		        else {
		            remove = true;
		        }
		        if (remove) {
		            this.remove(index);
		        }
		    }
		}
	);
        return this;
    };

    /**
    * Sort options (ascending or descending) in a select box (or series of select boxes)
    *
    * @name     sortOptions
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @param    Boolean ascending   (optional) Sort ascending (true/undefined), or descending (false)
    * @example  // ascending
    * $("#myselect").sortOptions(); // or $("#myselect").sortOptions(true);
    * @example  // descending
    * $("#myselect").sortOptions(false);
    *
    */
    $.fn.sortOptions = function(ascending) {
        // get selected values first
        var sel = $(this).selectedValues();
        var a = typeof (ascending) == "undefined" ? true : !!ascending;
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return;
		    // get options
		    var o = this.options;
		    // get number of options
		    var oL = o.length;
		    // create an array for sorting
		    var sA = [];
		    // loop through options, adding to sort array
		    for (var i = 0; i < oL; i++) {
		        sA[i] = {
		            v: o[i].value,
		            t: o[i].text
		        }
		    }
		    // sort items in array
		    sA.sort(
				function(o1, o2) {
				    // option text is made lowercase for case insensitive sorting
				    o1t = o1.t.toLowerCase(), o2t = o2.t.toLowerCase();
				    // if options are the same, no sorting is needed
				    if (o1t == o2t) return 0;
				    if (a) {
				        return o1t < o2t ? -1 : 1;
				    }
				    else {
				        return o1t > o2t ? -1 : 1;
				    }
				}
			);
		    // change the options to match the sort array
		    for (var i = 0; i < oL; i++) {
		        o[i].text = sA[i].t;
		        o[i].value = sA[i].v;
		    }
		}
	).selectOptions(sel, true); // select values, clearing existing ones
        return this;
    };
    /**
    * Selects an option by value
    *
    * @name     selectOptions
    * @author   Mathias Bank (http://www.mathias-bank.de), original function
    * @author   Sam Collett (http://www.texotela.co.uk), addition of regular expression matching
    * @type     jQuery
    * @param    String|RegExp|Array value  Which options should be selected
    * can be a string or regular expression, or an array of strings / regular expressions
    * @param    Boolean clear  Clear existing selected options, default false
    * @example  $("#myselect").selectOptions("val1"); // with the value 'val1'
    * @example  $("#myselect").selectOptions(["val1","val2","val3"]); // with the values 'val1' 'val2' 'val3'
    * @example  $("#myselect").selectOptions(/^val/i); // with the value starting with 'val', case insensitive
    *
    */
    $.fn.selectOptions = function(value, clear) {
        var v = value;
        var vT = typeof (value);
        // handle arrays
        if (vT == "object" && v.constructor == Array) {
            var $this = this;
            $.each(v, function() {
                $this.selectOptions(this, clear);
            }
		);
        };
        var c = clear || false;
        // has to be a string or regular expression (object in IE, function in Firefox)
        if (vT != "string" && vT != "function" && vT != "object") return this;
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return this;
		    // get options
		    var o = this.options;
		    // get number of options
		    var oL = o.length;
		    for (var i = 0; i < oL; i++) {
		        if (v.constructor == RegExp) {
		            if (o[i].value.match(v)) {
		                o[i].selected = true;
		            }
		            else if (c) {
		                o[i].selected = false;
		            }
		        }
		        else {
		            if (o[i].value == v) {
		                o[i].selected = true;
		            }
		            else if (c) {
		                o[i].selected = false;
		            }
		        }
		    }
		}
	);
        return this;
    };

    /**
    * Copy options to another select
    *
    * @name     copyOptions
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @param    String to  Element to copy to
    * @param    String which  (optional) Specifies which options should be copied - 'all' or 'selected'. Default is 'selected'
    * @example  $("#myselect").copyOptions("#myselect2"); // copy selected options from 'myselect' to 'myselect2'
    * @example  $("#myselect").copyOptions("#myselect2","selected"); // same as above
    * @example  $("#myselect").copyOptions("#myselect2","all"); // copy all options from 'myselect' to 'myselect2'
    *
    */
    $.fn.copyOptions = function(to, which) {
        var w = which || "selected";
        if ($(to).size() == 0) return this;
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return this;
		    // get options
		    var o = this.options;
		    // get number of options
		    var oL = o.length;
		    for (var i = 0; i < oL; i++) {
		        if (w == "all" || (w == "selected" && o[i].selected)) {
		            $(to).addOption(o[i].value, o[i].text);
		        }
		    }
		}
	);
        return this;
    };

    /**
    * Checks if a select box has an option with the supplied value
    *
    * @name     containsOption
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     Boolean|jQuery
    * @param    String|RegExp value  Which value to check for. Can be a string or regular expression
    * @param    Function fn          (optional) Function to apply if an option with the given value is found.
    * Use this if you don't want to break the chaining
    * @example  if($("#myselect").containsOption("val1")) alert("Has an option with the value 'val1'");
    * @example  if($("#myselect").containsOption(/^val/i)) alert("Has an option with the value starting with 'val'");
    * @example  $("#myselect").containsOption("val1", copyoption).doSomethingElseWithSelect(); // calls copyoption (user defined function) for any options found, chain is continued
    *
    */
    $.fn.containsOption = function(value, fn) {
        var found = false;
        var v = value;
        var vT = typeof (v);
        var fT = typeof (fn);
        // has to be a string or regular expression (object in IE, function in Firefox)
        if (vT != "string" && vT != "function" && vT != "object") return fT == "function" ? this : found;
        this.each(
		function() {
		    if (this.nodeName.toLowerCase() != "select") return this;
		    // option already found
		    if (found && fT != "function") return false;
		    // get options
		    var o = this.options;
		    // get number of options
		    var oL = o.length;
		    for (var i = 0; i < oL; i++) {
		        if (v.constructor == RegExp) {
		            if (o[i].value.match(v)) {
		                found = true;
		                if (fT == "function") fn.call(o[i], i);
		            }
		        }
		        else {
		            if (o[i].value == v) {
		                found = true;
		                if (fT == "function") fn.call(o[i], i);
		            }
		        }
		    }
		}
	);
        return fT == "function" ? this : found;
    };

    /**
    * Returns values which have been selected
    *
    * @name     selectedValues
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     Array
    * @example  $("#myselect").selectedValues();
    *
    */
    $.fn.selectedValues = function() {
        var v = [];
        this.selectedOptions().each(
		function() {
		    v[v.length] = this.value;
		}
	);
        return v;
    };

    /**
    * Returns text which has been selected
    *
    * @name     selectedTexts
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     Array
    * @example  $("#myselect").selectedTexts();
    *
    */
    $.fn.selectedTexts = function() {
        var t = [];
        this.selectedOptions().each(
		function() {
		    t[t.length] = this.text;
		}
	);
        return t;
    };

    /**
    * Returns options which have been selected
    *
    * @name     selectedOptions
    * @author   Sam Collett (http://www.texotela.co.uk)
    * @type     jQuery
    * @example  $("#myselect").selectedOptions();
    *
    */
    $.fn.selectedOptions = function() {
        return this.find("option:selected");
    };

})(jQuery);
