element.");
+ }
+ });
+ }
+
+ opts = $.extend({}, {
+ populateResults: function(container, results, query) {
+ var populate, data, result, children, id=this.opts.id, self=this;
+
+ populate=function(results, container, depth) {
+
+ var i, l, result, selectable, compound, node, label, innerContainer, formatted;
+ for (i = 0, l = results.length; i < l; i = i + 1) {
+
+ result=results[i];
+ selectable=id(result) !== undefined;
+ compound=result.children && result.children.length > 0;
+
+ node=$(" ");
+ node.addClass("select2-results-dept-"+depth);
+ node.addClass("select2-result");
+ node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
+ if (compound) { node.addClass("select2-result-with-children"); }
+ node.addClass(self.opts.formatResultCssClass(result));
+
+ label=$("
");
+ label.addClass("select2-result-label");
+
+ formatted=opts.formatResult(result, label, query);
+ if (formatted!==undefined) {
+ label.html(self.opts.escapeMarkup(formatted));
+ }
+
+ node.append(label);
+
+ if (compound) {
+
+ innerContainer=$("");
+ innerContainer.addClass("select2-result-sub");
+ populate(result.children, innerContainer, depth+1);
+ node.append(innerContainer);
+ }
+
+ node.data("select2-data", result);
+ container.append(node);
+ }
+ };
+
+ populate(results, container, 0);
+ }
+ }, $.fn.select2.defaults, opts);
+
+ if (typeof(opts.id) !== "function") {
+ idKey = opts.id;
+ opts.id = function (e) { return e[idKey]; };
+ }
+
+ if (select) {
+ opts.query = this.bind(function (query) {
+ var data = { results: [], more: false },
+ term = query.term,
+ children, firstChild, process;
+
+ process=function(element, collection) {
+ var group;
+ if (element.is("option")) {
+ if (query.matcher(term, element.text(), element)) {
+ collection.push({id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class")});
+ }
+ } else if (element.is("optgroup")) {
+ group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")};
+ element.children().each2(function(i, elm) { process(elm, group.children); });
+ if (group.children.length>0) {
+ collection.push(group);
+ }
+ }
+ };
+
+ children=element.children();
+
+ // ignore the placeholder option if there is one
+ if (this.getPlaceholder() !== undefined && children.length > 0) {
+ firstChild = children[0];
+ if ($(firstChild).text() === "") {
+ children=children.not(firstChild);
+ }
+ }
+
+ children.each2(function(i, elm) { process(elm, data.results); });
+
+ query.callback(data);
+ });
+ // this is needed because inside val() we construct choices from options and there id is hardcoded
+ opts.id=function(e) { return e.id; };
+ opts.formatResultCssClass = function(data) { return data.css; }
+ } else {
+ if (!("query" in opts)) {
+ if ("ajax" in opts) {
+ ajaxUrl = opts.element.data("ajax-url");
+ if (ajaxUrl && ajaxUrl.length > 0) {
+ opts.ajax.url = ajaxUrl;
+ }
+ 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, callback) {
+ var data = [];
+ $(splitVal(element.val(), opts.separator)).each(function () {
+ var id = this, text = this, tags=opts.tags;
+ if ($.isFunction(tags)) tags=tags();
+ $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } });
+ data.push({id: id, text: text});
+ });
+
+ callback(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
+ */
+ // abstract
+ monitorSource: function () {
+ this.opts.element.bind("change.select2", this.bind(function (e) {
+ if (this.opts.element.data("select2-change-triggered") !== true) {
+ this.initSelection();
+ }
+ }));
+ },
+
+ /**
+ * Triggers the change event on the source element
+ */
+ // abstract
+ triggerChange: function (details) {
+
+ details = details || {};
+ details= $.extend({}, details, { type: "change", val: this.val() });
+ // prevents recursive triggering
+ this.opts.element.data("select2-change-triggered", true);
+ this.opts.element.trigger(details);
+ this.opts.element.data("select2-change-triggered", false);
+
+ // some validation frameworks ignore the change event and listen instead to keyup, click for selects
+ // so here we trigger the click event manually
+ this.opts.element.click();
+
+ // ValidationEngine ignorea the change event and listens instead to blur
+ // so here we trigger the blur event manually if so desired
+ if (this.opts.blurOnChange)
+ this.opts.element.blur();
+ },
+
+
+ // abstract
+ enable: function() {
+ if (this.enabled) return;
+
+ this.enabled=true;
+ this.container.removeClass("select2-container-disabled");
+ },
+
+ // abstract
+ disable: function() {
+ if (!this.enabled) return;
+
+ this.close();
+
+ this.enabled=false;
+ this.container.addClass("select2-container-disabled");
+ },
+
+ // abstract
+ opened: function () {
+ return this.container.hasClass("select2-dropdown-open");
+ },
+
+ // abstract
+ positionDropdown: function() {
+ var offset = this.container.offset(),
+ height = this.container.outerHeight(),
+ width = this.container.outerWidth(),
+ dropHeight = this.dropdown.outerHeight(),
+ viewportBottom = $(window).scrollTop() + document.documentElement.clientHeight,
+ dropTop = offset.top + height,
+ dropLeft = offset.left,
+ enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
+ enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
+ aboveNow = this.dropdown.hasClass("select2-drop-above"),
+ bodyOffset,
+ above,
+ css;
+
+ // console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
+ // console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
+
+ // fix positioning when body has an offset and is not position: static
+
+ if (this.body().css('position') !== 'static') {
+ bodyOffset = this.body().offset();
+ dropTop -= bodyOffset.top;
+ dropLeft -= bodyOffset.left;
+ }
+
+ // always prefer the current above/below alignment, unless there is not enough room
+
+ if (aboveNow) {
+ above = true;
+ if (!enoughRoomAbove && enoughRoomBelow) above = false;
+ } else {
+ above = false;
+ if (!enoughRoomBelow && enoughRoomAbove) above = true;
+ }
+
+ if (above) {
+ dropTop = offset.top - dropHeight;
+ this.container.addClass("select2-drop-above");
+ this.dropdown.addClass("select2-drop-above");
+ }
+ else {
+ this.container.removeClass("select2-drop-above");
+ this.dropdown.removeClass("select2-drop-above");
+ }
+
+ css = $.extend({
+ top: dropTop,
+ left: dropLeft,
+ width: width
+ }, evaluate(this.opts.dropdownCss));
+
+ this.dropdown.css(css);
+ },
+
+ // abstract
+ shouldOpen: function() {
+ var event;
+
+ if (this.opened()) return false;
+
+ event = $.Event("open");
+ this.opts.element.trigger(event);
+ return !event.isDefaultPrevented();
+ },
+
+ // abstract
+ clearDropdownAlignmentPreference: function() {
+ // clear the classes used to figure out the preference of where the dropdown should be opened
+ this.container.removeClass("select2-drop-above");
+ this.dropdown.removeClass("select2-drop-above");
+ },
+
+ /**
+ * Opens the dropdown
+ *
+ * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
+ * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
+ */
+ // abstract
+ open: function () {
+
+ if (!this.shouldOpen()) return false;
+
+ window.setTimeout(this.bind(this.opening), 1);
+
+ return true;
+ },
+
+ /**
+ * Performs the opening of the dropdown
+ */
+ // abstract
+ opening: function() {
+ var cid = this.containerId, selector = this.containerSelector,
+ scroll = "scroll." + cid, resize = "resize." + cid;
+
+ this.container.parents().each(function() {
+ $(this).bind(scroll, function() {
+ var s2 = $(selector);
+ if (s2.length == 0) {
+ $(this).unbind(scroll);
+ }
+ s2.select2("close");
+ });
+ });
+
+ $(window).bind(resize, function() {
+ var s2 = $(selector);
+ if (s2.length == 0) {
+ $(window).unbind(resize);
+ }
+ s2.select2("close");
+ });
+
+ this.clearDropdownAlignmentPreference();
+
+ if (this.search.val() === " ") { this.search.val(""); }
+
+ this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
+
+ this.updateResults(true);
+
+ if(this.dropdown[0] !== this.body().children().last()[0]) {
+ this.dropdown.detach().appendTo(this.body());
+ }
+
+ this.dropdown.show();
+
+ this.positionDropdown();
+ this.dropdown.addClass("select2-drop-active");
+
+ this.ensureHighlightVisible();
+
+ this.focusSearch();
+ },
+
+ // abstract
+ close: function () {
+ if (!this.opened()) return;
+
+ var self = this;
+
+ this.container.parents().each(function() {
+ $(this).unbind("scroll." + self.containerId);
+ });
+ $(window).unbind("resize." + this.containerId);
+
+ this.clearDropdownAlignmentPreference();
+
+ this.dropdown.hide();
+ this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
+ this.results.empty();
+ this.clearSearch();
+
+ this.opts.element.trigger($.Event("close"));
+ },
+
+ // abstract
+ clearSearch: function () {
+
+ },
+
+ // abstract
+ ensureHighlightVisible: function () {
+ var results = this.results, children, index, child, hb, rb, y, more;
+
+ index = this.highlight();
+
+ if (index < 0) return;
+
+ if (index == 0) {
+
+ // if the first element is highlighted scroll all the way to the top,
+ // that way any unselectable headers above it will also be scrolled
+ // into view
+
+ results.scrollTop(0);
+ return;
+ }
+
+ children = results.find(".select2-result-selectable");
+
+ 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
+ }
+ },
+
+ // abstract
+ moveHighlight: function (delta) {
+ var choices = this.results.find(".select2-result-selectable"),
+ index = this.highlight();
+
+ while (index > -1 && index < choices.length) {
+ index += delta;
+ var choice = $(choices[index]);
+ if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled")) {
+ this.highlight(index);
+ break;
+ }
+ }
+ },
+
+ // abstract
+ highlight: function (index) {
+ var choices = this.results.find(".select2-result-selectable").not(".select2-disabled");
+
+ if (arguments.length === 0) {
+ return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
+ }
+
+ if (index >= choices.length) index = choices.length - 1;
+ if (index < 0) index = 0;
+
+ choices.removeClass("select2-highlighted");
+
+ $(choices[index]).addClass("select2-highlighted");
+ this.ensureHighlightVisible();
+
+ },
+
+ // abstract
+ countSelectableResults: function() {
+ return this.results.find(".select2-result-selectable").not(".select2-disabled").length;
+ },
+
+ // abstract
+ highlightUnderEvent: function (event) {
+ var el = $(event.target).closest(".select2-result-selectable");
+ if (el.length > 0 && !el.is(".select2-highlighted")) {
+ var choices = this.results.find('.select2-result-selectable');
+ this.highlight(choices.index(el));
+ } else if (el.length == 0) {
+ // if we are over an unselectable item remove al highlights
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
+ }
+ },
+
+ // abstract
+ 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,
+ self=this,
+ term=this.search.val(),
+ context=this.context;
+
+ 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: term,
+ page: page,
+ context: context,
+ matcher: this.opts.matcher,
+ callback: this.bind(function (data) {
+
+ // ignore a response if the select2 has been closed before it was received
+ if (!self.opened()) return;
+
+
+ self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
+
+ if (data.more===true) {
+ more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
+ } else {
+ more.remove();
+ }
+ self.positionDropdown();
+ self.resultsPage = page;
+ })});
+ }
+ },
+
+ /**
+ * Default tokenizer function which does nothing
+ */
+ tokenize: function() {
+
+ },
+
+ /**
+ * @param initial whether or not this is the call to this method right after the dropdown has been opened
+ */
+ // abstract
+ updateResults: function (initial) {
+ var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
+
+ // if the search is currently hidden we do not alter the results
+ if (initial !== true && (this.showSearchInput === false || !this.opened())) {
+ return;
+ }
+
+ search.addClass("select2-active");
+
+ function postRender() {
+ results.scrollTop(0);
+ search.removeClass("select2-active");
+ self.positionDropdown();
+ }
+
+ function render(html) {
+ results.html(self.opts.escapeMarkup(html));
+ postRender();
+ }
+
+ if (opts.maximumSelectionSize >=1) {
+ data = this.data();
+ if ($.isArray(data) && data.length >= opts.maximumSelectionSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
+ render("" + opts.formatSelectionTooBig(opts.maximumSelectionSize) + " ");
+ return;
+ }
+ }
+
+ if (search.val().length < opts.minimumInputLength && checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
+ render("" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + " ");
+ return;
+ }
+ else {
+ render("" + opts.formatSearching() + " ");
+ }
+
+ // give the tokenizer a chance to pre-process the input
+ input = this.tokenize();
+ if (input != undefined && input != null) {
+ search.val(input);
+ }
+
+ this.resultsPage = 1;
+ opts.query({
+ term: search.val(),
+ page: this.resultsPage,
+ context: null,
+ matcher: opts.matcher,
+ callback: this.bind(function (data) {
+ var def; // default choice
+
+ // ignore a response if the select2 has been closed before it was received
+ if (!this.opened()) return;
+
+ // save context, if any
+ this.context = (data.context===undefined) ? null : data.context;
+
+ // 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 && self.id(def) !== undefined && self.id(def) !== null) {
+ if ($(data.results).filter(
+ function () {
+ return equal(self.id(this), self.id(def));
+ }).length === 0) {
+ data.results.unshift(def);
+ }
+ }
+ }
+
+ if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
+ render("" + opts.formatNoMatches(search.val()) + " ");
+ return;
+ }
+
+ results.empty();
+ self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
+
+ if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
+ results.append("" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + " ");
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
+ }
+
+ this.postprocessResults(data, initial);
+
+ postRender();
+ })});
+ },
+
+ // abstract
+ cancel: function () {
+ this.close();
+ },
+
+ // abstract
+ blur: function () {
+ this.close();
+ this.container.removeClass("select2-container-active");
+ this.dropdown.removeClass("select2-drop-active");
+ // synonymous to .is(':focus'), which is available in jquery >= 1.6
+ if (this.search[0] === document.activeElement) { this.search.blur(); }
+ this.clearSearch();
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+ },
+
+ // abstract
+ focusSearch: function () {
+ // need to do it here as well as in timeout so it works in IE
+ this.search.show();
+ this.search.focus();
+
+ /* 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 () {
+ // reset the value so IE places the cursor at the end of the input box
+ this.search.show();
+ this.search.focus();
+ this.search.val(this.search.val());
+ }), 10);
+ },
+
+ // abstract
+ selectHighlighted: function () {
+ var index=this.highlight(),
+ highlighted=this.results.find(".select2-highlighted").not(".select2-disabled"),
+ data = highlighted.closest('.select2-result-selectable').data("select2-data");
+ if (data) {
+ highlighted.addClass("select2-disabled");
+ this.highlight(index);
+ this.onSelect(data);
+ }
+ },
+
+ // abstract
+ getPlaceholder: function () {
+ return this.opts.element.attr("placeholder") ||
+ this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
+ this.opts.element.data("placeholder") ||
+ 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.
+ */
+ // abstract
+ initContainerWidth: function () {
+ function resolveContainerWidth() {
+ var style, attrs, matches, i, l;
+
+ if (this.opts.width === "off") {
+ return null;
+ } else if (this.opts.width === "element"){
+ return this.opts.element.outerWidth() === 0 ? 'auto' : this.opts.element.outerWidth() + 'px';
+ } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
+ // check if there is inline style on the element that contains width
+ style = this.opts.element.attr('style');
+ if (style !== undefined) {
+ attrs = style.split(';');
+ for (i = 0, l = attrs.length; i < l; i = i + 1) {
+ 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];
+ }
+ }
+
+ if (this.opts.width === "resolve") {
+ // next check if css('width') can resolve a width that is percent based, this is sometimes possible
+ // when attached to input type=hidden or elements hidden via css
+ style = this.opts.element.css('width');
+ if (style.indexOf("%") > 0) return style;
+
+ // finally, fallback on the calculated width of the element
+ return (this.opts.element.outerWidth() === 0 ? 'auto' : this.opts.element.outerWidth() + 'px');
+ }
+
+ return null;
+ } else if ($.isFunction(this.opts.width)) {
+ return this.opts.width();
+ } else {
+ return this.opts.width;
+ }
+ };
+
+ var width = resolveContainerWidth.call(this);
+ if (width !== null) {
+ this.container.attr("style", "width: "+width);
+ }
+ }
+ });
+
+ SingleSelect2 = clazz(AbstractSelect2, {
+
+ // single
+
+ createContainer: function () {
+ var container = $("
", {
+ "class": "select2-container"
+ }).html([
+ " ",
+ " ",
+ "
" ,
+ " ",
+ " " ,
+ "
" ,
+ " " ,
+ "
" ,
+ "
" ,
+ "
"].join(""));
+ return container;
+ },
+
+ // single
+ opening: function () {
+ this.search.show();
+ this.parent.opening.apply(this, arguments);
+ this.dropdown.removeClass("select2-offscreen");
+ },
+
+ // single
+ close: function () {
+ if (!this.opened()) return;
+ this.parent.close.apply(this, arguments);
+ this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show();
+ },
+
+ // single
+ focus: function () {
+ this.close();
+ this.selection.focus();
+ },
+
+ // single
+ isFocused: function () {
+ return this.selection[0] === document.activeElement;
+ },
+
+ // single
+ cancel: function () {
+ this.parent.cancel.apply(this, arguments);
+ this.selection.focus();
+ },
+
+ // single
+ initContainer: function () {
+
+ var selection,
+ container = this.container,
+ dropdown = this.dropdown,
+ clickingInside = false;
+
+ this.selection = selection = container.find(".select2-choice");
+
+ this.search.bind("keydown", this.bind(function (e) {
+ if (!this.enabled) return;
+
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
+ // prevent the page from scrolling
+ killEvent(e);
+ return;
+ }
+
+ 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.TAB:
+ case KEY.ENTER:
+ this.selectHighlighted();
+ killEvent(e);
+ return;
+ case KEY.ESC:
+ this.cancel(e);
+ killEvent(e);
+ return;
+ }
+ } else {
+
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
+ return;
+ }
+
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+ return;
+ }
+
+ this.open();
+
+ if (e.which === KEY.ENTER) {
+ // do not propagate the event otherwise we open, and propagate enter which closes
+ return;
+ }
+ }
+ }));
+
+ this.search.bind("focus", this.bind(function() {
+ this.selection.attr("tabIndex", "-1");
+ }));
+ this.search.bind("blur", this.bind(function() {
+ if (!this.opened()) this.container.removeClass("select2-container-active");
+ window.setTimeout(this.bind(function() { this.selection.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10);
+ }));
+
+ selection.bind("mousedown", this.bind(function (e) {
+ clickingInside = true;
+
+ if (this.opened()) {
+ this.close();
+ this.selection.focus();
+ } else if (this.enabled) {
+ this.open();
+ }
+
+ clickingInside = false;
+ }));
+
+ dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
+
+ selection.bind("focus", this.bind(function() {
+ this.container.addClass("select2-container-active");
+ // hide the search so the tab key does not focus on it
+ this.search.attr("tabIndex", "-1");
+ }));
+
+ selection.bind("blur", this.bind(function() {
+ if (!this.opened()) {
+ this.container.removeClass("select2-container-active");
+ }
+ window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10);
+ }));
+
+ selection.bind("keydown", this.bind(function(e) {
+ if (!this.enabled) return;
+
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
+ // prevent the page from scrolling
+ killEvent(e);
+ return;
+ }
+
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
+ || e.which === KEY.ESC) {
+ return;
+ }
+
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+ return;
+ }
+
+ if (e.which == KEY.DELETE) {
+ if (this.opts.allowClear) {
+ this.clear();
+ }
+ return;
+ }
+
+ this.open();
+
+ if (e.which === KEY.ENTER) {
+ // do not propagate the event otherwise we open, and propagate enter which closes
+ killEvent(e);
+ return;
+ }
+
+ // do not set the search input value for non-alpha-numeric keys
+ // otherwise pressing down results in a '(' being set in the search field
+ if (e.which < 48 ) { // '0' == 48
+ killEvent(e);
+ return;
+ }
+
+ var keyWritten = String.fromCharCode(e.which).toLowerCase();
+
+ if (e.shiftKey) {
+ keyWritten = keyWritten.toUpperCase();
+ }
+
+ // focus the field before calling val so the cursor ends up after the value instead of before
+ this.search.focus();
+ this.search.val(keyWritten);
+
+ // prevent event propagation so it doesnt replay on the now focussed search field and result in double key entry
+ killEvent(e);
+ }));
+
+ selection.delegate("abbr", "mousedown", this.bind(function (e) {
+ if (!this.enabled) return;
+ this.clear();
+ killEvent(e);
+ this.close();
+ this.triggerChange();
+ this.selection.focus();
+ }));
+
+ this.setPlaceholder();
+
+ this.search.bind("focus", this.bind(function() {
+ this.container.addClass("select2-container-active");
+ }));
+ },
+
+ // single
+ clear: function() {
+ this.opts.element.val("");
+ this.selection.find("span").empty();
+ this.selection.removeData("select2-data");
+ this.setPlaceholder();
+ },
+
+ /**
+ * Sets selection based on source element's value
+ */
+ // single
+ initSelection: function () {
+ var selected;
+ if (this.opts.element.val() === "") {
+ this.close();
+ this.setPlaceholder();
+ } else {
+ var self = this;
+ this.opts.initSelection.call(null, this.opts.element, function(selected){
+ if (selected !== undefined && selected !== null) {
+ self.updateSelection(selected);
+ self.close();
+ self.setPlaceholder();
+ }
+ });
+ }
+ },
+
+ // single
+ prepareOpts: function () {
+ var opts = this.parent.prepareOpts.apply(this, arguments);
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ // install the selection initializer
+ opts.initSelection = function (element, callback) {
+ var selected = element.find(":selected");
+ // a single select box always has a value, no need to null check 'selected'
+ if ($.isFunction(callback))
+ callback({id: selected.attr("value"), text: selected.text()});
+ };
+ }
+
+ return opts;
+ },
+
+ // single
+ 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;
+
+ this.selection.find("span").html(this.opts.escapeMarkup(placeholder));
+
+ this.selection.addClass("select2-default");
+
+ this.selection.find("abbr").hide();
+ }
+ },
+
+ // single
+ postprocessResults: function (data, initial) {
+ var selected = 0, self = this, showSearchInput = true;
+
+ // find the selected element in the result list
+
+ this.results.find(".select2-result-selectable").each2(function (i, elm) {
+ if (equal(self.id(elm.data("select2-data")), 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) {
+ showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch;
+ this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden");
+
+ //add "select2-with-searchbox" to the container if search box is shown
+ $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
+ }
+
+ },
+
+ // single
+ onSelect: function (data) {
+ var old = this.opts.element.val();
+
+ this.opts.element.val(this.id(data));
+ this.updateSelection(data);
+ this.close();
+ this.selection.focus();
+
+ if (!equal(old, this.id(data))) { this.triggerChange(); }
+ },
+
+ // single
+ updateSelection: function (data) {
+
+ var container=this.selection.find("span"), formatted;
+
+ this.selection.data("select2-data", data);
+
+ container.empty();
+ formatted=this.opts.formatSelection(data, container);
+ if (formatted !== undefined) {
+ container.append(this.opts.escapeMarkup(formatted));
+ }
+
+ this.selection.removeClass("select2-default");
+
+ if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
+ this.selection.find("abbr").show();
+ }
+ },
+
+ // single
+ val: function () {
+ var val, data = null, self = this;
+
+ if (arguments.length === 0) {
+ return this.opts.element.val();
+ }
+
+ val = arguments[0];
+
+ if (this.select) {
+ this.select
+ .val(val)
+ .find(":selected").each2(function (i, elm) {
+ data = {id: elm.attr("value"), text: elm.text()};
+ return false;
+ });
+ this.updateSelection(data);
+ this.setPlaceholder();
+ } else {
+ if (this.opts.initSelection === undefined) {
+ throw new Error("cannot call val() if initSelection() is not defined");
+ }
+ // val is an id. !val is true for [undefined,null,'']
+ if (!val) {
+ this.clear();
+ return;
+ }
+ this.opts.element.val(val);
+ this.opts.initSelection(this.opts.element, function(data){
+ self.opts.element.val(!data ? "" : self.id(data));
+ self.updateSelection(data);
+ self.setPlaceholder();
+ });
+ }
+ },
+
+ // single
+ clearSearch: function () {
+ this.search.val("");
+ },
+
+ // single
+ data: function(value) {
+ var data;
+
+ if (arguments.length === 0) {
+ data = this.selection.data("select2-data");
+ if (data == undefined) data = null;
+ return data;
+ } else {
+ if (!value || value === "") {
+ this.clear();
+ } else {
+ this.opts.element.val(!value ? "" : this.id(value));
+ this.updateSelection(value);
+ }
+ }
+ }
+ });
+
+ MultiSelect2 = clazz(AbstractSelect2, {
+
+ // multi
+ createContainer: function () {
+ var container = $("
", {
+ "class": "select2-container select2-container-multi"
+ }).html([
+ " " ,
+ ""].join(""));
+ return container;
+ },
+
+ // multi
+ prepareOpts: function () {
+ var opts = this.parent.prepareOpts.apply(this, arguments);
+
+ // TODO validate placeholder is a string if specified
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ // install sthe selection initializer
+ opts.initSelection = function (element,callback) {
+
+ var data = [];
+ element.find(":selected").each2(function (i, elm) {
+ data.push({id: elm.attr("value"), text: elm.text()});
+ });
+
+ if ($.isFunction(callback))
+ callback(data);
+ };
+ }
+
+ return opts;
+ },
+
+ // multi
+ initContainer: function () {
+
+ var selector = ".select2-choices", selection;
+
+ this.searchContainer = this.container.find(".select2-search-field");
+ this.selection = selection = this.container.find(selector);
+
+ this.search.bind("keydown", this.bind(function (e) {
+ if (!this.enabled) return;
+
+ if (e.which === KEY.BACKSPACE && this.search.val() === "") {
+ this.close();
+
+ var choices,
+ selected = selection.find(".select2-search-choice-focus");
+ if (selected.length > 0) {
+ this.unselect(selected.first());
+ this.search.width(10);
+ killEvent(e);
+ return;
+ }
+
+ choices = selection.find(".select2-search-choice");
+ if (choices.length > 0) {
+ choices.last().addClass("select2-search-choice-focus");
+ }
+ } else {
+ 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:
+ case KEY.TAB:
+ this.selectHighlighted();
+ killEvent(e);
+ return;
+ case KEY.ESC:
+ this.cancel(e);
+ killEvent(e);
+ return;
+ }
+ }
+
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
+ || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
+ return;
+ }
+
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+ 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.search.bind("blur", this.bind(function(e) {
+ this.container.removeClass("select2-container-active");
+ this.search.removeClass("select2-focused");
+ this.clearSearch();
+ e.stopImmediatePropagation();
+ }));
+
+ this.container.delegate(selector, "mousedown", this.bind(function (e) {
+ if (!this.enabled) return;
+ if ($(e.target).closest(".select2-search-choice").length > 0) {
+ // clicked inside a select2 search choice, do not open
+ return;
+ }
+ this.clearPlaceholder();
+ this.open();
+ this.focusSearch();
+ e.preventDefault();
+ }));
+
+ this.container.delegate(selector, "focus", this.bind(function () {
+ if (!this.enabled) return;
+ this.container.addClass("select2-container-active");
+ this.dropdown.addClass("select2-drop-active");
+ this.clearPlaceholder();
+ }));
+
+ // set the placeholder if necessary
+ this.clearSearch();
+ },
+
+ // multi
+ enable: function() {
+ if (this.enabled) return;
+
+ this.parent.enable.apply(this, arguments);
+
+ this.search.removeAttr("disabled");
+ },
+
+ // multi
+ disable: function() {
+ if (!this.enabled) return;
+
+ this.parent.disable.apply(this, arguments);
+
+ this.search.attr("disabled", true);
+ },
+
+ // multi
+ initSelection: function () {
+ var data;
+ if (this.opts.element.val() === "") {
+ this.updateSelection([]);
+ this.close();
+ // set the placeholder if necessary
+ this.clearSearch();
+ }
+ if (this.select || this.opts.element.val() !== "") {
+ var self = this;
+ this.opts.initSelection.call(null, this.opts.element, function(data){
+ if (data !== undefined && data !== null) {
+ self.updateSelection(data);
+ self.close();
+ // set the placeholder if necessary
+ self.clearSearch();
+ }
+ });
+ }
+ },
+
+ // multi
+ clearSearch: function () {
+ var placeholder = this.getPlaceholder();
+
+ if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
+ this.search.val(placeholder).addClass("select2-default");
+ // stretch the search box to full width of the container so as much of the placeholder is visible as possible
+ this.resizeSearch();
+ } else {
+ // we set this to " " instead of "" and later clear it on focus() because there is a firefox bug
+ // that does not properly render the caret when the field starts out blank
+ this.search.val(" ").width(10);
+ }
+ },
+
+ // multi
+ clearPlaceholder: function () {
+ if (this.search.hasClass("select2-default")) {
+ this.search.val("").removeClass("select2-default");
+ } else {
+ // work around for the space character we set to avoid firefox caret bug
+ if (this.search.val() === " ") this.search.val("");
+ }
+ },
+
+ // multi
+ opening: function () {
+ this.parent.opening.apply(this, arguments);
+
+ this.clearPlaceholder();
+ this.resizeSearch();
+ this.focusSearch();
+ },
+
+ // multi
+ close: function () {
+ if (!this.opened()) return;
+ this.parent.close.apply(this, arguments);
+ },
+
+ // multi
+ focus: function () {
+ this.close();
+ this.search.focus();
+ },
+
+ // multi
+ isFocused: function () {
+ return this.search.hasClass("select2-focused");
+ },
+
+ // multi
+ updateSelection: function (data) {
+ var ids = [], filtered = [], self = this;
+
+ // filter out duplicates
+ $(data).each(function () {
+ if (indexOf(self.id(this), ids) < 0) {
+ ids.push(self.id(this));
+ filtered.push(this);
+ }
+ });
+ data = filtered;
+
+ this.selection.find(".select2-search-choice").remove();
+ $(data).each(function () {
+ self.addSelectedChoice(this);
+ });
+ self.postprocessResults();
+ },
+
+ tokenize: function() {
+ var input = this.search.val();
+ input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
+ if (input != null && input != undefined) {
+ this.search.val(input);
+ if (input.length > 0) {
+ this.open();
+ }
+ }
+
+ },
+
+ // multi
+ onSelect: function (data) {
+ this.addSelectedChoice(data);
+ if (this.select) { this.postprocessResults(); }
+
+ if (this.opts.closeOnSelect) {
+ this.close();
+ this.search.width(10);
+ } else {
+ if (this.countSelectableResults()>0) {
+ this.search.width(10);
+ this.resizeSearch();
+ this.positionDropdown();
+ } else {
+ // if nothing left to select close
+ this.close();
+ }
+ }
+
+ // 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({ added: data });
+
+ this.focusSearch();
+ },
+
+ // multi
+ cancel: function () {
+ this.close();
+ this.focusSearch();
+ },
+
+ // multi
+ addSelectedChoice: function (data) {
+ var choice=$(
+ "" +
+ "
" +
+ " " +
+ " "),
+ id = this.id(data),
+ val = this.getVal(),
+ formatted;
+
+ formatted=this.opts.formatSelection(data, choice);
+ choice.find("div").replaceWith(""+this.opts.escapeMarkup(formatted)+"
");
+ choice.find(".select2-search-choice-close")
+ .bind("mousedown", killEvent)
+ .bind("click dblclick", this.bind(function (e) {
+ if (!this.enabled) return;
+
+ $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
+ this.unselect($(e.target));
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+ this.close();
+ this.focusSearch();
+ })).dequeue();
+ killEvent(e);
+ })).bind("focus", this.bind(function () {
+ if (!this.enabled) return;
+ this.container.addClass("select2-container-active");
+ this.dropdown.addClass("select2-drop-active");
+ }));
+
+ choice.data("select2-data", data);
+ choice.insertBefore(this.searchContainer);
+
+ val.push(id);
+ this.setVal(val);
+ },
+
+ // multi
+ unselect: function (selected) {
+ var val = this.getVal(),
+ data,
+ index;
+
+ selected = selected.closest(".select2-search-choice");
+
+ if (selected.length === 0) {
+ throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
+ }
+
+ data = selected.data("select2-data");
+
+ index = indexOf(this.id(data), val);
+
+ if (index >= 0) {
+ val.splice(index, 1);
+ this.setVal(val);
+ if (this.select) this.postprocessResults();
+ }
+ selected.remove();
+ this.triggerChange({ removed: data });
+ },
+
+ // multi
+ postprocessResults: function () {
+ var val = this.getVal(),
+ choices = this.results.find(".select2-result-selectable"),
+ compound = this.results.find(".select2-result-with-children"),
+ self = this;
+
+ choices.each2(function (i, choice) {
+ var id = self.id(choice.data("select2-data"));
+ if (indexOf(id, val) >= 0) {
+ choice.addClass("select2-disabled").removeClass("select2-result-selectable");
+ } else {
+ choice.removeClass("select2-disabled").addClass("select2-result-selectable");
+ }
+ });
+
+ compound.each2(function(i, e) {
+ if (e.find(".select2-result-selectable").length==0) {
+ e.addClass("select2-disabled");
+ } else {
+ e.removeClass("select2-disabled");
+ }
+ });
+
+ choices.each2(function (i, choice) {
+ if (!choice.hasClass("select2-disabled") && choice.hasClass("select2-result-selectable")) {
+ self.highlight(0);
+ return false;
+ }
+ });
+
+ },
+
+ // multi
+ resizeSearch: function () {
+
+ var minimumWidth, left, maxWidth, containerLeft, searchWidth,
+ sideBorderPadding = getSideBorderPadding(this.search);
+
+ minimumWidth = measureTextWidth(this.search) + 10;
+
+ left = this.search.offset().left;
+
+ maxWidth = this.selection.width();
+ containerLeft = this.selection.offset().left;
+
+ searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
+ if (searchWidth < minimumWidth) {
+ searchWidth = maxWidth - sideBorderPadding;
+ }
+
+ if (searchWidth < 40) {
+ searchWidth = maxWidth - sideBorderPadding;
+ }
+ this.search.width(searchWidth);
+ },
+
+ // multi
+ getVal: function () {
+ var val;
+ if (this.select) {
+ val = this.select.val();
+ return val === null ? [] : val;
+ } else {
+ val = this.opts.element.val();
+ return splitVal(val, this.opts.separator);
+ }
+ },
+
+ // multi
+ setVal: function (val) {
+ var unique;
+ if (this.select) {
+ this.select.val(val);
+ } else {
+ unique = [];
+ // filter out duplicates
+ $(val).each(function () {
+ if (indexOf(this, unique) < 0) unique.push(this);
+ });
+ this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
+ }
+ },
+
+ // multi
+ val: function () {
+ var val, data = [], self=this;
+
+ if (arguments.length === 0) {
+ return this.getVal();
+ }
+
+ val = arguments[0];
+
+ if (!val) {
+ this.opts.element.val("");
+ this.updateSelection([]);
+ this.clearSearch();
+ return;
+ }
+
+ // val is a list of ids
+ this.setVal(val);
+
+ if (this.select) {
+ this.select.find(":selected").each(function () {
+ data.push({id: $(this).attr("value"), text: $(this).text()});
+ });
+ this.updateSelection(data);
+ } else {
+ if (this.opts.initSelection === undefined) {
+ throw new Error("val() cannot be called if initSelection() is not defined")
+ }
+
+ this.opts.initSelection(this.opts.element, function(data){
+ var ids=$(data).map(self.id);
+ self.setVal(ids);
+ self.updateSelection(data);
+ self.clearSearch();
+ });
+ }
+ this.clearSearch();
+ },
+
+ // multi
+ onSortStart: function() {
+ if (this.select) {
+ throw new Error("Sorting of elements is not supported when attached to . Attach to instead.");
+ }
+
+ // collapse search field into 0 width so its container can be collapsed as well
+ this.search.width(0);
+ // hide the container
+ this.searchContainer.hide();
+ },
+
+ // multi
+ onSortEnd:function() {
+
+ var val=[], self=this;
+
+ // show search and move it to the end of the list
+ this.searchContainer.show();
+ // make sure the search container is the last item in the list
+ this.searchContainer.appendTo(this.searchContainer.parent());
+ // since we collapsed the width in dragStarted, we resize it here
+ this.resizeSearch();
+
+ // update selection
+
+ this.selection.find(".select2-search-choice").each(function() {
+ val.push(self.opts.id($(this).data("select2-data")));
+ });
+ this.setVal(val);
+ this.triggerChange();
+ },
+
+ // multi
+ data: function(values) {
+ var self=this, ids;
+ if (arguments.length === 0) {
+ return this.selection
+ .find(".select2-search-choice")
+ .map(function() { return $(this).data("select2-data"); })
+ .get();
+ } else {
+ if (!values) { values = []; }
+ ids = $.map(values, function(e) { return self.opts.id(e)});
+ this.setVal(ids);
+ this.updateSelection(values);
+ this.clearSearch();
+ }
+ }
+ });
+
+ $.fn.select2 = function () {
+
+ var args = Array.prototype.slice.call(arguments, 0),
+ opts,
+ select2,
+ value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"];
+
+ this.each(function () {
+ if (args.length === 0 || typeof(args[0]) === "object") {
+ opts = args.length === 0 ? {} : $.extend({}, args[0]);
+ opts.element = $(this);
+
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
+ multiple = opts.element.attr("multiple");
+ } else {
+ multiple = opts.multiple || false;
+ if ("tags" in opts) {opts.multiple = multiple = true;}
+ }
+
+ select2 = multiple ? new MultiSelect2() : new SingleSelect2();
+ select2.init(opts);
+ } else if (typeof(args[0]) === "string") {
+
+ if (indexOf(args[0], allowedMethods) < 0) {
+ throw "Unknown method: " + args[0];
+ }
+
+ value = undefined;
+ select2 = $(this).data("select2");
+ if (select2 === undefined) return;
+ if (args[0] === "container") {
+ value=select2.container;
+ } else {
+ value = select2[args[0]].apply(select2, args.slice(1));
+ }
+ if (value !== undefined) {return false;}
+ } else {
+ throw "Invalid arguments to select2 plugin: " + args;
+ }
+ });
+ return (value === undefined) ? this : value;
+ };
+
+ // plugin defaults, accessible to users
+ $.fn.select2.defaults = {
+ width: "copy",
+ closeOnSelect: true,
+ openOnEnter: true,
+ containerCss: {},
+ dropdownCss: {},
+ containerCssClass: "",
+ dropdownCssClass: "",
+ formatResult: function(result, container, query) {
+ var markup=[];
+ markMatch(result.text, query.term, markup);
+ return markup.join("");
+ },
+ formatSelection: function (data, container) {
+ return data ? data.text : undefined;
+ },
+ formatResultCssClass: function(data) {return undefined;},
+ formatNoMatches: function () { return "No matches found"; },
+ formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
+ formatLoadMore: function (pageNumber) { return "Loading more results..."; },
+ formatSearching: function () { return "Searching..."; },
+ minimumResultsForSearch: 0,
+ minimumInputLength: 0,
+ maximumSelectionSize: 0,
+ id: function (e) { return e.id; },
+ matcher: function(term, text) {
+ return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
+ },
+ separator: ",",
+ tokenSeparators: [],
+ tokenizer: defaultTokenizer,
+ escapeMarkup: function (markup) {
+ if (markup && typeof(markup) === "string") {
+ return markup.replace(/&/g, "&");
+ }
+ return markup;
+ },
+ blurOnChange: false
+ };
+
+ // exports
+ window.Select2 = {
+ query: {
+ ajax: ajax,
+ local: local,
+ tags: tags
+ }, util: {
+ debounce: debounce,
+ markMatch: markMatch
+ }, "class": {
+ "abstract": AbstractSelect2,
+ "single": SingleSelect2,
+ "multi": MultiSelect2
+ }
+ };
+
+}(jQuery));
diff --git a/src/inputs/select2/lib/select2.min.js b/src/inputs/select2/lib/select2.min.js
new file mode 100644
index 0000000..1523735
--- /dev/null
+++ b/src/inputs/select2/lib/select2.min.js
@@ -0,0 +1,82 @@
+/*
+Copyright 2012 Igor Vaynberg
+
+Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in
+compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed under the License is
+distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+(function(e){"undefined"==typeof e.fn.each2&&e.fn.extend({each2:function(g){for(var i=e([0]),m=-1,s=this.length;++ma.length)return[];
+c=a.split(b);d=0;for(j=c.length;dd?c.push(a):(c.push(a.substring(0,d)),c.push(""),c.push(a.substring(d,d+b)),c.push(" "),c.push(a.substring(d+b,a.length)))}function C(a){var b,
+c=0,d=null,j=a.quietMillis||100;return function(h){window.clearTimeout(b);b=window.setTimeout(function(){var b=c+=1,j=a.data,n=a.transport||e.ajax,f=a.traditional||!1,g=a.type||"GET",j=j.call(this,h.term,h.page,h.context);null!==d&&d.abort();d=n.call(null,{url:a.url,dataType:a.dataType,data:j,type:g,traditional:f,success:function(d){bd.tokenSeparators.length)return g;for(;;){h=-1;k=0;
+for(n=d.tokenSeparators.length;kh)break;f=a.substring(0,h);a=a.substring(h+o.length);if(0=a}};var I=1;G=function(){return I++};
+e(document).delegate("body","mousemove",function(a){e.data(document,"select2-lastpos",{x:a.pageX,y:a.pageY})});e(document).ready(function(){e(document).delegate("body","mousedown touchend",function(a){var b=e(a.target).closest("div.select2-container").get(0),c;b?e(document).find("div.select2-container-active").each(function(){this!==b&&e(this).data("select2").blur()}):(b=e(a.target).closest("div.select2-drop").get(0),e(document).find("div.select2-drop-active").each(function(){this!==b&&e(this).data("select2").blur()}));
+b=e(a.target);c=b.attr("for");"LABEL"===a.target.tagName&&(c&&0\|])/g,"\\$1");this.container.attr("id",this.containerId);var d=!1,j;this.body=function(){!1===d&&(j=a.element.closest("body"),d=!0);return j};a.element.attr("class")!==g&&this.container.addClass(a.element.attr("class").replace(/validate\[[\S ]+] ?/,""));this.container.css(v(a.containerCss));this.container.addClass(v(a.containerCssClass));this.opts.element.data("select2",this).hide().before(this.container);this.container.data("select2",
+this);this.dropdown=this.container.find(".select2-drop");this.dropdown.addClass(v(a.dropdownCssClass));this.dropdown.data("select2",this);this.results=b=this.container.find(".select2-results");this.search=c=this.container.find("input.select2-input");c.attr("tabIndex",this.opts.element.attr("tabIndex"));this.resultsPage=0;this.context=null;this.initContainer();this.initContainerWidth();this.results.bind("mousemove",function(a){var b=e.data(document,"select2-lastpos");(b===g||b.x!==a.pageX||b.y!==a.pageY)&&
+e(a.target).trigger("mousemove-filtered",a)});this.dropdown.delegate(".select2-results","mousemove-filtered",this.bind(this.highlightUnderEvent));var h=this.results,f=A(80,function(a){h.trigger("scroll-debounced",a)});h.bind("scroll",function(a){0<=i(a.target,h.get())&&f(a)});this.dropdown.delegate(".select2-results","scroll-debounced",this.bind(this.loadMoreIfNeeded));e.fn.mousewheel&&b.mousewheel(function(a,c,d,e){c=b.scrollTop();0=c-e?(b.scrollTop(0),l(a)):0>e&&b.get(0).scrollHeight-b.scrollTop()+
+e<=b.height()&&(b.scrollTop(b.get(0).scrollHeight-b.height()),l(a))});c.bind("keydown",function(){e.data(c,"keyup-change-value")===g&&e.data(c,"keyup-change-value",c.val())});c.bind("keyup",function(){var a=e.data(c,"keyup-change-value");a!==g&&c.val()!==a&&(e.removeData(c,"keyup-change-value"),c.trigger("keyup-change"))});c.bind("keyup-change",this.bind(this.updateResults));c.bind("focus",function(){c.addClass("select2-focused");" "===c.val()&&c.val("")});c.bind("blur",function(){c.removeClass("select2-focused")});
+this.dropdown.delegate(".select2-results","mouseup",this.bind(function(a){0 element.");});a=e.extend({},{populateResults:function(b,
+c,d){var f,n=this.opts.id,o=this;f=function(b,c,j){var h,l,i,m,r,p,q;h=0;for(l=b.length;h0;p=e(" ");p.addClass("select2-results-dept-"+j);p.addClass("select2-result");p.addClass(m?"select2-result-selectable":"select2-result-unselectable");r&&p.addClass("select2-result-with-children");p.addClass(o.opts.formatResultCssClass(i));m=e("
");m.addClass("select2-result-label");q=a.formatResult(i,m,d);q!==g&&m.html(o.opts.escapeMarkup(q));
+p.append(m);if(r){r=e("");r.addClass("select2-result-sub");f(i.children,r,j+1);p.append(r)}p.data("select2-data",i);c.append(p)}};f(c,b,0)}},e.fn.select2.defaults,a);"function"!==typeof a.id&&(d=a.id,a.id=function(a){return a[d]});if(c)a.query=this.bind(function(a){var c={results:[],more:false},d=a.term,f,n,o;o=function(b,c){var e;if(b.is("option"))a.matcher(d,b.text(),b)&&c.push({id:b.attr("value"),text:b.text(),element:b.get(),css:b.attr("class")});else if(b.is("optgroup")){e={text:b.attr("label"),
+children:[],element:b.get(),css:b.attr("class")};b.children().each2(function(a,b){o(b,e.children)});e.children.length>0&&c.push(e)}};f=b.children();if(this.getPlaceholder()!==g&&f.length>0){n=f[0];e(n).text()===""&&(f=f.not(n))}f.each2(function(a,b){o(b,c.results)});a.callback(c)}),a.id=function(a){return a.id},a.formatResultCssClass=function(a){return a.css};else if(!("query"in a))if("ajax"in a){if((c=a.element.data("ajax-url"))&&0=this.body().scrollTop(),k=this.dropdown.hasClass("select2-drop-above"),n;"static"!==this.body().css("position")&&
+(n=this.body().offset(),b-=n.top,f-=n.left);k?(k=!0,!g&&j&&(k=!1)):(k=!1,!j&&g&&(k=!0));k?(b=a.top-d,this.container.addClass("select2-drop-above"),this.dropdown.addClass("select2-drop-above")):(this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above"));a=e.extend({top:b,left:f,width:c},v(this.opts.dropdownCss));this.dropdown.css(a)},shouldOpen:function(){var a;if(this.opened())return!1;a=e.Event("open");this.opts.element.trigger(a);return!a.isDefaultPrevented()},
+clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above");this.dropdown.removeClass("select2-drop-above")},open:function(){if(!this.shouldOpen())return!1;window.setTimeout(this.bind(this.opening),1);return!0},opening:function(){var a=this.containerId,b=this.containerSelector,c="scroll."+a,d="resize."+a;this.container.parents().each(function(){e(this).bind(c,function(){var a=e(b);0==a.length&&e(this).unbind(c);a.select2("close")})});e(window).bind(d,function(){var a=
+e(b);0==a.length&&e(window).unbind(d);a.select2("close")});this.clearDropdownAlignmentPreference();" "===this.search.val()&&this.search.val("");this.container.addClass("select2-dropdown-open").addClass("select2-container-active");this.updateResults(!0);this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body());this.dropdown.show();this.positionDropdown();this.dropdown.addClass("select2-drop-active");this.ensureHighlightVisible();this.focusSearch()},close:function(){if(this.opened()){var a=
+this;this.container.parents().each(function(){e(this).unbind("scroll."+a.containerId)});e(window).unbind("resize."+this.containerId);this.clearDropdownAlignmentPreference();this.dropdown.hide();this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");this.results.empty();this.clearSearch();this.opts.element.trigger(e.Event("close"))}},clearSearch:function(){},ensureHighlightVisible:function(){var a=this.results,b,c,d,f;c=this.highlight();0>c||(0==c?a.scrollTop(0):
+(b=a.find(".select2-result-selectable"),d=e(b[c]),f=d.offset().top+d.outerHeight(),c===b.length-1&&(b=a.find("li.select2-more-results"),0b&&a.scrollTop(a.scrollTop()+(f-b)),d=d.offset().top-a.offset().top,0>d&&a.scrollTop(a.scrollTop()+d)))},moveHighlight:function(a){for(var b=this.results.find(".select2-result-selectable"),c=this.highlight();-1=b.length&&(a=b.length-1);0>a&&(a=0);b.removeClass("select2-highlighted");e(b[a]).addClass("select2-highlighted");this.ensureHighlightVisible()},countSelectableResults:function(){return this.results.find(".select2-result-selectable").not(".select2-disabled").length},
+highlightUnderEvent:function(a){a=e(a.target).closest(".select2-result-selectable");if(0=c&&(b.addClass("select2-active"),this.opts.query({term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),!0===c.more?(b.detach().appendTo(a).text(e.opts.formatLoadMore(d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d)})})))},tokenize:function(){},updateResults:function(a){function b(){f.scrollTop(0);d.removeClass("select2-active");
+k.positionDropdown()}function c(a){f.html(k.opts.escapeMarkup(a));b()}var d=this.search,f=this.results,h=this.opts,i,k=this;if(!(!0!==a&&(!1===this.showSearchInput||!this.opened()))){d.addClass("select2-active");if(1<=h.maximumSelectionSize&&(i=this.data(),e.isArray(i)&&i.length>=h.maximumSelectionSize&&u(h.formatSelectionTooBig,"formatSelectionTooBig"))){c(""+h.formatSelectionTooBig(h.maximumSelectionSize)+" ");return}d.val().length"+h.formatInputTooShort(d.val(),h.minimumInputLength)+""):(c(""+h.formatSearching()+" "),i=this.tokenize(),i!=g&&null!=i&&d.val(i),this.resultsPage=1,h.query({term:d.val(),page:this.resultsPage,context:null,matcher:h.matcher,callback:this.bind(function(i){var l;this.opened()&&((this.context=i.context===g?null:i.context,this.opts.createSearchChoice&&""!==d.val()&&(l=this.opts.createSearchChoice.call(null,
+d.val(),i.results),l!==g&&null!==l&&k.id(l)!==g&&null!==k.id(l)&&0===e(i.results).filter(function(){return m(k.id(this),k.id(l))}).length&&i.results.unshift(l)),0===i.results.length&&u(h.formatNoMatches,"formatNoMatches"))?c(""+h.formatNoMatches(d.val())+" "):(f.empty(),k.opts.populateResults.call(this,f,i.results,{term:d.val(),page:this.resultsPage,context:null}),!0===i.more&&u(h.formatLoadMore,"formatLoadMore")&&(f.append(""+k.opts.escapeMarkup(h.formatLoadMore(this.resultsPage))+
+" "),window.setTimeout(function(){k.loadMoreIfNeeded()},10)),this.postprocessResults(i,a),b()))})}))}},cancel:function(){this.close()},blur:function(){this.close();this.container.removeClass("select2-container-active");this.dropdown.removeClass("select2-drop-active");this.search[0]===document.activeElement&&this.search.blur();this.clearSearch();this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){this.search.show();this.search.focus();
+window.setTimeout(this.bind(function(){this.search.show();this.search.focus();this.search.val(this.search.val())}),10)},selectHighlighted:function(){var a=this.highlight(),b=this.results.find(".select2-highlighted").not(".select2-disabled"),c=b.closest(".select2-result-selectable").data("select2-data");c&&(b.addClass("select2-disabled"),this.highlight(a),this.onSelect(c))},getPlaceholder:function(){return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||
+this.opts.placeholder},initContainerWidth:function(){var a=function(){var a,c,d,f;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth()?"auto":this.opts.element.outerWidth()+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){a=this.opts.element.attr("style");if(a!==g){a=a.split(";");d=0;for(f=a.length;d",{"class":"select2-container"}).html("
")},
+opening:function(){this.search.show();this.parent.opening.apply(this,arguments);this.dropdown.removeClass("select2-offscreen")},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show())},focus:function(){this.close();this.selection.focus()},isFocused:function(){return this.selection[0]===document.activeElement},cancel:function(){this.parent.cancel.apply(this,arguments);this.selection.focus()},
+initContainer:function(){var a,b=this.dropdown;this.selection=a=this.container.find(".select2-choice");this.search.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(this.opened())switch(a.which){case f.UP:case f.DOWN:this.moveHighlight(a.which===f.UP?-1:1);l(a);break;case f.TAB:case f.ENTER:this.selectHighlighted();l(a);break;case f.ESC:this.cancel(a),l(a)}else a.which===f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC||!1===
+this.opts.openOnEnter&&a.which===f.ENTER||this.open()}));this.search.bind("focus",this.bind(function(){this.selection.attr("tabIndex","-1")}));this.search.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.selection.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("mousedown",this.bind(function(){this.opened()?(this.close(),this.selection.focus()):this.enabled&&this.open()}));b.bind("mousedown",
+this.bind(function(){this.search.focus()}));a.bind("focus",this.bind(function(){this.container.addClass("select2-container-active");this.search.attr("tabIndex","-1")}));a.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.search.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(!(a.which===
+f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC)&&!(!1===this.opts.openOnEnter&&a.which===f.ENTER))if(a.which==f.DELETE)this.opts.allowClear&&this.clear();else{this.open();if(a.which!==f.ENTER&&!(48>a.which)){var b=String.fromCharCode(a.which).toLowerCase();a.shiftKey&&(b=b.toUpperCase());this.search.focus();this.search.val(b)}l(a)}}));a.delegate("abbr","mousedown",this.bind(function(a){this.enabled&&(this.clear(),l(a),this.close(),this.triggerChange(),this.selection.focus())}));this.setPlaceholder();
+this.search.bind("focus",this.bind(function(){this.container.addClass("select2-container-active")}))},clear:function(){this.opts.element.val("");this.selection.find("span").empty();this.selection.removeData("select2-data");this.setPlaceholder()},initSelection:function(){if(""===this.opts.element.val())this.close(),this.setPlaceholder();else{var a=this;this.opts.initSelection.call(null,this.opts.element,function(b){b!==g&&null!==b&&(a.updateSelection(b),a.close(),a.setPlaceholder())})}},prepareOpts:function(){var a=
+this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=a.find(":selected");e.isFunction(c)&&c({id:d.attr("value"),text:d.text()})});return a},setPlaceholder:function(){var a=this.getPlaceholder();""===this.opts.element.val()&&a!==g&&!(this.select&&""!==this.select.find("option:first").text())&&(this.selection.find("span").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.selection.find("abbr").hide())},
+postprocessResults:function(a,b){var c=0,d=this,f=!0;this.results.find(".select2-result-selectable").each2(function(a,b){if(m(d.id(b.data("select2-data")),d.opts.element.val()))return c=a,!1});this.highlight(c);!0===b&&(f=this.showSearchInput=F(a.results)>=this.opts.minimumResultsForSearch,this.dropdown.find(".select2-search")[f?"removeClass":"addClass"]("select2-search-hidden"),e(this.dropdown,this.container)[f?"addClass":"removeClass"]("select2-with-searchbox"))},onSelect:function(a){var b=this.opts.element.val();
+this.opts.element.val(this.id(a));this.updateSelection(a);this.close();this.selection.focus();m(b,this.id(a))||this.triggerChange()},updateSelection:function(a){var b=this.selection.find("span");this.selection.data("select2-data",a);b.empty();a=this.opts.formatSelection(a,b);a!==g&&b.append(this.opts.escapeMarkup(a));this.selection.removeClass("select2-default");this.opts.allowClear&&this.getPlaceholder()!==g&&this.selection.find("abbr").show()},val:function(){var a,b=null,c=this;if(0===arguments.length)return this.opts.element.val();
+a=arguments[0];if(this.select)this.select.val(a).find(":selected").each2(function(a,c){b={id:c.attr("value"),text:c.text()};return!1}),this.updateSelection(b),this.setPlaceholder();else{if(this.opts.initSelection===g)throw Error("cannot call val() if initSelection() is not defined");a?(this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){c.opts.element.val(!a?"":c.id(a));c.updateSelection(a);c.setPlaceholder()})):this.clear()}},clearSearch:function(){this.search.val("")},
+data:function(a){var b;if(0===arguments.length)return b=this.selection.data("select2-data"),b==g&&(b=null),b;!a||""===a?this.clear():(this.opts.element.val(!a?"":this.id(a)),this.updateSelection(a))}});z=x(w,{createContainer:function(){return e("
",{"class":"select2-container select2-container-multi"}).html(" ")},
+prepareOpts:function(){var a=this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=[];a.find(":selected").each2(function(a,b){d.push({id:b.attr("value"),text:b.text()})});e.isFunction(c)&&c(d)});return a},initContainer:function(){var a;this.searchContainer=this.container.find(".select2-search-field");this.selection=a=this.container.find(".select2-choices");this.search.bind("keydown",this.bind(function(b){if(this.enabled){if(b.which===
+f.BACKSPACE&&""===this.search.val()){this.close();var c;c=a.find(".select2-search-choice-focus");if(0i(d.id(this),b)&&(b.push(d.id(this)),c.push(this))});a=c;this.selection.find(".select2-search-choice").remove();e(a).each(function(){d.addSelectedChoice(this)});d.postprocessResults()},tokenize:function(){var a=this.search.val(),a=this.opts.tokenizer(a,this.data(),this.bind(this.onSelect),
+this.opts);null!=a&&a!=g&&(this.search.val(a),0
"),
+c=this.id(a),d=this.getVal(),f;f=this.opts.formatSelection(a,b);b.find("div").replaceWith(""+this.opts.escapeMarkup(f)+"
");b.find(".select2-search-choice-close").bind("mousedown",l).bind("click dblclick",this.bind(function(a){this.enabled&&(e(a.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(e(a.target));this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");this.close();this.focusSearch()})).dequeue(),
+l(a))})).bind("focus",this.bind(function(){this.enabled&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))}));b.data("select2-data",a);b.insertBefore(this.searchContainer);d.push(c);this.setVal(d)},unselect:function(a){var b=this.getVal(),c,d,a=a.closest(".select2-search-choice");if(0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data");d=i(this.id(c),b);0<=d&&(b.splice(d,1),this.setVal(b),this.select&&
+this.postprocessResults());a.remove();this.triggerChange({removed:c})},postprocessResults:function(){var a=this.getVal(),b=this.results.find(".select2-result-selectable"),c=this.results.find(".select2-result-with-children"),d=this;b.each2(function(b,c){var e=d.id(c.data("select2-data"));0<=i(e,a)?c.addClass("select2-disabled").removeClass("select2-result-selectable"):c.removeClass("select2-disabled").addClass("select2-result-selectable")});c.each2(function(a,b){0==b.find(".select2-result-selectable").length?
+b.addClass("select2-disabled"):b.removeClass("select2-disabled")});b.each2(function(a,b){if(!b.hasClass("select2-disabled")&&b.hasClass("select2-result-selectable"))return d.highlight(0),!1})},resizeSearch:function(){var a,b,c,d,f=this.search.outerWidth()-this.search.width();a=this.search;q||(c=a[0].currentStyle||window.getComputedStyle(a[0],null),q=e("
").css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,
+fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),e("body").append(q));q.text(a.val());a=q.width()+10;b=this.search.offset().left;c=this.selection.width();d=this.selection.offset().left;b=c-(b-d)-f;bb&&(b=c-f);this.search.width(b)},getVal:function(){var a;if(this.select)return a=this.select.val(),null===a?[]:a;a=this.opts.element.val();return s(a,this.opts.separator)},setVal:function(a){var b;this.select?this.select.val(a):(b=
+[],e(a).each(function(){0>i(this,b)&&b.push(this)}),this.opts.element.val(0===b.length?"":b.join(this.opts.separator)))},val:function(){var a,b=[],c=this;if(0===arguments.length)return this.getVal();if(a=arguments[0])if(this.setVal(a),this.select)this.select.find(":selected").each(function(){b.push({id:e(this).attr("value"),text:e(this).text()})}),this.updateSelection(b);else{if(this.opts.initSelection===g)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,
+function(a){var b=e(a).map(c.id);c.setVal(b);c.updateSelection(a);c.clearSearch()})}else this.opts.element.val(""),this.updateSelection([]);this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to . Attach to instead.");this.search.width(0);this.searchContainer.hide()},onSortEnd:function(){var a=[],b=this;this.searchContainer.show();this.searchContainer.appendTo(this.searchContainer.parent());this.resizeSearch();
+this.selection.find(".select2-search-choice").each(function(){a.push(b.opts.id(e(this).data("select2-data")))});this.setVal(a);this.triggerChange()},data:function(a){var b=this,c;if(0===arguments.length)return this.selection.find(".select2-search-choice").map(function(){return e(this).data("select2-data")}).get();a||(a=[]);c=e.map(a,function(a){return b.opts.id(a)});this.setVal(c);this.updateSelection(a);this.clearSearch()}});e.fn.select2=function(){var a=Array.prototype.slice.call(arguments,0),b,
+c,d,f,h="val destroy opened open close focus isFocused container onSortStart onSortEnd enable disable positionDropdown data".split(" ");this.each(function(){if(0===a.length||"object"===typeof a[0])b=0===a.length?{}:e.extend({},a[0]),b.element=e(this),"select"===b.element.get(0).tagName.toLowerCase()?f=b.element.attr("multiple"):(f=b.multiple||!1,"tags"in b&&(b.multiple=f=!0)),c=f?new z:new y,c.init(b);else if("string"===typeof a[0]){if(0>i(a[0],h))throw"Unknown method: "+a[0];d=g;c=e(this).data("select2");
+if(c!==g&&(d="container"===a[0]?c.container:c[a[0]].apply(c,a.slice(1)),d!==g))return!1}else throw"Invalid arguments to select2 plugin: "+a;});return d===g?this:d};e.fn.select2.defaults={width:"copy",closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c){b=[];B(a.text,c.term,b);return b.join("")},formatSelection:function(a){return a?a.text:g},formatResultCssClass:function(){return g},formatNoMatches:function(){return"No matches found"},
+formatInputTooShort:function(a,b){return"Please enter "+(b-a.length)+" more characters"},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return 0<=b.toUpperCase().indexOf(a.toUpperCase())},separator:",",tokenSeparators:[],tokenizer:H,
+escapeMarkup:function(a){return a&&"string"===typeof a?a.replace(/&/g,"&"):a},blurOnChange:!1};window.Select2={query:{ajax:C,local:D,tags:E},util:{debounce:A,markMatch:B},"class":{"abstract":w,single:y,multi:z}}}})(jQuery);
diff --git a/src/inputs/select2/lib/select2.png b/src/inputs/select2/lib/select2.png
new file mode 100644
index 0000000..1d804ff
Binary files /dev/null and b/src/inputs/select2/lib/select2.png differ
diff --git a/src/inputs/select2/lib/select2x2.png b/src/inputs/select2/lib/select2x2.png
new file mode 100644
index 0000000..4bdd5c9
Binary files /dev/null and b/src/inputs/select2/lib/select2x2.png differ
diff --git a/src/inputs/select2/lib/spinner.gif b/src/inputs/select2/lib/spinner.gif
new file mode 100644
index 0000000..5b33f7e
Binary files /dev/null and b/src/inputs/select2/lib/spinner.gif differ
diff --git a/src/inputs/select2/select2.js b/src/inputs/select2/select2.js
new file mode 100644
index 0000000..fb1a103
--- /dev/null
+++ b/src/inputs/select2/select2.js
@@ -0,0 +1,193 @@
+/**
+Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
+Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
+You should manually include select2 distributive:
+
+
+
+
+@class select2
+@extends abstractinput
+@since 1.4.1
+@final
+@example
+
+
+**/
+(function ($) {
+
+ var Constructor = function (options) {
+ this.init('select2', options, Constructor.defaults);
+
+ options.select2 = options.select2 || {};
+
+ var that = this,
+ mixin = {
+ placeholder: options.placeholder
+ };
+
+ //detect whether it is multi-valued
+ this.isMultiple = options.select2.tags || options.select2.multiple;
+
+ //if not `tags` mode, we need define init set data from source
+ if(!options.select2.tags) {
+ if(options.source) {
+ mixin.data = options.source;
+ }
+
+ //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
+ mixin.initSelection = function (element, callback) {
+ var val = that.str2value(element.val()),
+ data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
+
+ //for single-valued mode should not use array. Take first element instead.
+ if($.isArray(data) && data.length && !that.isMultiple) {
+ data = data[0];
+ }
+
+ callback(data);
+ };
+ }
+
+ //overriding objects in config (as by default jQuery extend() is not recursive)
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
+ };
+
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
+
+ $.extend(Constructor.prototype, {
+ render: function() {
+ this.setClass();
+ //apply select2
+ this.$input.select2(this.options.select2);
+
+ //trigger resize of editableform to re-position container in multi-valued mode
+ if(this.isMultiple) {
+ this.$input.on('change', function() {
+ $(this).closest('form').parent().triggerHandler('resize');
+ });
+ }
+
+ },
+
+ value2html: function(value, element) {
+ var text = '', data;
+ if(this.$input) { //when submitting form
+ data = this.$input.select2('data');
+ } else { //on init (autotext)
+ //here select2 instance not created yet and data may be even not loaded.
+ //we can check data/tags property of select config and if exist lookup text
+ if(this.options.select2.tags) {
+ data = value;
+ } else if(this.options.select2.data) {
+ data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
+ }
+ }
+
+ if($.isArray(data)) {
+ //collect selected data and show with separator
+ text = [];
+ $.each(data, function(k, v){
+ text.push(v && typeof v === 'object' ? v.text : v);
+ });
+ } else if(data) {
+ text = data.text;
+ }
+
+ text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
+
+ $(element).text(text);
+ },
+
+ html2value: function(html) {
+ return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
+ },
+
+ value2input: function(value) {
+ this.$input.val(value).trigger('change');
+ },
+
+ input2value: function() {
+ return this.$input.select2('val');
+ },
+
+ str2value: function(str, separator) {
+ if(typeof str !== 'string' || !this.isMultiple) {
+ return str;
+ }
+
+ separator = separator || this.options.select2.separator || $.fn.select2.defaults.separator;
+
+ var val, i, l;
+
+ if (str === null || str.length < 1) {
+ return null;
+ }
+ val = str.split(separator);
+ for (i = 0, l = val.length; i < l; i = i + 1) {
+ val[i] = $.trim(val[i]);
+ }
+
+ return val;
+ }
+
+ });
+
+ Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+ /**
+ @property tpl
+ @default
+ **/
+ tpl:' ',
+ /**
+ Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
+
+ @property select2
+ @type object
+ @default null
+ **/
+ select2: null,
+ /**
+ Placeholder attribute of select
+
+ @property placeholder
+ @type string
+ @default null
+ **/
+ placeholder: null,
+ /**
+ Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
+ Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
+ E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
+
+ @property source
+ @type array
+ @default null
+ **/
+ source: null,
+ /**
+ Separator used to display tags.
+
+ @property viewseparator
+ @type string
+ @default ', '
+ **/
+ viewseparator: ', '
+ });
+
+ $.fn.editabletypes.select2 = Constructor;
+
+}(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs/text.js b/src/inputs/text.js
index 93fd193..b41d717 100644
--- a/src/inputs/text.js
+++ b/src/inputs/text.js
@@ -48,10 +48,7 @@ $(function(){
.keyup($.proxy(this.toggleClear, this))
.parent().css('position', 'relative');
- this.$clear.click($.proxy(function(){
- this.$clear.hide();
- this.$input.val('').focus();
- }, this));
+ this.$clear.click($.proxy(this.clear, this));
}
},
@@ -76,12 +73,17 @@ $(function(){
return;
}
- if(this.$input.val()) {
+ if(this.$input.val().length) {
this.$clear.show();
} else {
this.$clear.hide();
}
- }
+ },
+
+ clear: function() {
+ this.$clear.hide();
+ this.$input.val('').focus();
+ }
});
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
diff --git a/src/inputs/typeahead.js b/src/inputs/typeahead.js
new file mode 100644
index 0000000..8945059
--- /dev/null
+++ b/src/inputs/typeahead.js
@@ -0,0 +1,221 @@
+/**
+Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead).
+Depending on `source` format typeahead operates in two modes:
+
+* **strings**:
+ When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`.
+ User can submit one of these strings or any text entered in input (even if it is not matching source).
+
+* **objects**:
+ When `source` defined as array of objects, e.g. `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`.
+ User can submit only values that are in source (otherwise `null` is submitted). This is more like *dropdown* behavior.
+
+@class typeahead
+@extends list
+@since 1.4.1
+@final
+@example
+
+
+**/
+(function ($) {
+
+ var Constructor = function (options) {
+ this.init('typeahead', options, Constructor.defaults);
+
+ //overriding objects in config (as by default jQuery extend() is not recursive)
+ this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, {
+ //set default methods for typeahead to work with objects
+ matcher: this.matcher,
+ sorter: this.sorter,
+ highlighter: this.highlighter,
+ updater: this.updater
+ }, options.typeahead);
+ };
+
+ $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.list);
+
+ $.extend(Constructor.prototype, {
+ renderList: function() {
+ this.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]');
+
+ //set source of typeahead
+ this.options.typeahead.source = this.sourceData;
+
+ //apply typeahead
+ this.$input.typeahead(this.options.typeahead);
+
+ //attach own render method
+ this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead'));
+
+ this.renderClear();
+ this.setClass();
+ this.setAttr('placeholder');
+ },
+
+ value2htmlFinal: function(value, element) {
+ if(this.getIsObjects()) {
+ var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
+ $(element).text(items.length ? items[0].text : '');
+ } else {
+ $(element).text(value);
+ }
+ },
+
+ html2value: function (html) {
+ return html ? html : null;
+ },
+
+ value2input: function(value) {
+ if(this.getIsObjects()) {
+ var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
+ this.$input.data('value', value).val(items.length ? items[0].text : '');
+ } else {
+ this.$input.val(value);
+ }
+ },
+
+ input2value: function() {
+ if(this.getIsObjects()) {
+ var value = this.$input.data('value'),
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData);
+
+ if(items.length && items[0].text.toLowerCase() === this.$input.val().toLowerCase()) {
+ return value;
+ } else {
+ return null; //entered string not found in source
+ }
+ } else {
+ return this.$input.val();
+ }
+ },
+
+ /*
+ if in sourceData values <> texts, typeahead in "objects" mode:
+ user must pick some value from list, otherwise `null` returned.
+ if all values == texts put typeahead in "strings" mode:
+ anything what entered is submited.
+ */
+ getIsObjects: function() {
+ if(this.isObjects === undefined) {
+ this.isObjects = false;
+ for(var i=0; i
+ **/
+ tpl:' ',
+ /**
+ Configuration of typeahead. [Full list of options](http://twitter.github.com/bootstrap/javascript.html#typeahead).
+
+ @property typeahead
+ @type object
+ @default null
+ **/
+ typeahead: null,
+ /**
+ Whether to show `clear` button
+
+ @property clear
+ @type boolean
+ @default true
+ **/
+ clear: true
+ });
+
+ $.fn.editabletypes.typeahead = Constructor;
+
+}(window.jQuery));
\ No newline at end of file
diff --git a/test/index.html b/test/index.html
index 233b5e7..dca946e 100644
--- a/test/index.html
+++ b/test/index.html
@@ -21,7 +21,7 @@