diff --git a/select2.js b/select2.js index 2cbf0980..96a0ba35 100755 --- a/select2.js +++ b/select2.js @@ -24,7 +24,9 @@ return; } - var KEY = { + var KEY, AbstractSelect2, SingleSelect2, MultiSelect2; + + KEY = { TAB: 9, ENTER: 13, ESC: 27, @@ -298,788 +300,534 @@ }); }); + /** + * Creates a new class * - * @param opts + * @param superClass + * @param methods */ - function AbstractSelect2() { + function clazz(superClass, methods) { + var clazz = function () {}; + clazz.prototype = new superClass; + clazz.prototype.constructor = clazz; + clazz.prototype.parent = superClass.prototype; + clazz.prototype = $.extend(clazz.prototype, methods); + return clazz; } - AbstractSelect2.prototype.bind = function (func) { - var self = this; - return function () { - func.apply(self, arguments); - }; - }; + AbstractSelect2 = clazz(Object, { - AbstractSelect2.prototype.init = function (opts) { - var results, search, resultsSelector = ".select2-results"; + bind: function (func) { + var self = this; + return function () { + func.apply(self, arguments); + }; + }, - // prepare options - this.opts = this.prepareOpts(opts); + init: function (opts) { + var results, search, resultsSelector = ".select2-results"; - // destroy if called on an existing component - if (opts.element.data("select2") !== undefined) { - this.destroy(); - } + // prepare options + this.opts = this.prepareOpts(opts); - this.container = this.createContainer(); - - if (opts.element.attr("class") !== undefined) { - this.container.addClass(opts.element.attr("class")); - } - - // swap container for the element - this.opts.element - .data("select2", this) - .hide() - .after(this.container); - this.container.data("select2", this); - - this.dropdown = this.container.find(".select2-drop"); - this.results = results = this.container.find(resultsSelector); - this.search = search = this.container.find("input[type=text]"); - - this.resultsPage = 0; - - // initialize the container - this.initContainer(); - - installFilteredMouseMove(this.results); - this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); - - installDebouncedScroll(80, this.results); - this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); - - // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel - if ($.fn.mousewheel) { - results.mousewheel(function (e, delta, deltaX, deltaY) { - var top = results.scrollTop(), height; - if (deltaY > 0 && top - deltaY <= 0) { - results.scrollTop(0); - killEvent(e); - } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { - results.scrollTop(results.get(0).scrollHeight - results.height()); - killEvent(e); - } - }); - } - - installKeyUpChangeEvent(search); - search.bind("keyup-change", this.bind(this.updateResults)); - - this.container.delegate(resultsSelector, "click", this.bind(function (e) { - if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) { - this.highlightUnderEvent(e); - this.selectHighlighted(e); - } else { - killEvent(e); - this.focusSearch(); + // destroy if called on an existing component + if (opts.element.data("select2") !== undefined) { + this.destroy(); } - })); - if ($.isFunction(this.opts.initSelection)) { - // initialize selection based on the current value of the source element - this.initSelection(); + this.container = this.createContainer(); - // if the user has provided a function that can set selection based on the value of the source element - // we monitor the change event on the element and trigger it, allowing for two way synchronization - this.monitorSource(); - } - }; + if (opts.element.attr("class") !== undefined) { + this.container.addClass(opts.element.attr("class")); + } - AbstractSelect2.prototype.destroy = function () { - var select2 = this.opts.element.data("select2"); - if (select2 !== undefined) { - select2.container.remove(); - select2.opts.element - .removeData("select2") - .show(); - } - }; + // swap container for the element + this.opts.element + .data("select2", this) + .hide() + .after(this.container); + this.container.data("select2", this); - AbstractSelect2.prototype.prepareOpts = function (opts) { - var element, select; + this.dropdown = this.container.find(".select2-drop"); + this.results = results = this.container.find(resultsSelector); + this.search = search = this.container.find("input[type=text]"); - opts = $.extend({}, { - formatResult: function (data) { return data.text; }, - formatSelection: function (data) { return data.text; }, - formatNoMatches: function () { return "No matches found"; }, - formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; }, - minimumResultsForSearch: 0 - }, opts); + this.resultsPage = 0; - element = opts.element; + // initialize the container + this.initContainer(); - if (element.get(0).tagName.toLowerCase() === "select") { - this.select = select = opts.element; - } + installFilteredMouseMove(this.results); + this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); - // TODO add missing validation logic - if (select) { - /*$.each(["multiple", "ajax", "query", "minimumInputLength"], function () { - if (this in opts) { - throw "Option '" + this + "' is not allowed for Select2 when attached to a select element"; - } - });*/ - this.opts = opts = $.extend({}, { - miniumInputLength: 0 - }, opts); - } else { - this.opts = opts = $.extend({}, { - miniumInputLength: 0 - }, opts); - } + installDebouncedScroll(80, this.results); + this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); - if (select) { - opts.query = this.bind(function (query) { - var data = {results: [], more: false}, - term = query.term.toUpperCase(), - placeholder = this.getPlaceholder(); - element.find("option").each(function (i) { - var e = $(this), - text = e.text(); - - if (i === 0 && placeholder !== undefined && text === "") return true; - - if (text.toUpperCase().indexOf(term) >= 0) { - data.results.push({id: e.attr("value"), text: text}); + // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel + if ($.fn.mousewheel) { + results.mousewheel(function (e, delta, deltaX, deltaY) { + var top = results.scrollTop(), height; + if (deltaY > 0 && top - deltaY <= 0) { + results.scrollTop(0); + killEvent(e); + } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { + results.scrollTop(results.get(0).scrollHeight - results.height()); + killEvent(e); } }); - query.callback(data); - }); - } else { - if (!("query" in opts)) { - if ("ajax" in opts) { - opts.query = ajax(opts.ajax); - } else if ("data" in opts) { - opts.query = local(opts.data); - } else if ("tags" in opts) { - opts.query = tags(opts.tags); - opts.createSearchChoice = function (term) { return {id: term, text: term};} - opts.initSelection = function (element) { - var data = []; - $(element.val().split(",")).each(function () { - data.push({id: this, text: this}); - }); - return data; + } + + installKeyUpChangeEvent(search); + search.bind("keyup-change", this.bind(this.updateResults)); + + this.container.delegate(resultsSelector, "click", this.bind(function (e) { + if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) { + this.highlightUnderEvent(e); + this.selectHighlighted(e); + } else { + killEvent(e); + this.focusSearch(); + } + })); + + if ($.isFunction(this.opts.initSelection)) { + // initialize selection based on the current value of the source element + this.initSelection(); + + // if the user has provided a function that can set selection based on the value of the source element + // we monitor the change event on the element and trigger it, allowing for two way synchronization + this.monitorSource(); + } + }, + + destroy: function () { + var select2 = this.opts.element.data("select2"); + if (select2 !== undefined) { + select2.container.remove(); + select2.opts.element + .removeData("select2") + .show(); + } + }, + + prepareOpts: function (opts) { + var element, select; + + opts = $.extend({}, { + formatResult: function (data) { return data.text; }, + formatSelection: function (data) { return data.text; }, + formatNoMatches: function () { return "No matches found"; }, + formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; }, + minimumResultsForSearch: 0 + }, opts); + + element = opts.element; + + if (element.get(0).tagName.toLowerCase() === "select") { + this.select = select = opts.element; + } + + // TODO add missing validation logic + if (select) { + /*$.each(["multiple", "ajax", "query", "minimumInputLength"], function () { + if (this in opts) { + throw "Option '" + this + "' is not allowed for Select2 when attached to a select element"; + } + });*/ + this.opts = opts = $.extend({}, { + miniumInputLength: 0 + }, opts); + } else { + this.opts = opts = $.extend({}, { + miniumInputLength: 0 + }, opts); + } + + if (select) { + opts.query = this.bind(function (query) { + var data = {results: [], more: false}, + term = query.term.toUpperCase(), + placeholder = this.getPlaceholder(); + element.find("option").each(function (i) { + var e = $(this), + text = e.text(); + + if (i === 0 && placeholder !== undefined && text === "") return true; + + if (text.toUpperCase().indexOf(term) >= 0) { + data.results.push({id: e.attr("value"), text: text}); + } + }); + query.callback(data); + }); + } else { + if (!("query" in opts)) { + if ("ajax" in opts) { + opts.query = ajax(opts.ajax); + } else if ("data" in opts) { + opts.query = local(opts.data); + } else if ("tags" in opts) { + opts.query = tags(opts.tags); + opts.createSearchChoice = function (term) { return {id: term, text: term};} + opts.initSelection = function (element) { + var data = []; + $(element.val().split(",")).each(function () { + data.push({id: this, text: this}); + }); + return data; + } } } } - } - if (typeof(opts.query) !== "function") { - throw "query function not defined for Select2 " + opts.element.attr("id"); - } - - return opts; - }; - - /** - * Monitor the original element for changes and update select2 accordingly - */ - AbstractSelect2.prototype.monitorSource = function () { - this.opts.element.bind("change", this.bind(function (e) { - if (this.opts.element.data("select2-change-triggered") !== true) { - this.initSelection(); + if (typeof(opts.query) !== "function") { + throw "query function not defined for Select2 " + opts.element.attr("id"); } - })); - }; - /** - * Triggers the change event on the source element - */ - AbstractSelect2.prototype.triggerChange = function () { - // Prevents recursive triggering - this.opts.element.data("select2-change-triggered", true); - this.opts.element.trigger("change"); - this.opts.element.data("select2-change-triggered", false); - }; + return opts; + }, - AbstractSelect2.prototype.opened = function () { - return this.container.hasClass("select2-dropdown-open"); - }; + /** + * Monitor the original element for changes and update select2 accordingly + */ + monitorSource: function () { + this.opts.element.bind("change", this.bind(function (e) { + if (this.opts.element.data("select2-change-triggered") !== true) { + this.initSelection(); + } + })); + }, - AbstractSelect2.prototype.alignDropdown = function () { - this.dropdown.css({ - top: this.container.height() - }); - }; + /** + * Triggers the change event on the source element + */ + triggerChange: function () { + // Prevents recursive triggering + this.opts.element.data("select2-change-triggered", true); + this.opts.element.trigger("change"); + this.opts.element.data("select2-change-triggered", false); + }, - AbstractSelect2.prototype.open = function () { - if (this.opened()) return; + opened: function () { + return this.container.hasClass("select2-dropdown-open"); + }, - this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); + alignDropdown: function () { + this.dropdown.css({ + top: this.container.height() + }); + }, - this.updateResults(true); - this.alignDropdown(); - this.dropdown.show(); - this.focusSearch(); - }; + open: function () { + if (this.opened()) return; - AbstractSelect2.prototype.close = function () { - if (!this.opened()) return; + this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); - this.dropdown.hide(); - this.container.removeClass("select2-dropdown-open"); - this.results.empty(); - this.clearSearch(); - }; + this.updateResults(true); + this.alignDropdown(); + this.dropdown.show(); + this.focusSearch(); + }, - AbstractSelect2.prototype.clearSearch = function () { + close: function () { + if (!this.opened()) return; - }; + this.dropdown.hide(); + this.container.removeClass("select2-dropdown-open"); + this.results.empty(); + this.clearSearch(); + }, - AbstractSelect2.prototype.ensureHighlightVisible = function () { - var results = this.results, children, index, child, hb, rb, y, more; + clearSearch: function () { - children = results.children(".select2-result"); - index = this.highlight(); + }, - if (index < 0) return; + ensureHighlightVisible: function () { + var results = this.results, children, index, child, hb, rb, y, more; - child = $(children[index]); - - hb = child.offset().top + child.outerHeight(); - - // if this is the last child lets also make sure select2-more-results is visible - if (index === children.length - 1) { - more = results.find("li.select2-more-results"); - if (more.length > 0) { - hb = more.offset().top + more.outerHeight(); - } - } - - rb = results.offset().top + results.outerHeight(); - if (hb > rb) { - results.scrollTop(results.scrollTop() + (hb - rb)); - } - y = child.offset().top - results.offset().top; - - // make sure the top of the element is visible - if (y < 0) { - results.scrollTop(results.scrollTop() + y); // y is negative - } - }; - - AbstractSelect2.prototype.moveHighlight = function (delta) { - var choices = this.results.children(".select2-result"), + children = results.children(".select2-result"); index = this.highlight(); - while (index > -1 && index < choices.length) { - index += delta; - if (!$(choices[index]).hasClass("select2-disabled")) { - this.highlight(index); - break; + if (index < 0) return; + + child = $(children[index]); + + hb = child.offset().top + child.outerHeight(); + + // if this is the last child lets also make sure select2-more-results is visible + if (index === children.length - 1) { + more = results.find("li.select2-more-results"); + if (more.length > 0) { + hb = more.offset().top + more.outerHeight(); + } } - } - }; - AbstractSelect2.prototype.highlight = function (index) { - var choices = this.results.children(".select2-result"); + rb = results.offset().top + results.outerHeight(); + if (hb > rb) { + results.scrollTop(results.scrollTop() + (hb - rb)); + } + y = child.offset().top - results.offset().top; - if (arguments.length === 0) { - return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); - } + // make sure the top of the element is visible + if (y < 0) { + results.scrollTop(results.scrollTop() + y); // y is negative + } + }, - choices.removeClass("select2-highlighted"); + moveHighlight: function (delta) { + var choices = this.results.children(".select2-result"), + index = this.highlight(); - if (index >= choices.length) index = choices.length - 1; - if (index < 0) index = 0; + while (index > -1 && index < choices.length) { + index += delta; + if (!$(choices[index]).hasClass("select2-disabled")) { + this.highlight(index); + break; + } + } + }, - $(choices[index]).addClass("select2-highlighted"); - this.ensureHighlightVisible(); + highlight: function (index) { + var choices = this.results.children(".select2-result"); - if (this.opened()) this.focusSearch(); - }; + if (arguments.length === 0) { + return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); + } - AbstractSelect2.prototype.highlightUnderEvent = function (event) { - var el = $(event.target).closest(".select2-result"); - if (el.length > 0) { - this.highlight(el.index()); - } - }; + choices.removeClass("select2-highlighted"); - AbstractSelect2.prototype.loadMoreIfNeeded = function () { - var results = this.results, - more = results.find("li.select2-more-results"), - below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible - offset = -1, // index of first element without data - page = this.resultsPage + 1; + if (index >= choices.length) index = choices.length - 1; + if (index < 0) index = 0; - if (more.length === 0) return; + $(choices[index]).addClass("select2-highlighted"); + this.ensureHighlightVisible(); - below = more.offset().top - results.offset().top - results.height(); + if (this.opened()) this.focusSearch(); + }, - if (below <= 0) { - more.addClass("select2-active"); - this.opts.query({term: this.search.val(), page: page, callback: this.bind(function (data) { - var parts = [], self = this; - $(data.results).each(function () { - parts.push("
  • "); - parts.push(self.opts.formatResult(this)); - parts.push("
  • "); - }); - more.before(parts.join("")); - results.find(".select2-result").each(function (i) { - var e = $(this); - if (e.data("select2-data") !== undefined) { - offset = i; + highlightUnderEvent: function (event) { + var el = $(event.target).closest(".select2-result"); + if (el.length > 0) { + this.highlight(el.index()); + } + }, + + loadMoreIfNeeded: function () { + var results = this.results, + more = results.find("li.select2-more-results"), + below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible + offset = -1, // index of first element without data + page = this.resultsPage + 1; + + if (more.length === 0) return; + + below = more.offset().top - results.offset().top - results.height(); + + if (below <= 0) { + more.addClass("select2-active"); + this.opts.query({term: this.search.val(), page: page, callback: this.bind(function (data) { + var parts = [], self = this; + $(data.results).each(function () { + parts.push("
  • "); + parts.push(self.opts.formatResult(this)); + parts.push("
  • "); + }); + more.before(parts.join("")); + results.find(".select2-result").each(function (i) { + var e = $(this); + if (e.data("select2-data") !== undefined) { + offset = i; + } else { + e.data("select2-data", data.results[i - offset - 1]); + } + }); + if (data.more) { + more.removeClass("select2-active"); } else { - e.data("select2-data", data.results[i - offset - 1]); + more.remove(); } - }); - if (data.more) { - more.removeClass("select2-active"); - } else { - more.remove(); - } - this.resultsPage = page; - })}); - } - }; + this.resultsPage = page; + })}); + } + }, - /** - * @param initial whether or not this is the call to this method right after the dropdown has been opened - */ - AbstractSelect2.prototype.updateResults = function (initial) { - var search = this.search, results = this.results, opts = this.opts; + /** + * @param initial whether or not this is the call to this method right after the dropdown has been opened + */ + updateResults: function (initial) { + var search = this.search, results = this.results, opts = this.opts; - search.addClass("select2-active"); + search.addClass("select2-active"); - function render(html) { - results.html(html); - results.scrollTop(0); - search.removeClass("select2-active"); - } + function render(html) { + results.html(html); + results.scrollTop(0); + search.removeClass("select2-active"); + } - if (search.val().length < opts.minimumInputLength) { - render("
  • " + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "
  • "); - return; - } + if (search.val().length < opts.minimumInputLength) { + render("
  • " + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "
  • "); + return; + } - this.resultsPage = 1; - opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) { - var parts = [], // html parts - def; // default choice + this.resultsPage = 1; + opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) { + var parts = [], // html parts + def; // default choice - // create a default choice and prepend it to the list - if (this.opts.createSearchChoice && search.val() !== "") { - def = this.opts.createSearchChoice.call(null, search.val(), data.results); - if (def !== undefined && def !== null && def.id !== undefined && def.id != null) { - if ($(data.results).filter( - function () { - return equal(this.id, def.id); - }).length === 0) { - data.results.unshift(def); + // create a default choice and prepend it to the list + if (this.opts.createSearchChoice && search.val() !== "") { + def = this.opts.createSearchChoice.call(null, search.val(), data.results); + if (def !== undefined && def !== null && def.id !== undefined && def.id != null) { + if ($(data.results).filter( + function () { + return equal(this.id, def.id); + }).length === 0) { + data.results.unshift(def); + } } } - } - if (data.results.length === 0) { - render("
  • " + opts.formatNoMatches(search.val()) + "
  • "); - return; - } - - $(data.results).each(function () { - parts.push("
  • "); - parts.push(opts.formatResult(this)); - parts.push("
  • "); - }); - - if (data.more === true) { - parts.push("
  • Loading more results...
  • "); - } - - render(parts.join("")); - results.children(".select2-result").each(function (i) { - var d = data.results[i]; - $(this).data("select2-data", d); - }); - this.postprocessResults(data, initial); - })}); - }; - - AbstractSelect2.prototype.cancel = function () { - this.close(); - }; - - AbstractSelect2.prototype.blur = function () { - /* we do this in a timeout so that current event processing can complete before this code is executed. - this allows tab index to be preserved even if this code blurs the textfield */ - window.setTimeout(this.bind(function () { - this.close(); - this.container.removeClass("select2-container-active"); - this.clearSearch(); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - this.search.blur(); - }), 10); - }; - - AbstractSelect2.prototype.focusSearch = function () { - /* we do this in a timeout so that current event processing can complete before this code is executed. - this makes sure the search field is focussed even if the current event would blur it */ - window.setTimeout(this.bind(function () { - this.search.focus(); - }), 10); - }; - - AbstractSelect2.prototype.selectHighlighted = function () { - var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data"); - if (data) { - this.onSelect(data); - } - }; - - AbstractSelect2.prototype.getPlaceholder = function () { - var placeholder = this.opts.element.data("placeholder"); - if (placeholder !== undefined) return placeholder; - return this.opts.placeholder; - }; - - /** - * Get the desired width for the container element. This is - * derived first from option `width` passed to select2, then - * the inline 'style' on the original element, and finally - * falls back to the jQuery calculated element width. - * - * @returns The width string (with units) for the container. - */ - AbstractSelect2.prototype.getContainerWidth = function () { - if (this.opts.width !== undefined) - return this.opts.width; - - var style = this.opts.element.attr('style'); - if (style !== undefined) { - var attrs = style.split(';'); - for (var i = 0; i < attrs.length; i++) { - var matches = attrs[i].replace(/\s/g, '') - .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/); - if (matches != null && matches.length >= 1) - return matches[1]; - } - } - return this.opts.element.width() + 'px'; - }; - - function SingleSelect2() { - } - - SingleSelect2.prototype = new AbstractSelect2(); - SingleSelect2.prototype.constructor = SingleSelect2; - SingleSelect2.prototype.parent = AbstractSelect2.prototype; - - SingleSelect2.prototype.createContainer = function () { - return $("
    ", { - "class": "select2-container", - "style": "width: " + this.getContainerWidth() - }).html([ - " ", - " ", - "
    " , - "
    ", - " "].join("")); - }; - - SingleSelect2.prototype.open = function () { - - if (this.opened()) return; - - this.parent.open.apply(this, arguments); - - }; - - SingleSelect2.prototype.close = function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - }; - - SingleSelect2.prototype.cancel = function () { - this.parent.cancel.apply(this, arguments); - this.selection.focus(); - }; - - SingleSelect2.prototype.initContainer = function () { - - var selection, container = this.container, clickingInside = false, - selector = ".select2-choice", selected; - - this.selection = selection = container.find(selector); - - this.search.bind("keydown", this.bind(function (e) { - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.TAB: - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.ESC: - this.cancel(e); - e.preventDefault(); - return; - } - })); - - container.delegate(selector, "click", this.bind(function (e) { - clickingInside = true; - - if (this.opened()) { - this.close(); - selection.focus(); - } else { - this.open(); - } - e.preventDefault(); - - clickingInside = false; - })); - container.delegate(selector, "keydown", this.bind(function (e) { - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { - return; - } - this.open(); - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN || e.which === KEY.SPACE) { - // prevent the page from scrolling - killEvent(e); - } - if (e.which === KEY.ENTER) { - // do not propagate the event otherwise we open, and propagate enter which closes - killEvent(e); - } - })); - container.delegate(selector, "focus", function () { container.addClass("select2-container-active"); }); - container.delegate(selector, "blur", this.bind(function () { - if (clickingInside) return; - if (!this.opened()) this.blur(); - })); - - selection.delegate("abbr", "click", this.bind(function (e) { - this.val(""); - killEvent(e); - this.close(); - this.triggerChange(); - })); - - this.setPlaceholder(); - }; - - /** - * Sets selection based on source element's value - */ - SingleSelect2.prototype.initSelection = function () { - var selected; - if (this.opts.element.val() === "") { - this.updateSelection({id: "", text: ""}); - } else { - selected = this.opts.initSelection.call(null, this.opts.element); - if (selected !== undefined && selected !== null) { - this.updateSelection(selected); - } - } - - this.close(); - this.setPlaceholder(); - }; - - SingleSelect2.prototype.prepareOpts = function () { - var opts = this.parent.prepareOpts.apply(this, arguments); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install sthe selection initializer - this.opts.initSelection = function (element) { - var selected = element.find(":selected"); - // a single select box always has a value, no need to null check 'selected' - return {id: selected.attr("value"), text: selected.text()}; - }; - } - - return opts; - }; - - SingleSelect2.prototype.setPlaceholder = function () { - var placeholder = this.getPlaceholder(); - - if (this.opts.element.val() === "" && placeholder !== undefined) { - - // check for a first blank option if attached to a select - if (this.select && this.select.find("option:first").text() !== "") return; - - if (typeof(placeholder) === "object") { - this.updateSelection(placeholder); - } else { - this.selection.find("span").html(placeholder); - } - this.selection.addClass("select2-default"); - - this.selection.find("abbr").hide(); - } - }; - - SingleSelect2.prototype.postprocessResults = function (data, initial) { - var selected = 0, self = this; - - // find the selected element in the result list - - this.results.find(".select2-result").each(function (i) { - if (equal($(this).data("select2-data").id, self.opts.element.val())) { - selected = i; - return false; - } - }); - - // and highlight it - - this.highlight(selected); - - // hide the search box if this is the first we got the results and there are a few of them - - if (initial === true) { - this.search.parent().toggle(data.results.length >= this.opts.minimumResultsForSearch); - } - - }; - - SingleSelect2.prototype.onSelect = function (data) { - var old = this.opts.element.val(); - - this.opts.element.val(data.id); - this.updateSelection(data); - this.close(); - this.selection.focus(); - - if (!equal(old, data.id)) { this.triggerChange(); } - }; - - SingleSelect2.prototype.updateSelection = function (data) { - this.selection - .find("span") - .html(this.opts.formatSelection(data)); - - this.selection.removeClass("select2-default"); - - if (this.opts.allowClear && this.getPlaceholder() !== undefined) { - this.selection.find("abbr").show(); - } - }; - - SingleSelect2.prototype.val = function () { - var val, data = null; - - if (arguments.length === 0) { - return this.opts.element.val(); - } - - val = arguments[0]; - - if (this.select) { - // val is an id - this.select - .val(val) - .find(":selected").each(function () { - data = {id: $(this).attr("value"), text: $(this).text()}; - return false; - }); - this.updateSelection(data); - } else { - // val is an object - this.opts.element.val((val === null) ? "" : val.id); - this.updateSelection(val); - } - this.setPlaceholder(); - - }; - - SingleSelect2.prototype.clearSearch = function () { - this.search.val(""); - }; - - function MultiSelect2(opts) { - - } - - MultiSelect2.prototype = new AbstractSelect2(); - MultiSelect2.prototype.constructor = AbstractSelect2; - MultiSelect2.prototype.parent = AbstractSelect2.prototype; - - MultiSelect2.prototype.createContainer = function () { - return $("
    ", { - "class": "select2-container select2-container-multi", - "style": "width: " + this.getContainerWidth() - }).html([ - " " , - ""].join("")); - }; - - MultiSelect2.prototype.prepareOpts = function () { - var opts = this.parent.prepareOpts.apply(this, arguments); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install sthe selection initializer - this.opts.initSelection = function (element) { - var data = []; - element.find(":selected").each(function () { - data.push({id: $(this).attr("value"), text: $(this).text()}); - }); - return data; - }; - } - - return opts; - }; - - MultiSelect2.prototype.initContainer = function () { - - var selector = ".select2-choices", selection, data; - - this.searchContainer = this.container.find(".select2-search-field"); - this.selection = selection = this.container.find(selector); - - this.search.bind("keydown", this.bind(function (e) { - if (e.which === KEY.BACKSPACE && this.search.val() === "") { - this.close(); - - var choices, - selected = this.selection.find(".select2-search-choice-focus"); - if (selected.length > 0) { - this.unselect(selected.first()); - this.search.width(10); - killEvent(e); + if (data.results.length === 0) { + render("
  • " + opts.formatNoMatches(search.val()) + "
  • "); return; } - choices = this.selection.find(".select2-search-choice"); - if (choices.length > 0) { - choices.last().addClass("select2-search-choice-focus"); - } - } else { - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - } + $(data.results).each(function () { + parts.push("
  • "); + parts.push(opts.formatResult(this)); + parts.push("
  • "); + }); - if (this.opened()) { + if (data.more === true) { + parts.push("
  • Loading more results...
  • "); + } + + render(parts.join("")); + results.children(".select2-result").each(function (i) { + var d = data.results[i]; + $(this).data("select2-data", d); + }); + this.postprocessResults(data, initial); + })}); + }, + + cancel: function () { + this.close(); + }, + + blur: function () { + /* we do this in a timeout so that current event processing can complete before this code is executed. + this allows tab index to be preserved even if this code blurs the textfield */ + window.setTimeout(this.bind(function () { + this.close(); + this.container.removeClass("select2-container-active"); + this.clearSearch(); + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + this.search.blur(); + }), 10); + }, + + focusSearch: function () { + /* we do this in a timeout so that current event processing can complete before this code is executed. + this makes sure the search field is focussed even if the current event would blur it */ + window.setTimeout(this.bind(function () { + this.search.focus(); + }), 10); + }, + + selectHighlighted: function () { + var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data"); + if (data) { + this.onSelect(data); + } + }, + + getPlaceholder: function () { + var placeholder = this.opts.element.data("placeholder"); + if (placeholder !== undefined) return placeholder; + return this.opts.placeholder; + }, + + /** + * Get the desired width for the container element. This is + * derived first from option `width` passed to select2, then + * the inline 'style' on the original element, and finally + * falls back to the jQuery calculated element width. + * + * @returns The width string (with units) for the container. + */ + getContainerWidth: function () { + if (this.opts.width !== undefined) + return this.opts.width; + + var style = this.opts.element.attr('style'); + if (style !== undefined) { + var attrs = style.split(';'); + for (var i = 0; i < attrs.length; i++) { + var matches = attrs[i].replace(/\s/g, '') + .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/); + if (matches != null && matches.length >= 1) + return matches[1]; + } + } + return this.opts.element.width() + 'px'; + } + }); + + SingleSelect2 = clazz(AbstractSelect2, { + + createContainer: function () { + return $("
    ", { + "class": "select2-container", + "style": "width: " + this.getContainerWidth() + }).html([ + " ", + " ", + "
    " , + "
    ", + " "].join("")); + }, + + open: function () { + + if (this.opened()) return; + + this.parent.open.apply(this, arguments); + + }, + + close: function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }, + + cancel: function () { + this.parent.cancel.apply(this, arguments); + this.selection.focus(); + }, + + initContainer: function () { + + var selection, container = this.container, clickingInside = false, + selector = ".select2-choice", selected; + + this.selection = selection = container.find(selector); + + this.search.bind("keydown", this.bind(function (e) { switch (e.which) { case KEY.UP: case KEY.DOWN: this.moveHighlight((e.which === KEY.UP) ? -1 : 1); killEvent(e); return; + case KEY.TAB: case KEY.ENTER: this.selectHighlighted(); killEvent(e); @@ -1089,272 +837,529 @@ e.preventDefault(); return; } - } + })); - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { - return; - } + container.delegate(selector, "click", this.bind(function (e) { + clickingInside = true; - this.open(); + if (this.opened()) { + this.close(); + selection.focus(); + } else { + this.open(); + } + e.preventDefault(); - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling + clickingInside = false; + })); + container.delegate(selector, "keydown", this.bind(function (e) { + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { + return; + } + this.open(); + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN || e.which === KEY.SPACE) { + // prevent the page from scrolling + killEvent(e); + } + if (e.which === KEY.ENTER) { + // do not propagate the event otherwise we open, and propagate enter which closes + killEvent(e); + } + })); + container.delegate(selector, "focus", function () { container.addClass("select2-container-active"); }); + container.delegate(selector, "blur", this.bind(function () { + if (clickingInside) return; + if (!this.opened()) this.blur(); + })); + + selection.delegate("abbr", "click", this.bind(function (e) { + this.val(""); killEvent(e); + this.close(); + this.triggerChange(); + })); + + this.setPlaceholder(); + }, + + /** + * Sets selection based on source element's value + */ + initSelection: function () { + var selected; + if (this.opts.element.val() === "") { + this.updateSelection({id: "", text: ""}); + } else { + selected = this.opts.initSelection.call(null, this.opts.element); + if (selected !== undefined && selected !== null) { + this.updateSelection(selected); + } } - })); - this.search.bind("keyup", this.bind(this.resizeSearch)); + this.close(); + this.setPlaceholder(); + }, - this.container.delegate(selector, "click", this.bind(function (e) { - this.open(); - this.focusSearch(); - e.preventDefault(); - })); + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments); - this.container.delegate(selector, "focus", this.bind(function () { - this.container.addClass("select2-container-active"); - this.clearPlaceholder(); - })); + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install sthe selection initializer + this.opts.initSelection = function (element) { + var selected = element.find(":selected"); + // a single select box always has a value, no need to null check 'selected' + return {id: selected.attr("value"), text: selected.text()}; + }; + } - // set the placeholder if necessary - this.clearSearch(); - }; + return opts; + }, - MultiSelect2.prototype.initSelection = function () { - var data; - if (this.opts.element.val() === "") { - this.updateSelection([]); - } - if (this.select || this.opts.element.val() !== "") { - data = this.opts.initSelection.call(null, this.opts.element); - if (data !== undefined && data != null) { + setPlaceholder: function () { + var placeholder = this.getPlaceholder(); + + if (this.opts.element.val() === "" && placeholder !== undefined) { + + // check for a first blank option if attached to a select + if (this.select && this.select.find("option:first").text() !== "") return; + + if (typeof(placeholder) === "object") { + this.updateSelection(placeholder); + } else { + this.selection.find("span").html(placeholder); + } + this.selection.addClass("select2-default"); + + this.selection.find("abbr").hide(); + } + }, + + postprocessResults: function (data, initial) { + var selected = 0, self = this; + + // find the selected element in the result list + + this.results.find(".select2-result").each(function (i) { + if (equal($(this).data("select2-data").id, self.opts.element.val())) { + selected = i; + return false; + } + }); + + // and highlight it + + this.highlight(selected); + + // hide the search box if this is the first we got the results and there are a few of them + + if (initial === true) { + this.search.parent().toggle(data.results.length >= this.opts.minimumResultsForSearch); + } + + }, + + onSelect: function (data) { + var old = this.opts.element.val(); + + this.opts.element.val(data.id); + this.updateSelection(data); + this.close(); + this.selection.focus(); + + if (!equal(old, data.id)) { this.triggerChange(); } + }, + + updateSelection: function (data) { + this.selection + .find("span") + .html(this.opts.formatSelection(data)); + + this.selection.removeClass("select2-default"); + + if (this.opts.allowClear && this.getPlaceholder() !== undefined) { + this.selection.find("abbr").show(); + } + }, + + val: function () { + var val, data = null; + + if (arguments.length === 0) { + return this.opts.element.val(); + } + + val = arguments[0]; + + if (this.select) { + // val is an id + this.select + .val(val) + .find(":selected").each(function () { + data = {id: $(this).attr("value"), text: $(this).text()}; + return false; + }); this.updateSelection(data); + } else { + // val is an object + this.opts.element.val((val === null) ? "" : val.id); + this.updateSelection(val); } + this.setPlaceholder(); + + }, + + clearSearch: function () { + this.search.val(""); } + }); - this.close(); + MultiSelect2 = clazz(AbstractSelect2, { - // set the placeholder if necessary - this.clearSearch(); - }; + createContainer: function () { + return $("
    ", { + "class": "select2-container select2-container-multi", + "style": "width: " + this.getContainerWidth() + }).html([ + " " , + ""].join("")); + }, - MultiSelect2.prototype.clearSearch = function () { - var placeholder = this.getPlaceholder(); + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments); - this.search.val("").width(10); + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install sthe selection initializer + this.opts.initSelection = function (element) { + var data = []; + element.find(":selected").each(function () { + data.push({id: $(this).attr("value"), text: $(this).text()}); + }); + return data; + }; + } - if (placeholder !== undefined && this.getVal().length === 0) { - this.search.val(placeholder).addClass("select2-default"); + return opts; + }, + + initContainer: function () { + + var selector = ".select2-choices", selection, data; + + this.searchContainer = this.container.find(".select2-search-field"); + this.selection = selection = this.container.find(selector); + + this.search.bind("keydown", this.bind(function (e) { + if (e.which === KEY.BACKSPACE && this.search.val() === "") { + this.close(); + + var choices, + selected = this.selection.find(".select2-search-choice-focus"); + if (selected.length > 0) { + this.unselect(selected.first()); + this.search.width(10); + killEvent(e); + return; + } + + choices = this.selection.find(".select2-search-choice"); + if (choices.length > 0) { + choices.last().addClass("select2-search-choice-focus"); + } + } else { + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + } + + if (this.opened()) { + switch (e.which) { + case KEY.UP: + case KEY.DOWN: + this.moveHighlight((e.which === KEY.UP) ? -1 : 1); + killEvent(e); + return; + case KEY.ENTER: + this.selectHighlighted(); + killEvent(e); + return; + case KEY.ESC: + this.cancel(e); + e.preventDefault(); + return; + } + } + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { + return; + } + + this.open(); + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + } + })); + + this.search.bind("keyup", this.bind(this.resizeSearch)); + + this.container.delegate(selector, "click", this.bind(function (e) { + this.open(); + this.focusSearch(); + e.preventDefault(); + })); + + this.container.delegate(selector, "focus", this.bind(function () { + this.container.addClass("select2-container-active"); + this.clearPlaceholder(); + })); + + // set the placeholder if necessary + this.clearSearch(); + }, + + initSelection: function () { + var data; + if (this.opts.element.val() === "") { + this.updateSelection([]); + } + if (this.select || this.opts.element.val() !== "") { + data = this.opts.initSelection.call(null, this.opts.element); + if (data !== undefined && data != null) { + this.updateSelection(data); + } + } + + this.close(); + + // set the placeholder if necessary + this.clearSearch(); + }, + + clearSearch: function () { + var placeholder = this.getPlaceholder(); + + this.search.val("").width(10); + + if (placeholder !== undefined && this.getVal().length === 0) { + this.search.val(placeholder).addClass("select2-default"); + this.resizeSearch(); + } + }, + + clearPlaceholder: function () { + if (this.search.hasClass("select2-default")) { + this.search.val("").removeClass("select2-default"); + } + }, + + open: function () { + if (this.opened()) return; + this.parent.open.apply(this, arguments); this.resizeSearch(); - } - }; + this.focusSearch(); + }, - MultiSelect2.prototype.clearPlaceholder = function () { - if (this.search.hasClass("select2-default")) { - this.search.val("").removeClass("select2-default"); - } - }; + close: function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }, - MultiSelect2.prototype.open = function () { - if (this.opened()) return; - this.parent.open.apply(this, arguments); - this.resizeSearch(); - this.focusSearch(); - }; + updateSelection: function (data) { + var ids = [], filtered = [], self = this; - MultiSelect2.prototype.close = function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - }; + // filter out duplicates + $(data).each(function () { + if (indexOf(this.id, ids) < 0) { + ids.push(this.id); + filtered.push(this); + } + }); + data = filtered; - MultiSelect2.prototype.updateSelection = function (data) { - var ids = [], filtered = [], self = this; + this.selection.find(".select2-search-choice").remove(); + $(data).each(function () { + self.addSelectedChoice(this); + }); + self.postprocessResults(); + this.alignDropdown(); + }, - // filter out duplicates - $(data).each(function () { - if (indexOf(this.id, ids) < 0) { - ids.push(this.id); - filtered.push(this); - } - }); - data = filtered; + onSelect: function (data) { + this.addSelectedChoice(data); + if (this.select) { this.postprocessResults(); } + this.close(); + this.search.width(10); - this.selection.find(".select2-search-choice").remove(); - $(data).each(function () { - self.addSelectedChoice(this); - }); - self.postprocessResults(); - this.alignDropdown(); - }; + // since its not possible to select an element that has already been + // added we do not need to check if this is a new element before firing change + this.triggerChange(); - MultiSelect2.prototype.onSelect = function (data) { - this.addSelectedChoice(data); - if (this.select) { this.postprocessResults(); } - this.close(); - this.search.width(10); + this.focusSearch(); + }, - // since its not possible to select an element that has already been - // added we do not need to check if this is a new element before firing change - this.triggerChange(); - - this.focusSearch(); - }; - - MultiSelect2.prototype.cancel = function () { - this.close(); - this.focusSearch(); - }; - - MultiSelect2.prototype.addSelectedChoice = function (data) { - var choice, - id = data.id, - parts, - val = this.getVal(); - - parts = ["
  • ", - this.opts.formatSelection(data), - "", - "
  • " - ]; - - choice = $(parts.join("")); - choice.find("a") - .bind("click dblclick", this.bind(function (e) { - this.unselect($(e.target)); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - killEvent(e); + cancel: function () { this.close(); this.focusSearch(); - })).bind("focus", this.bind(function () { - this.container.addClass("select2-container-active"); - })); + }, - choice.data("select2-data", data); - choice.insertBefore(this.searchContainer); + addSelectedChoice: function (data) { + var choice, + id = data.id, + parts, + val = this.getVal(); - val.push(id); - this.setVal(val); - }; + parts = ["
  • ", + this.opts.formatSelection(data), + "", + "
  • " + ]; - MultiSelect2.prototype.unselect = function (selected) { - var val = this.getVal(), - index; + choice = $(parts.join("")); + choice.find("a") + .bind("click dblclick", this.bind(function (e) { + this.unselect($(e.target)); + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + killEvent(e); + this.close(); + this.focusSearch(); + })).bind("focus", this.bind(function () { + this.container.addClass("select2-container-active"); + })); - selected = selected.closest(".select2-search-choice"); + choice.data("select2-data", data); + choice.insertBefore(this.searchContainer); - if (selected.length === 0) { - throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; - } - - index = indexOf(selected.data("select2-data").id, val); - - if (index >= 0) { - val.splice(index, 1); + val.push(id); this.setVal(val); - if (this.select) this.postprocessResults(); - } - selected.remove(); - this.triggerChange(); - window.setTimeout(this.bind(this.alignDropdown), 20); - }; + }, - MultiSelect2.prototype.postprocessResults = function () { - var val = this.getVal(), - choices = this.results.find(".select2-result"), - self = this; + unselect: function (selected) { + var val = this.getVal(), + index; - choices.each(function () { - var choice = $(this), id = choice.data("select2-data").id; - if (indexOf(id, val) >= 0) { - choice.addClass("select2-disabled"); + selected = selected.closest(".select2-search-choice"); + + if (selected.length === 0) { + throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; + } + + index = indexOf(selected.data("select2-data").id, val); + + if (index >= 0) { + val.splice(index, 1); + this.setVal(val); + if (this.select) this.postprocessResults(); + } + selected.remove(); + this.triggerChange(); + window.setTimeout(this.bind(this.alignDropdown), 20); + }, + + postprocessResults: function () { + var val = this.getVal(), + choices = this.results.find(".select2-result"), + self = this; + + choices.each(function () { + var choice = $(this), id = choice.data("select2-data").id; + if (indexOf(id, val) >= 0) { + choice.addClass("select2-disabled"); + } else { + choice.removeClass("select2-disabled"); + } + }); + + choices.each(function (i) { + if (!$(this).hasClass("select2-disabled")) { + self.highlight(i); + return false; + } + }); + + }, + + resizeSearch: function () { + + var minimumWidth, left, maxWidth, containerLeft, searchWidth; + + minimumWidth = measureTextWidth(this.search) + 10; + + left = this.search.offset().left; + + maxWidth = this.selection.width(); + containerLeft = this.selection.offset().left; + + searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search); + + if (searchWidth < minimumWidth) { + searchWidth = maxWidth - getSideBorderPadding(this.search); + } + + if (searchWidth < 40) { + searchWidth = maxWidth - getSideBorderPadding(this.search); + } + this.search.width(searchWidth); + }, + + getVal: function () { + var val; + if (this.select) { + val = this.select.val(); + return val === null ? [] : val; } else { - choice.removeClass("select2-disabled"); + val = this.opts.element.val(); + return (val === null || val === "") ? [] : val.split(","); } - }); + }, - choices.each(function (i) { - if (!$(this).hasClass("select2-disabled")) { - self.highlight(i); - return false; + setVal: function (val) { + var unique = []; + if (this.select) { + this.select.val(val); + } else { + // filter out duplicates + $(val).each(function () { + if (indexOf(this, unique) < 0) unique.push(this); + }); + + this.opts.element.val(unique.length === 0 ? "" : unique.join(",")); } - }); + }, - }; + val: function () { + var val, data = []; - MultiSelect2.prototype.resizeSearch = function () { + if (arguments.length === 0) { + return this.getVal(); + } - var minimumWidth, left, maxWidth, containerLeft, searchWidth; + val = arguments[0]; - minimumWidth = measureTextWidth(this.search) + 10; + if (this.select) { + // val is a list of ids + this.setVal(val); + this.select.find(":selected").each(function () { + data.push({id: $(this).attr("value"), text: $(this).text()}); + }); + this.updateSelection(data); + } else { + val = (val === null) ? [] : val; + this.setVal(val); + // val is a list of objects - left = this.search.offset().left; - - maxWidth = this.selection.width(); - containerLeft = this.selection.offset().left; - - searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search); - - if (searchWidth < minimumWidth) { - searchWidth = maxWidth - getSideBorderPadding(this.search); + $(val).each(function () { data.push(this.id); }); + this.setVal(data); + this.updateSelection(val); + } } - - if (searchWidth < 40) { - searchWidth = maxWidth - getSideBorderPadding(this.search); - } - this.search.width(searchWidth); - }; - - MultiSelect2.prototype.getVal = function () { - var val; - if (this.select) { - val = this.select.val(); - return val === null ? [] : val; - } else { - val = this.opts.element.val(); - return (val === null || val === "") ? [] : val.split(","); - } - }; - - MultiSelect2.prototype.setVal = function (val) { - var unique = []; - if (this.select) { - this.select.val(val); - } else { - // filter out duplicates - $(val).each(function () { - if (indexOf(this, unique) < 0) unique.push(this); - }); - - this.opts.element.val(unique.length === 0 ? "" : unique.join(",")); - } - }; - - MultiSelect2.prototype.val = function () { - var val, data = []; - - if (arguments.length === 0) { - return this.getVal(); - } - - val = arguments[0]; - - if (this.select) { - // val is a list of ids - this.setVal(val); - this.select.find(":selected").each(function () { - data.push({id: $(this).attr("value"), text: $(this).text()}); - }); - this.updateSelection(data); - } else { - val = (val === null) ? [] : val; - this.setVal(val); - // val is a list of objects - - $(val).each(function () { data.push(this.id); }); - this.setVal(data); - this.updateSelection(val); - } - }; + }); $.fn.select2 = function () {