/*
jQuery-SelectBox
    
Traditional select elements are very difficult to style by themselves, 
but they are also very usable and feature rich. This plugin attempts to 
recreate all selectbox functionality and appearance while adding 
animation and stylability.
    
This product includes software developed 
by RevSystems, Inc (http://www.revsystems.com/) and its contributors
    
Please see the accompanying LICENSE.txt for licensing information.
*/

(function($, window, undefined) {
    // utility functions
    $.fn.borderWidth = function() { return $wickedweb(this).outerWidth() - $wickedweb(this).innerWidth(); };
    $.fn.paddingWidth = function() { return $wickedweb(this).innerWidth() - $wickedweb(this).width(); };
    $.fn.extraWidth = function() { return $wickedweb(this).outerWidth(true) - $wickedweb(this).width(); };
    $.fn.offsetFrom = function(e) {
        var $e = $(e);
        return {
            left: $wickedweb(this).offset().left - $e.offset().left,
            top: $wickedweb(this).offset().top - $e.offset().top
        };
    };
    $.fn.maxWidth = function() {
        var max = 0;
        $wickedweb(this).each(function() {
            if ($wickedweb(this).width() > max) {
                max = $wickedweb(this).width();
            }
        });
        return max;
    };
    $.fn.triggerAll = function(event, params) {
        return $wickedweb(this).each(function() {
            $wickedweb(this).triggerHandler(event, params);
        });
    };
    var aps = Array.prototype.slice,
        randInt = function() {
            return Math.floor(Math.random() * 999999999);
        };

    // jQuery-Proto
    $.proto = function() {
        var name = arguments[0],    // The name of the jQuery function that will be called
            clazz = arguments[1],   // A reference to the class that you are associating
            klazz = clazz,          // A version of clazz with a delayed constructor
            extOpt = {},            // used to extend clazz with a variable name for the init function
            undefined;              // safety net

        opts = $.extend({
            elem: "elem",           // the property name on the object that will be set to the current jQuery context
            access: "access",       // the name of the access function to be set on the object
            init: "init",           // the name of the init function to be set on the object
            instantAccess: false    // when true, treat all args as access args (ignore constructor args) and allow construct/function call at the same time
        }, arguments[2]);

        if (clazz._super) {
            extOpt[opts.init] = function() { };
            klazz = clazz.extend(extOpt);
        }

        $.fn[name] = function() {
            var result, args = arguments;

            $wickedweb(this).each(function() {
                var $e = $wickedweb(this),
                    obj = $e.data(name),
                    isNew = !obj;

                // if the object is not defined for this element, then construct
                if (isNew) {

                    // create the new object and restore init if necessary
                    obj = new klazz();
                    if (clazz._super) {
                        obj[opts.init] = clazz.prototype.init;
                    }

                    // set the elem property and initialize the object
                    obj[opts.elem] = $e[0];
                    if (obj[opts.init]) {
                        obj[opts.init].apply(obj, opts.instantAccess ? [] : aps.call(args, 0));
                    }

                    // associate it with the element
                    $e.data(name, obj);

                }

                // if it is defined or we allow instance access, then access
                if (!isNew || opts.instantAccess) {

                    // call the access function if it exists (allows lazy loading)
                    if (obj[opts.access]) {
                        obj[opts.access].apply(obj, aps.call(args, 0));
                    }

                    // do something with the object
                    if (args.length > 0) {

                        if ($.isFunction(obj[args[0]])) {

                            // use the method access interface
                            result = obj[args[0]].apply(obj, aps.call(args, 1));

                        } else if (args.length === 1) {

                            // just retrieve the property (leverage deep access with getObject if we can)
                            if ($.getObject) {
                                result = $.getObject(args[0], obj);
                            } else {
                                result = obj[args[0]];
                            }

                        } else {

                            // set the property (leverage deep access with setObject if we can)
                            if ($.setObject) {
                                $.setObject(args[0], args[1], obj);
                            } else {
                                obj[args[0]] = args[1];
                            }

                        }

                    } else if (result === undefined) {

                        // return the first object if there are no args
                        result = $e.data(name);

                    }
                }
            });

            // chain if no results were returned from the clazz's method (it's a setter)
            if (result === undefined) {
                return $wickedweb(this);
            }

            // return the first result if not chaining
            return result;
        };
    };

    var falseFunc = function() {
        return false;
    },
        SelectBox = function() {

            var self = this,
            o = {},
            $orig = null,
            $label = null,
            $sb = null,
            $display = null,
            $dd = null,
            $items = null,
            searchTerm = "",
            cstTimeout = null,
            delayReloadTimeout = null,
            resizeTimeout = null,

            // functions
            loadSB,
            createOption,
            focusOrig,
            blurOrig,
            destroySB,
            reloadSB,
            delayReloadSB,
            openSB,
            centerOnSelected,
            closeSB,
            positionSB,
            positionSBIfOpen,
            delayPositionSB,
            clickSB,
            clickSBItem,
            keyupSB,
            keydownSB,
            focusSB,
            blurSB,
            addHoverState,
            removeHoverState,
            addActiveState,
            removeActiveState,
            getDDCtx,
            getSelected,
            getEnabled,
            selectItem,
            clearSearchTerm,
            findMatchingItem,
            selectMatchingItem,
            selectNextItemStartsWith,
            closeAll,
            closeAllButMe,
            closeAndUnbind,
            blurAllButMe,
            stopPageHotkeys,
            flickerDisplay,
            unbind;

            loadSB = function() {

                // create the new sb
                $sb = $("<div class='sb " + o.selectboxClass + " " + $orig.attr("class") + "' id='sb" + randInt() + "'></div>")
                .attr("role", "listbox")
                .attr("aria-has-popup", "true")
                .attr("aria-labelledby", $label.attr("id") ? $label.attr("id") : "");
                $("body").append($sb);

                // generate the display markup
                var displayMarkup = $orig.children().size() > 0
                ? o.displayFormat.call($orig.find("option:selected")[0], 0, 0)
                : "&nbsp;";
                $display = $("<div class='display " + $orig.attr("class") + "' id='sbd" + randInt() + "'></div>")
                .append($("<div class='text'></div>").append(displayMarkup))
                .append(o.arrowMarkup);
                $sb.append($display);

                // generate the dropdown markup
                $dd = $("<ul class='" + o.selectboxClass + " items " + $orig.attr("class") + "' role='menu' id='sbdd" + randInt() + "'></ul>")
                .attr("aria-hidden", "true");
                $sb.append($dd)
                .attr("aria-owns", $dd.attr("id"));
                if ($orig.children().size() === 0) {
                    $dd.append(createOption().addClass("selected"));
                } else {
                    $orig.children().each(function(i) {
                        var $opt, $og, $ogItem, $ogList;
                        if ($wickedweb(this).is("optgroup")) {
                            $og = $wickedweb(this);
                            $ogItem = $("<li class='optgroup'>" + o.optgroupFormat.call($og[0], i + 1) + "</li>")
                            .addClass($og.is(":disabled") ? "disabled" : "")
                            .attr("aria-disabled", $og.is(":disabled") ? "true" : "");
                            $ogList = $("<ul class='items'></ul>");
                            $ogItem.append($ogList);
                            $dd.append($ogItem);
                            $og.children("option").each(function() {
                                $opt = createOption($wickedweb(this), i)
                                .addClass($og.is(":disabled") ? "disabled" : "")
                                .attr("aria-disabled", $og.is(":disabled") ? "true" : "");
                                $ogList.append($opt);
                            });
                        } else {
                            $dd.append(createOption($wickedweb(this), i));
                        }
                    });
                }

                // cache all sb items
                $items = $dd.find("li").not(".optgroup");

                // for accessibility/styling
                $sb.attr("aria-active-descendant", $items.filter(".selected").attr("id"));
                $dd.children(":first").addClass("first");
                $dd.children(":last").addClass("last");

                // modify width based on fixedWidth/maxWidth options
                if (!o.fixedWidth) {
                    var largestWidth = $dd.find(".text, .optgroup").maxWidth() + $display.extraWidth() + 1;
                    $sb.width(o.maxWidth ? Math.min(o.maxWidth, largestWidth) : largestWidth);
                } else if (o.maxWidth && $sb.width() > o.maxWidth) {
                    $sb.width(o.maxWidth);
                }

                // place the new markup in its semantic location (hide/show fixes positioning bugs)
                $orig.before($sb).addClass("has_sb").hide().show();

                // these two lines fix a div/span display bug on load in ie7
                positionSB();
                flickerDisplay();

                // hide the dropdown now that it's initialized
                $dd.hide();

                // bind events
                if (!$orig.is(":disabled")) {
                    $orig
                    .bind("blur.sb", blurOrig)
                    .bind("focus.sb", focusOrig);
                    $display
                    .mouseup(addActiveState)
                    .mouseup(clickSB)
                    .click(falseFunc)
                    .focus(focusSB)
                    .blur(blurSB)
                    .hover(addHoverState, removeHoverState);
                    getEnabled()
                    .click(clickSBItem)
                    .hover(addHoverState, removeHoverState);
                    $dd.find(".optgroup")
                    .hover(addHoverState, removeHoverState)
                    .click(falseFunc);
                    $items.filter(".disabled")
                    .click(falseFunc);
                    if (!$.browser.msie || $.browser.version >= 9) {
                        $(window).resize($.throttle ? $.throttle(100, positionSBIfOpen) : delayPositionSB);
                    }
                } else {
                    $sb.addClass("disabled").attr("aria-disabled");
                    $display.click(function(e) { e.preventDefault(); });
                }

                // bind custom events
                $sb.bind("close.sb", closeSB).bind("destroy.sb", destroySB);
                $orig.bind("reload.sb", reloadSB);
                if ($.fn.tie && o.useTie) {
                    $orig.bind("domupdate.sb", delayReloadSB);
                }
            };

            delayPositionSB = function() {
                clearTimeout(resizeTimeout);
                resizeTimeout = setTimeout(positionSBIfOpen, 50);
            };

            positionSBIfOpen = function() {
                if ($sb.is(".open")) {
                    positionSB();
                    openSB(true);
                }
            }

            // create new markup from an <option>
            createOption = function($option, index) {
                if (!$option) {
                    $option = $("<option value=''>&nbsp;</option>");
                    index = 0;
                }
                var $li = $("<li id='sbo" + randInt() + "'></li>")
                    .attr("role", "option")
                    .data("orig", $option[0])
                    .data("value", $option ? $option.attr("value") : "")
                    .addClass($option.is(":selected") ? "selected" : "")
                    .addClass($option.is(":disabled") ? "disabled" : "")
                    .attr("aria-disabled", $option.is(":disabled") ? "true" : ""),
                $inner = $("<div class='item'></div>"),
                $text = $("<div class='text'></div>")
                    .html(o.optionFormat.call($option[0], 0, index + 1));
                return $li.append($inner.append($text));
            };

            // causes focus if original is focused
            focusOrig = function() {
                blurAllButMe();
                $display.triggerHandler("focus");
            };

            // loses focus if original is blurred
            blurOrig = function() {
                if (!$sb.is(".open")) {
                    $display.triggerHandler("blur");
                }
            };

            // unbind and remove
            destroySB = function(internal) {
                $sb.remove();
                $orig
                .unbind(".sb")
                .removeClass("has_sb");
                $(window).unbind("resize", delayPositionSB);
                if (!internal) {
                    $orig.removeData("sb");
                }
            };

            // destroy then load, maintaining open/focused state if applicable
            reloadSB = function() {
                var isOpen = $sb.is(".open"),
                isFocused = $display.is(".focused");
                closeSB(true);
                destroySB(true);
                self.init(o);
                if (isOpen) {
                    $orig.focus();
                    openSB(true);
                } else if (isFocused) {
                    $orig.focus();
                }
            };

            // debouncing when useTie === true
            delayReloadSB = function() {
                clearTimeout(delayReloadTimeout);
                delayReloadTimeout = setTimeout(reloadSB, 30);
            };

            // when the user clicks outside the sb
            closeAndUnbind = function() {
                $sb.removeClass("focused");
                closeSB();
                unbind();
            };

            unbind = function() {
                $(document)
              .unbind("click", closeAndUnbind)
              .unbind("keyup", keyupSB)
              .unbind("keypress", stopPageHotkeys)
              .unbind("keydown", stopPageHotkeys)
              .unbind("keydown", keydownSB);
            };

            // trigger all sbs to close
            closeAll = function() {
                $(".sb.open." + o.selectboxClass).triggerAll("close");
            };

            // trigger all sbs to blur
            blurAllButMe = function() {
                $(".sb.focused." + o.selectboxClass).not($sb[0]).find(".display").blur();
            };

            // to prevent multiple selects open at once
            closeAllButMe = function() {
                $(".sb.open." + o.selectboxClass).not($sb[0]).triggerAll("close");
            };

            // hide and reset dropdown markup
            closeSB = function(instantClose) {
                if ($sb.is(".open")) {
                    $display.blur();
                    $items.removeClass("hover");
                    unbind();
                    $dd.attr("aria-hidden", "true");
                    if (instantClose === true) {
                        $dd.hide();
                        $sb.removeClass("open");
                        $sb.append($dd);
                    } else {
                        $dd.fadeOut(o.animDuration, function() {
                            $sb.removeClass("open");
                            $sb.append($dd);
                        });
                    }
                }
            };

            // since the context can change, we should get it dynamically
            getDDCtx = function() {
                var $ddCtx = null;
                if (o.ddCtx === "self") {
                    $ddCtx = $sb;
                } else if ($.isFunction(o.ddCtx)) {
                    $ddCtx = $(o.ddCtx.call($orig[0]));
                } else {
                    $ddCtx = $(o.ddCtx);
                }
                return $ddCtx;
            };

            // DRY
            getSelected = function() {
                return $items.filter(".selected");
            };

            // DRY
            getEnabled = function() {
                return $items.not(".disabled");
            };

            // reposition the scroll of the dropdown so the selected option is centered (or appropriately onscreen)
            centerOnSelected = function() {
                $dd.scrollTop($dd.scrollTop() + getSelected().offsetFrom($dd).top - $dd.height() / 2 + getSelected().outerHeight(true) / 2);
            };

            flickerDisplay = function() {
                if ($.browser.msie && $.browser.version < 8) {
                    $("." + o.selectboxClass + " .display").hide().show(); // fix ie7 display bug
                }
            };

            // show, reposition, and reset dropdown markup
            openSB = function(instantOpen) {
                var dir,
                $ddCtx = getDDCtx();
                blurAllButMe();
                $sb.addClass("open");
                $ddCtx.append($dd);
                dir = positionSB();
                $dd.attr("aria-hidden", "false");
                if (instantOpen === true) {
                    $dd.show();
                    centerOnSelected();
                } else if (dir === "down") {
                    $dd.slideDown(o.animDuration, centerOnSelected);
                } else {
                    $dd.fadeIn(o.animDuration, centerOnSelected);
                }
                $orig.focus();
            };

            // position dropdown based on collision detection
            positionSB = function() {
                var $ddCtx = getDDCtx(),
                ddMaxHeight = 0,
                ddX = $display.offsetFrom($ddCtx).left,
                ddY = 0,
                dir = "",
                ml, mt,
                bottomSpace, topSpace,
                bottomOffset, spaceDiff,
                bodyX, bodyY;

                // modify dropdown css for getting values
                $dd.removeClass("above");
                $dd.show().css({
                    maxHeight: "none",
                    position: "relative",
                    visibility: "hidden"
                });
                if (!o.fixedWidth) {
                    $dd.width($display.outerWidth() - $dd.extraWidth() + 1);
                }

                // figure out if we should show above/below the display box
                bottomSpace = $(window).scrollTop() + $(window).height() - $display.offset().top - $display.outerHeight();
                topSpace = $display.offset().top - $(window).scrollTop();
                bottomOffset = $display.offsetFrom($ddCtx).top + $display.outerHeight();
                spaceDiff = bottomSpace - topSpace + o.dropupThreshold;
                if ($dd.outerHeight() < bottomSpace) {
                    ddMaxHeight = o.maxHeight ? o.maxHeight : bottomSpace;
                    ddY = bottomOffset;
                    dir = "down";
                } else if ($dd.outerHeight() < topSpace) {
                    ddMaxHeight = o.maxHeight ? o.maxHeight : topSpace;
                    ddY = $display.offsetFrom($ddCtx).top - Math.min(ddMaxHeight, $dd.outerHeight());
                    dir = "up";
                } else if (spaceDiff >= 0) {
                    ddMaxHeight = o.maxHeight ? o.maxHeight : bottomSpace;
                    ddY = bottomOffset;
                    dir = "down";
                } else if (spaceDiff < 0) {
                    ddMaxHeight = o.maxHeight ? o.maxHeight : topSpace;
                    ddY = $display.offsetFrom($ddCtx).top - Math.min(ddMaxHeight, $dd.outerHeight());
                    dir = "up";
                } else {
                    ddMaxHeight = o.maxHeight ? o.maxHeight : "none";
                    ddY = bottomOffset;
                    dir = "down";
                }

                ml = ("" + $("body").css("margin-left")).match(/^\d+/) ? $("body").css("margin-left") : 0;
                mt = ("" + $("body").css("margin-top")).match(/^\d+/) ? $("body").css("margin-top") : 0;
                bodyX = $().jquery >= "1.4.2"
                ? parseInt(ml)
                : $("body").offset().left;
                bodyY = $().jquery >= "1.4.2"
                ? parseInt(mt)
                : $("body").offset().top;


                // modify dropdown css for display
                $dd.hide().css({
                    left: ddX + ($ddCtx.is("body") ? bodyX : 0),
                    maxHeight: ddMaxHeight,
                    position: "absolute",
                    top: ddY + ($ddCtx.is("body") ? bodyY : 0),
                    visibility: "visible"
                });
                if (dir === "up") {
                    $dd.addClass("above");
                }
                return dir;
            };

            // when the user explicitly clicks the display
            clickSB = function(e) {
                if ($sb.is(".open")) {
                    closeSB();
                } else {
                    openSB();
                }
                return false;
            };

            // when the user selects an item in any manner
            selectItem = function() {
                var $item = $wickedweb(this),
                oldVal = $orig.val(),
                newVal = $item.data("value");

                // update the original <select>
                $orig.find("option").each(function() { this.selected = false; });
                $($item.data("orig")).each(function() { this.selected = true; });

                // change the selection to this item
                $items.removeClass("selected");
                $item.addClass("selected");
                $sb.attr("aria-active-descendant", $item.attr("id"));

                // update the title attr and the display markup
                $display.find(".text").attr("title", $item.find(".text").html());
                $display.find(".text").html(o.displayFormat.call($item.data("orig")));

                // trigger change on the old <select> if necessary
                if (oldVal !== newVal) {
                    $orig.change();
                }
            };

            // when the user explicitly clicks an item
            clickSBItem = function(e) {
                closeAndUnbind();
                $orig.focus();
                selectItem.call(this);
                return false;
            };

            // start over for generating the search term
            clearSearchTerm = function() {
                searchTerm = "";
            };

            // iterate over all the options to see if any match the search term
            findMatchingItem = function(term) {
                var i, t, $tNode,
                $available = getEnabled();
                for (i = 0; i < $available.size(); i++) {
                    $tNode = $available.eq(i).find(".text");
                    t = $tNode.children().size() == 0 ? $tNode.text() : $tNode.find("*").text();
                    if (term.length > 0 && t.toLowerCase().match("^" + term.toLowerCase())) {
                        return $available.eq(i);
                    }
                }
                return null;
            };

            // if we get a match for any options, select it
            selectMatchingItem = function(text) {
                var $matchingItem = findMatchingItem(text);
                if ($matchingItem !== null) {
                    selectItem.call($matchingItem[0]);
                    return true;
                }
                return false;
            };

            // stop up/down/backspace/space from moving the page
            stopPageHotkeys = function(e) {
                if (e.ctrlKey || e.altKey) {
                    return;
                }
                if (e.which === 38 || e.which === 40 || e.which === 8 || e.which === 32) {
                    e.preventDefault();
                }
            };

            // if a normal match fails, try matching the next element that starts with the pressed letter
            selectNextItemStartsWith = function(c) {
                var i, t,
                $selected = getSelected(),
                $available = getEnabled();
                for (i = $available.index($selected) + 1; i < $available.size(); i++) {
                    t = $available.eq(i).find(".text").text();
                    if (t !== "" && t.substring(0, 1).toLowerCase() === c.toLowerCase()) {
                        selectItem.call($available.eq(i)[0]);
                        return true;
                    }
                }
                return false;
            };

            // go up/down using arrows or attempt to autocomplete based on string
            keydownSB = function(e) {
                if (e.altKey || e.ctrlKey) {
                    return false;
                }
                var $selected = getSelected(),
                $enabled = getEnabled();
                switch (e.which) {
                    case 9: // tab
                        closeSB();
                        blurSB();
                        break;
                    case 35: // end
                        if ($selected.size() > 0) {
                            e.preventDefault();
                            selectItem.call($enabled.filter(":last")[0]);
                            centerOnSelected();
                        }
                        break;
                    case 36: // home
                        if ($selected.size() > 0) {
                            e.preventDefault();
                            selectItem.call($enabled.filter(":first")[0]);
                            centerOnSelected();
                        }
                        break;
                    case 38: // up
                        if ($selected.size() > 0) {
                            if ($enabled.filter(":first")[0] !== $selected[0]) {
                                e.preventDefault();
                                selectItem.call($enabled.eq($enabled.index($selected) - 1)[0]);
                            }
                            centerOnSelected();
                        }
                        break;
                    case 40: // down
                        if ($selected.size() > 0) {
                            if ($enabled.filter(":last")[0] !== $selected[0]) {
                                e.preventDefault();
                                selectItem.call($enabled.eq($enabled.index($selected) + 1)[0]);
                                centerOnSelected();
                            }
                        } else if ($items.size() > 1) {
                            e.preventDefault();
                            selectItem.call($items.eq(0)[0]);
                        }
                        break;
                    default:
                        break;
                }
            };

            // the user is typing -- try to select an item based on what they press
            keyupSB = function(e) {
                if (e.altKey || e.ctrlKey) {
                    return false;
                }
                if (e.which !== 38 && e.which !== 40) {

                    // add to the search term
                    searchTerm += String.fromCharCode(e.keyCode);

                    if (selectMatchingItem(searchTerm)) {

                        // we found a match, continue with the current search term
                        clearTimeout(cstTimeout);
                        cstTimeout = setTimeout(clearSearchTerm, o.acTimeout);

                    } else if (selectNextItemStartsWith(String.fromCharCode(e.keyCode))) {

                        // we selected the next item that starts with what you just pressed
                        centerOnSelected();
                        clearTimeout(cstTimeout);
                        cstTimeout = setTimeout(clearSearchTerm, o.acTimeout);

                    } else {

                        // no matches were found, clear everything
                        clearSearchTerm();
                        clearTimeout(cstTimeout);

                    }
                }
            };

            // when the sb is focused (by tab or click), allow hotkey selection and kill all other selectboxes
            focusSB = function() {
                closeAllButMe();
                $sb.addClass("focused");
                $(document)
                .click(closeAndUnbind)
                .keyup(keyupSB)
                .keypress(stopPageHotkeys)
                .keydown(stopPageHotkeys)
                .keydown(keydownSB);
            };

            // when the sb is blurred (by tab or click), disable hotkey selection
            blurSB = function() {
                $sb.removeClass("focused");
                $display.removeClass("active");
                $(document)
                .unbind("keyup", keyupSB)
                .unbind("keydown", stopPageHotkeys)
                .unbind("keypress", stopPageHotkeys)
                .unbind("keydown", keydownSB);
            };

            // add hover class to an element
            addHoverState = function() {
                $wickedweb(this).addClass("hover");
            };

            // remove hover class from an element
            removeHoverState = function() {
                $wickedweb(this).removeClass("hover");
            };

            // add active class to the display
            addActiveState = function() {
                $display.addClass("active");
                $(document).bind("mouseup", removeActiveState);
            };

            // remove active class from an element
            removeActiveState = function() {
                $display.removeClass("active");
                $(document).unbind("mouseup", removeActiveState);
            };

            // constructor
            this.init = function(opts) {

                // this plugin is not compatible with IE6 and below;
                // a normal <select> will be displayed for old browsers
                if ($.browser.msie && $.browser.version < 7) {
                    return;
                }

                // get the original <select> and <label>
                $orig = $(this.elem);
                if ($orig.attr("id")) {
                    $label = $("label[for='" + $orig.attr("id") + "']:first");
                }
                if (!$label || $label.size() === 0) {
                    $label = $orig.closest("label");
                }

                // don't create duplicate SBs
                if ($orig.hasClass("has_sb")) {
                    return;
                }

                // set the various options
                o = $.extend({
                    acTimeout: 800,               // time between each keyup for the user to create a search string
                    animDuration: 200,            // time to open/close dropdown in ms
                    ddCtx: 'body',                // body | self | any selector | a function that returns a selector (the original select is the context)
                    dropupThreshold: 150,         // the minimum amount of extra space required above the selectbox for it to display a dropup
                    fixedWidth: false,            // if false, dropdown expands to widest and display conforms to whatever is selected
                    maxHeight: false,             // if an integer, show scrollbars if the dropdown is too tall
                    maxWidth: false,              // if an integer, prevent the display/dropdown from growing past this width; longer items will be clipped
                    selectboxClass: 'selectbox',  // class to apply our markup
                    useTie: false,                // if jquery.tie is included and this is true, the selectbox will update dynamically

                    // markup appended to the display, typically for styling an arrow
                    arrowMarkup: "<div class='arrow_btn'><span class='arrow'></span></div>",

                    // use optionFormat by default
                    displayFormat: undefined,

                    // formatting for the display; note that it will be wrapped with <a href='#'><span class='text'></span></a>
                    optionFormat: function(ogIndex, optIndex) {
                        if ($wickedweb(this).size() > 0) {
                            var label = $wickedweb(this).attr("label");
                            if (label && label.length > 0) {
                                return label;
                            }
                            return $wickedweb(this).text();
                        } else {
                            return "";
                        }
                    },

                    // the function to produce optgroup markup
                    optgroupFormat: function(ogIndex) {
                        return "<span class='label'>" + $wickedweb(this).attr("label") + "</span>";
                    }
                }, opts);
                o.displayFormat = o.displayFormat || o.optionFormat;

                // generate the new sb
                loadSB();
            };

            // public method interface
            this.open = openSB;
            this.close = closeSB;
            this.refresh = reloadSB;
            this.destroy = destroySB;
            this.options = function(opts) {
                o = $.extend(o, opts);
                reloadSB();
            };
        };

    $.proto("sb", SelectBox);

} (jQuery, window));
