﻿; (function ($) {
$.fn.sexyCombo = function (config) {
return this.each(function () {
if ("SELECT" != this.tagName.toUpperCase()) { return; }
new $sc(this, config);
});
}; var defaults = { skin: "sexy", suffix: "__sexyCombo", hiddenSuffix: "__sexyComboHidden", renameOriginal: false, initialHiddenValue: "", emptyText: "", autoFill: false, triggerSelected: true, filterFn: null, dropUp: false, separator: ",", key: "value", value: "text", showListCallback: null, hideListCallback: null, initCallback: null, initEventsCallback: null, changeCallback: null, textChangeCallback: null, checkWidth: true }; $.sexyCombo = function (selectbox, config) {
if (selectbox.tagName.toUpperCase() != "SELECT") return; this.config = $.extend({}, defaults, config || {}); this.selectbox = $(selectbox); this.options = this.selectbox.children().filter("option"); this.wrapper = this.selectbox.wrap("<div>").hide().parent().addClass("combo").addClass(this.config.skin); this.input = $("<input type='text' />").appendTo(this.wrapper).attr("autocomplete", "off").attr("value", "").attr("name", this.selectbox.attr("name") + this.config.suffix); var origName = this.selectbox.attr("name"); var newName = origName + this.config.hiddenSuffix; if (this.config.renameOriginal) { this.selectbox.attr("name", newName); }
this.hidden = $("<input type='hidden' />").appendTo(this.wrapper).attr("autocomplete", "off").attr("value", this.config.initialHiddenValue).attr("name", this.config.renameOriginal ? origName : newName); this.icon = $("<div />").appendTo(this.wrapper).addClass("icon"); this.listWrapper = $("<div />").appendTo(this.wrapper).addClass("list-wrapper"); this.updateDrop(); this.list = $("<ul />").appendTo(this.listWrapper); var self = this; var optWidths = []; this.options.each(function () { var optionText = $.trim($(this).text()); if (self.config.checkWidth) { optWidths.push($("<li />").addClass("option" + $(this).val()).appendTo(self.list).html("<span>" + optionText + "</span>").addClass("visible").find("span").outerWidth()); } else { $("<li />").appendTo(self.list).html("<span>" + optionText + "</span>").addClass("visible"); } }); this.listItems = this.list.children(); if (optWidths.length) { optWidths = optWidths.sort(function (a, b) { return a - b; }); var maxOptionWidth = optWidths[optWidths.length - 1]; }
this.singleItemHeight = this.listItems.outerHeight(); this.listWrapper.addClass("invisible"); if ($.browser.opera) { this.wrapper.css({ position: "relative", left: "0", top: "0" }); }
this.filterFn = ("function" == typeof (this.config.filterFn)) ? this.config.filterFn : this.filterFn; this.lastKey = null; this.multiple = this.selectbox.attr("multiple"); var self = this; this.wrapper.data("sc:lastEvent", "click"); this.overflowCSS = "overflowY"; if ((this.config.checkWidth) && (this.listWrapper.innerWidth() < maxOptionWidth)) { this.overflowCSS = "overflow"; }
this.notify("init"); this.initEvents();
}; var $sc = $.sexyCombo; $sc.fn = $sc.prototype = {}; $sc.fn.extend = $sc.extend = $.extend; $sc.fn.extend({ initEvents: function () {
var self = this; this.icon.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.input.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.wrapper.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.icon.bind("click", function () {
if (self.input.attr("disabled")) { self.input.attr("disabled", false); }
self.wrapper.data("sc:lastEvent", "click"); self.filter(); self.iconClick();
}); this.listItems.bind("mouseover", function (e) { if ("LI" == e.target.nodeName.toUpperCase()) { self.highlight(e.target); } else { self.highlight($(e.target).parent()); } }); this.listItems.bind("click", function (e) { self.listItemClick($(e.target)); }); this.input.bind("keyup", function (e) { self.wrapper.data("sc:lastEvent", "key"); self.keyUp(e); }); this.input.bind("keypress", function (e) {
if ($sc.KEY.RETURN == e.keyCode) { e.preventDefault(); }
if ($sc.KEY.TAB == e.keyCode) e.preventDefault();
}); $(document).bind("click", function (e) { if ((self.icon.get(0) == e.target) || (self.input.get(0) == e.target)) return; self.hideList(); }); this.triggerSelected(); this.applyEmptyText(); this.input.bind("click", function (e) { self.wrapper.data("sc:lastEvent", "click"); self.icon.trigger("click"); }); this.wrapper.bind("click", function () { self.wrapper.data("sc:lastEvent", "click"); }); this.input.bind("keydown", function (e) { if (9 == e.keyCode) { e.preventDefault(); } }); this.wrapper.bind("keyup", function (e) {
var k = e.keyCode; for (key in $sc.KEY) { if ($sc.KEY[key] == k) { return; } }
self.wrapper.data("sc:lastEvent", "key");
}); this.input.bind("click", function () { self.wrapper.data("sc:lastEvent", "click"); }); this.icon.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.input.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.wrapper.bind("click", function (e) { if (!self.wrapper.data("sc:positionY")) { self.wrapper.data("sc:positionY", e.pageY); } }); this.notify("initEvents");
}, getTextValue: function () { return this.__getValue("input"); }, getCurrentTextValue: function () { return this.__getCurrentValue("input"); }, getHiddenValue: function () { return this.__getValue("hidden"); }, getCurrentHiddenValue: function () { return this.__getCurrentValue("hidden"); }, __getValue: function (prop) {
prop = this[prop]; if (!this.multiple) return $.trim(prop.val()); var tmpVals = prop.val().split(this.config.separator); var vals = []; for (var i = 0, len = tmpVals.length; i < len; ++i) { vals.push($.trim(tmpVals[i])); }
vals = $sc.normalizeArray(vals); return vals;
}, __getCurrentValue: function (prop) { prop = this[prop]; if (!this.multiple) return $.trim(prop.val()); return $.trim(prop.val().split(this.config.separator).pop()); }, iconClick: function () { if (this.listVisible()) { this.hideList(); this.input.blur(); } else { this.showList(); this.input.focus(); if (this.input.val().length) { this.selection(this.input.get(0), 0, this.input.val().length); } } }, listVisible: function () { return this.listWrapper.hasClass("visible"); }, showList: function () {
if (!this.listItems.filter(".visible").length) return; this.listWrapper.removeClass("invisible").addClass("visible"); this.wrapper.css("zIndex", "99999"); this.listWrapper.css("zIndex", "99999"); this.setListHeight(); var listHeight = this.listWrapper.height(); var inputHeight = this.wrapper.height(); var bottomPos = parseInt(this.wrapper.data("sc:positionY")) + inputHeight + listHeight; var maxShown = $(window).height() + $(document).scrollTop(); if (bottomPos > maxShown) { this.setDropUp(true); } else { this.setDropUp(false); }
if ("" == $.trim(this.input.val())) { this.highlightFirst(); this.listWrapper.scrollTop(0); } else { this.highlightSelected(); }
this.notify("showList");
}, hideList: function () { if (this.listWrapper.hasClass("invisible")) return; this.listWrapper.removeClass("visible").addClass("invisible"); this.wrapper.css("zIndex", "0"); this.listWrapper.css("zIndex", "99999"); this.notify("hideList"); }, getListItemsHeight: function () { var itemHeight = this.singleItemHeight; var liHeight = itemHeight * this.liLen(); if (liHeight == 0 && this.liLen() > 0 ){ liHeight = 200; } return liHeight; }, setOverflow: function () { var maxHeight = this.getListMaxHeight(); if (this.getListItemsHeight() >= maxHeight) this.listWrapper.css(this.overflowCSS, "scroll"); else this.listWrapper.css(this.overflowCSS, "hidden"); }, highlight: function (activeItem) { if (($sc.KEY.DOWN == this.lastKey) || ($sc.KEY.UP == this.lastKey)) return; this.listItems.removeClass("active"); $(activeItem).addClass("active"); }, setComboValue: function (val, pop, hideList) {
var oldVal = this.input.val(); var v = ""; if (this.multiple) { v = this.getTextValue(); if (pop) v.pop(); v.push($.trim(val)); v = $sc.normalizeArray(v); v = v.join(this.config.separator) + this.config.separator; } else { v = $.trim(val); }
this.input.val(v); this.setHiddenValue(val); this.filter(); if (hideList) this.hideList(); this.input.removeClass("empty"); if (this.multiple) this.input.focus(); if (this.input.val() != oldVal) this.notify("textChange");
}, setHiddenValue: function (val) {
var set = false; val = $.trim(val); var oldVal = this.hidden.val(); if (!this.multiple) { for (var i = 0, len = this.options.length; i < len; ++i) { if (val == this.options.eq(i).text()) { this.hidden.val(this.options.eq(i).val()); set = true; break; } } } else {
var comboVals = this.getTextValue(); var hiddenVals = []; for (var i = 0, len = comboVals.length; i < len; ++i) { for (var j = 0, len1 = this.options.length; j < len1; ++j) { if (comboVals[i] == this.options.eq(j).text()) { hiddenVals.push(this.options.eq(j).val()); } } }
if (hiddenVals.length) { set = true; this.hidden.val(hiddenVals.join(this.config.separator)); } 
}
if (!set) { this.hidden.val(this.config.initialHiddenValue); }
if (oldVal != this.hidden.val()) this.notify("change"); this.selectbox.val(this.hidden.val()); this.selectbox.trigger("change");
}, listItemClick: function (item) { this.setComboValue(item.text(), true, true); this.inputFocus(); }, filter: function () {
if ("yes" == this.wrapper.data("sc:optionsChanged")) { var self = this; this.listItems.remove(); this.options = this.selectbox.children().filter("option"); this.options.each(function () { var optionText = $.trim($(this).text()); $("<li />").appendTo(self.list).text(optionText).addClass("visible"); }); this.listItems = this.list.children(); this.listItems.bind("mouseover", function (e) { self.highlight(e.target); }); this.listItems.bind("click", function (e) { self.listItemClick($(e.target)); }); self.wrapper.data("sc:optionsChanged", ""); }
var comboValue = this.input.val(); var self = this; this.listItems.each(function () { var $this = $(this); var itemValue = $this.text(); if (self.filterFn.call(self, self.getCurrentTextValue(), itemValue, self.getTextValue())) { $this.removeClass("invisible").addClass("visible"); } else { $this.removeClass("visible").addClass("invisible"); } }); this.setOverflow(); this.setListHeight();
}, filterFn: function (currentComboValue, itemValue, allComboValues) {
if ("click" == this.wrapper.data("sc:lastEvent")) { return true; }
if (!this.multiple) { return itemValue.toLowerCase().indexOf(currentComboValue.toLowerCase()) == 0; } else {
for (var i = 0, len = allComboValues.length; i < len; ++i) { if (itemValue == allComboValues[i]) { return false; } }
return itemValue.toLowerCase().search(currentComboValue.toLowerCase()) == 0;
} 
}, getListMaxHeight: function () {
var result = parseInt(this.listWrapper.css("maxHeight"), 10); if (isNaN(result)) { result = this.singleItemHeight * 10; }
return result;
}, setListHeight: function () { var liHeight = this.getListItemsHeight(); var maxHeight = this.getListMaxHeight(); var listHeight = this.listWrapper.height(); if (liHeight < listHeight) { this.listWrapper.height(liHeight); return liHeight; } else if (liHeight > listHeight) { this.listWrapper.height(Math.min(maxHeight, liHeight)); return Math.min(maxHeight, liHeight); } }, getActive: function () { return this.listItems.filter(".active"); }, keyUp: function (e) { this.lastKey = e.keyCode; var k = $sc.KEY; switch (e.keyCode) { case k.RETURN: case k.TAB: this.setComboValue(this.getActive().text(), true, true); if (!this.multiple) break; case k.DOWN: this.highlightNext(); break; case k.UP: this.highlightPrev(); break; case k.ESC: this.hideList(); break; default: this.inputChanged(); break; } }, liLen: function () { return this.listItems.filter(".visible").length; }, inputChanged: function () {
this.filter(); if (this.liLen()) { this.showList(); this.setOverflow(); this.setListHeight(); } else { this.hideList(); }
this.setHiddenValue(this.input.val()); this.notify("textChange");
}, highlightFirst: function () { this.listItems.removeClass("active").filter(".visible:eq(0)").addClass("active"); this.autoFill(); }, highlightSelected: function () { this.listItems.removeClass("active"); var val = $.trim(this.input.val()); try { this.listItems.each(function () { var $this = $(this); if ($this.text() == val) { $this.addClass("active"); self.listWrapper.scrollTop(0); self.scrollDown(); } }); this.highlightFirst(); } catch (e) { } }, highlightNext: function () {
var $next = this.getActive().next(); while ($next.hasClass("invisible") && $next.length) { $next = $next.next(); }
if ($next.length) { this.listItems.removeClass("active"); $next.addClass("active"); this.scrollDown(); } 
}, scrollDown: function () { if ("scroll" != this.listWrapper.css(this.overflowCSS)) return; var beforeActive = this.getActiveIndex() + 1; var minScroll = this.listItems.outerHeight() * beforeActive - this.listWrapper.height(); if ($.browser.msie) minScroll += beforeActive; if (this.listWrapper.scrollTop() < minScroll) this.listWrapper.scrollTop(minScroll); }, highlightPrev: function () { var $prev = this.getActive().prev(); while ($prev.length && $prev.hasClass("invisible")) $prev = $prev.prev(); if ($prev.length) { this.getActive().removeClass("active"); $prev.addClass("active"); this.scrollUp(); } }, getActiveIndex: function () { return $.inArray(this.getActive().get(0), this.listItems.filter(".visible").get()); }, scrollUp: function () { if ("scroll" != this.listWrapper.css(this.overflowCSS)) return; var maxScroll = this.getActiveIndex() * this.listItems.outerHeight(); if (this.listWrapper.scrollTop() > maxScroll) { this.listWrapper.scrollTop(maxScroll); } }, applyEmptyText: function () { if (!this.config.emptyText.length) return; var self = this; this.input.bind("focus", function () { self.inputFocus(); }).bind("blur", function () { self.inputBlur(); }); if ("" == this.input.val()) { this.input.addClass("empty").val(this.config.emptyText); } }, inputFocus: function () { if (this.input.hasClass("empty")) { this.input.removeClass("empty").val(""); } }, inputBlur: function () { if ("" == this.input.val()) { this.input.addClass("empty").val(this.config.emptyText); } }, triggerSelected: function () {
if (!this.config.triggerSelected) return; var self = this; try { this.options.each(function () { if ($(this).attr("selected")) { self.setComboValue($(this).text(), false, true); throw new Error(); } }); } catch (e) { return; }
self.setComboValue(this.options.eq(0).text(), false, false);
}, autoFill: function () { if (!this.config.autoFill || ($sc.KEY.BACKSPACE == this.lastKey) || this.multiple) return; var curVal = this.input.val(); var newVal = this.getActive().text(); this.input.val(newVal); this.selection(this.input.get(0), curVal.length, newVal.length); }, selection: function (field, start, end) { if (field.createTextRange) { var selRange = field.createTextRange(); selRange.collapse(true); selRange.moveStart("character", start); selRange.moveEnd("character", end); selRange.select(); } else if (field.setSelectionRange) { field.setSelectionRange(start, end); } else { if (field.selectionStart) { field.selectionStart = start; field.selectionEnd = end; } } }, updateDrop: function () { if (this.config.dropUp) this.listWrapper.addClass("list-wrapper-up"); else this.listWrapper.removeClass("list-wrapper-up"); }, setDropUp: function (drop) { this.config.dropUp = drop; this.updateDrop(); }, notify: function (evt) { if (!$.isFunction(this.config[evt + "Callback"])) return; this.config[evt + "Callback"].call(this); } 
}); $sc.extend({ KEY: { UP: 38, DOWN: 40, DEL: 46, TAB: 9, RETURN: 13, ESC: 27, COMMA: 188, PAGEUP: 33, PAGEDOWN: 34, BACKSPACE: 8 }, log: function (msg) { var $log = $("#log"); $log.html($log.html() + msg + "<br />"); }, createSelectbox: function (config) {
var $selectbox = $("<select />").appendTo(config.container).attr({ name: config.name, id: config.id, size: "1" }); if (config.multiple) $selectbox.attr("multiple", true); var data = config.data; var selected = false; for (var i = 0, len = data.length; i < len; ++i) { selected = data[i].selected || false; $("<option />").appendTo($selectbox).attr("value", data[i][config.key]).text(data[i][config.value]).attr("selected", selected); }
return $selectbox.get(0);
}, create: function (config) {
var defaults = { name: "", id: "", data: [], multiple: false, key: "value", value: "text", container: $(document), url: "", ajaxData: {} }; config = $.extend({}, defaults, config || {}); if (config.url) { return $.getJSON(config.url, config.ajaxData, function (data) { delete config.url; delete config.ajaxData; config.data = data; return $sc.create(config); }); }
config.container = $(config.container); var selectbox = $sc.createSelectbox(config); return new $sc(selectbox, config);
}, deactivate: function ($select) {
$select = $($select); $select.each(function () {
if ("SELECT" != this.tagName.toUpperCase()) { return; }
var $this = $(this); if (!$this.parent().is(".combo")) { return; } 
});
}, activate: function ($select) {
$select = $($select); $select.each(function () {
if ("SELECT" != this.tagName.toUpperCase()) { return; }
var $this = $(this); if (!$this.parent().is(".combo")) { return; }
$this.parent().find("input[type='text']").attr("disabled", false);
});
}, changeOptions: function ($select) {
$select = $($select); $select.each(function () {
if ("SELECT" != this.tagName.toUpperCase()) { return; }
var $this = $(this); var $wrapper = $this.parent(); var $input = $wrapper.find("input[type='text']"); var $listWrapper = $wrapper.find("ul").parent(); $listWrapper.removeClass("visible").addClass("invisible"); $wrapper.css("zIndex", "0"); $listWrapper.css("zIndex", "99999"); $input.val(""); $wrapper.data("sc:optionsChanged", "yes"); var $selectbox = $this; $selectbox.parent().find("input[type='text']").val($selectbox.find("option:eq(0)").text()); $selectbox.parent().data("sc:lastEvent", "click"); $selectbox.find("option:eq(0)").attr('selected', 'selected');
});
}, normalizeArray: function (arr) {
var result = []; for (var i = 0, len = arr.length; i < len; ++i) { if ("" == arr[i]) continue; result.push(arr[i]); }
return result;
} 
});
})(jQuery);
