(function( $, window, undefined ) { | |
// utility functions | |
$.fn.borderWidth = function() { return $(this).outerWidth() - $(this).innerWidth(); }; | |
$.fn.paddingWidth = function() { return $(this).innerWidth() - $(this).width(); }; | |
$.fn.extraWidth = function() { return $(this).outerWidth(true) - $(this).width(); }; | |
$.fn.offsetFrom = function( e ) { | |
var $e = $(e); | |
return { | |
left: $(this).offset().left - $e.offset().left, | |
top: $(this).offset().top - $e.offset().top | |
}; | |
}; | |
$.fn.maxWidth = function() { | |
var max = 0; | |
$(this).each(function() { | |
if($(this).width() > max) { | |
max = $(this).width(); | |
} | |
}); | |
return max; | |
}; | |
$.fn.triggerAll = function(event, params) { | |
return $(this).each(function() { | |
$(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; | |
$(this).each(function() { | |
var $e = $(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 $(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) | |
: " "; | |
$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($(this).is("optgroup")) { | |
$og = $(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($(this), i) | |
.addClass($og.is(":disabled") ? "disabled" : "") | |
.attr("aria-disabled", $og.is(":disabled") ? "true" : ""); | |
$ogList.append($opt); | |
}); | |
} else { | |
$dd.append(createOption($(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=''> </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 = $(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() { | |
$(this).addClass("hover"); | |
}; | |
// remove hover class from an element | |
removeHoverState = function() { | |
$(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($(this).size() > 0) { | |
var label = $(this).attr("label"); | |
if(label && label.length > 0) { | |
return label; | |
} | |
return $(this).text(); | |
} else { | |
return ""; | |
} | |
}, | |
// the function to produce optgroup markup | |
optgroupFormat: function( ogIndex ) { | |
return "<span class='label'>" + $(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)); |