select2 with ajax source, fixes

This commit is contained in:
vitalets
2013-06-22 21:01:26 +04:00
parent 80ce493ac0
commit a0fbbaf0af
5 changed files with 340 additions and 160 deletions

@ -1,7 +1,7 @@
/* /*
Copyright 2012 Igor Vaynberg Copyright 2012 Igor Vaynberg
Version: 3.4.0 Timestamp: Tue May 14 08:27:33 PDT 2013 Version: @@ver@@ Timestamp: @@timestamp@@
This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
General Public License version 2 (the "GPL License"). You may choose either license to govern your General Public License version 2 (the "GPL License"). You may choose either license to govern your
@ -47,7 +47,7 @@ the specific language governing permissions and limitations under the Apache Lic
} }
var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer, var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
lastMousePosition, $document, scrollBarDimensions, lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
KEY = { KEY = {
TAB: 9, TAB: 9,
@ -172,7 +172,8 @@ the specific language governing permissions and limitations under the Apache Lic
} }
$document.on("mousemove", function (e) { $document.on("mousemove", function (e) {
lastMousePosition = {x: e.pageX, y: e.pageY}; lastMousePosition.x = e.pageX;
lastMousePosition.y = e.pageY;
}); });
/** /**
@ -355,6 +356,22 @@ the specific language governing permissions and limitations under the Apache Lic
markup.push(escapeMarkup(text.substring(match + tl, text.length))); markup.push(escapeMarkup(text.substring(match + tl, text.length)));
} }
function defaultEscapeMarkup(markup) {
var replace_map = {
'\\': '\',
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
"/": '&#47;'
};
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
return replace_map[match];
});
}
/** /**
* Produces an ajax-based query function * Produces an ajax-based query function
* *
@ -399,7 +416,7 @@ the specific language governing permissions and limitations under the Apache Lic
data = data ? data.call(self, query.term, query.page, query.context) : null; data = data ? data.call(self, query.term, query.page, query.context) : null;
url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url; url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
if( null !== handler) { handler.abort(); } if (handler) { handler.abort(); }
if (options.params) { if (options.params) {
if ($.isFunction(options.params)) { if ($.isFunction(options.params)) {
@ -526,7 +543,7 @@ the specific language governing permissions and limitations under the Apache Lic
function checkFormatter(formatter, formatterName) { function checkFormatter(formatter, formatterName) {
if ($.isFunction(formatter)) return true; if ($.isFunction(formatter)) return true;
if (!formatter) return false; if (!formatter) return false;
throw new Error("formatterName must be a function or a falsy value"); throw new Error(formatterName +" must be a function or a falsy value");
} }
function evaluate(val) { function evaluate(val) {
@ -746,21 +763,24 @@ the specific language governing permissions and limitations under the Apache Lic
// abstract // abstract
destroy: function () { destroy: function () {
var select2 = this.opts.element.data("select2"); var element=this.opts.element, select2 = element.data("select2");
if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
if (select2 !== undefined) { if (select2 !== undefined) {
select2.container.remove(); select2.container.remove();
select2.dropdown.remove(); select2.dropdown.remove();
select2.opts.element element
.removeClass("select2-offscreen") .removeClass("select2-offscreen")
.removeData("select2") .removeData("select2")
.off(".select2") .off(".select2")
.attr({"tabindex": this.elementTabIndex}) .prop("autofocus", this.autofocus || false);
.prop("autofocus", this.autofocus||false) if (this.elementTabIndex) {
.show(); element.attr({tabindex: this.elementTabIndex});
} else {
element.removeAttr("tabindex");
}
element.show();
} }
}, },
@ -874,7 +894,7 @@ the specific language governing permissions and limitations under the Apache Lic
opts.query = this.bind(function (query) { opts.query = this.bind(function (query) {
var data = { results: [], more: false }, var data = { results: [], more: false },
term = query.term, term = query.term,
children, firstChild, process; children, placeholderOption, process;
process=function(element, collection) { process=function(element, collection) {
var group; var group;
@ -895,9 +915,9 @@ the specific language governing permissions and limitations under the Apache Lic
// ignore the placeholder option if there is one // ignore the placeholder option if there is one
if (this.getPlaceholder() !== undefined && children.length > 0) { if (this.getPlaceholder() !== undefined && children.length > 0) {
firstChild = children[0]; placeholderOption = this.getPlaceholderOption();
if ($(firstChild).text() === "") { if (placeholderOption) {
children=children.not(firstChild); children=children.not(placeholderOption);
} }
} }
@ -1201,7 +1221,7 @@ the specific language governing permissions and limitations under the Apache Lic
scroll = "scroll." + cid, scroll = "scroll." + cid,
resize = "resize."+cid, resize = "resize."+cid,
orient = "orientationchange."+cid, orient = "orientationchange."+cid,
mask; mask, maskCss;
this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
@ -1218,7 +1238,7 @@ the specific language governing permissions and limitations under the Apache Lic
mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask"); mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
mask.hide(); mask.hide();
mask.appendTo(this.body()); mask.appendTo(this.body());
mask.on("mousedown touchstart", function (e) { mask.on("mousedown touchstart click", function (e) {
var dropdown = $("#select2-drop"), self; var dropdown = $("#select2-drop"), self;
if (dropdown.length > 0) { if (dropdown.length > 0) {
self=dropdown.data("select2"); self=dropdown.data("select2");
@ -1242,20 +1262,22 @@ the specific language governing permissions and limitations under the Apache Lic
this.dropdown.attr("id", "select2-drop"); this.dropdown.attr("id", "select2-drop");
// show the elements // show the elements
mask.css(_makeMaskCss()); maskCss=_makeMaskCss();
mask.show();
mask.css(maskCss).show();
this.dropdown.show(); this.dropdown.show();
this.positionDropdown(); this.positionDropdown();
this.dropdown.addClass("select2-drop-active"); this.dropdown.addClass("select2-drop-active");
this.ensureHighlightVisible();
// attach listeners to events that can change the position of the container and thus require // attach listeners to events that can change the position of the container and thus require
// the position of the dropdown to be updated as well so it does not come unglued from the container // the position of the dropdown to be updated as well so it does not come unglued from the container
var that = this; var that = this;
this.container.parents().add(window).each(function () { this.container.parents().add(window).each(function () {
$(this).on(resize+" "+scroll+" "+orient, function (e) { $(this).on(resize+" "+scroll+" "+orient, function (e) {
$("#select2-drop-mask").css(_makeMaskCss()); var maskCss=_makeMaskCss();
$("#select2-drop-mask").css(maskCss);
that.positionDropdown(); that.positionDropdown();
}); });
}); });
@ -1294,6 +1316,16 @@ the specific language governing permissions and limitations under the Apache Lic
this.opts.element.trigger($.Event("select2-close")); this.opts.element.trigger($.Event("select2-close"));
}, },
/**
* Opens control, sets input value, and updates results.
*/
// abstract
externalSearch: function (term) {
this.open();
this.search.val(term);
this.updateResults(false);
},
// abstract // abstract
clearSearch: function () { clearSearch: function () {
@ -1487,7 +1519,6 @@ the specific language governing permissions and limitations under the Apache Lic
} }
function postRender() { function postRender() {
results.scrollTop(0);
search.removeClass("select2-active"); search.removeClass("select2-active");
self.positionDropdown(); self.positionDropdown();
} }
@ -1512,7 +1543,7 @@ the specific language governing permissions and limitations under the Apache Lic
} else { } else {
render(""); render("");
} }
if (initial) this.showSearch(true); if (initial && this.showSearch) this.showSearch(true);
return; return;
} }
@ -1586,7 +1617,7 @@ the specific language governing permissions and limitations under the Apache Lic
postRender(); postRender();
this.opts.element.trigger({ type: "select2-loaded", data:data }); this.opts.element.trigger({ type: "select2-loaded", items: data });
})}); })});
}, },
@ -1623,15 +1654,34 @@ the specific language governing permissions and limitations under the Apache Lic
if (data) { if (data) {
this.highlight(index); this.highlight(index);
this.onSelect(data, options); this.onSelect(data, options);
} else if (options.noFocus) {
this.close();
} }
}, },
// abstract // abstract
getPlaceholder: function () { getPlaceholder: function () {
var placeholderOption;
return this.opts.element.attr("placeholder") || return this.opts.element.attr("placeholder") ||
this.opts.element.attr("data-placeholder") || // jquery 1.4 compat this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
this.opts.element.data("placeholder") || this.opts.element.data("placeholder") ||
this.opts.placeholder; this.opts.placeholder ||
((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
},
// abstract
getPlaceholderOption: function() {
if (this.select) {
var firstOption = this.select.children().first();
if (this.opts.placeholderOption !== undefined ) {
//Determine the placeholder option based on the specified placeholderOption setting
return (this.opts.placeholderOption === "first" && firstOption) ||
(typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
} else if (firstOption.text() === "" && firstOption.val() === "") {
//No explicit placeholder option specified, use the first if it's blank
return firstOption;
}
}
}, },
/** /**
@ -1662,12 +1712,12 @@ the specific language governing permissions and limitations under the Apache Lic
} }
} }
// 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 && style.length > 0) return style;
if (this.opts.width === "resolve") { 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 // finally, fallback on the calculated width of the element
return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'); return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
} }
@ -1702,7 +1752,7 @@ the specific language governing permissions and limitations under the Apache Lic
"<input class='select2-focusser select2-offscreen' type='text'/>", "<input class='select2-focusser select2-offscreen' type='text'/>",
"<div class='select2-drop select2-display-none'>" , "<div class='select2-drop select2-display-none'>" ,
" <div class='select2-search'>" , " <div class='select2-search'>" ,
" <input type='text' autocomplete='off' autocorrect='off' autocapitilize='off' spellcheck='false' class='select2-input'/>" , " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>" ,
" </div>" , " </div>" ,
" <ul class='select2-results'>" , " <ul class='select2-results'>" ,
" </ul>" , " </ul>" ,
@ -1719,8 +1769,14 @@ the specific language governing permissions and limitations under the Apache Lic
// single // single
opening: function () { opening: function () {
var el, range; var el, range, len;
if (this.opts.minimumResultsForSearch >= 0) {
this.showSearch(true);
}
this.parent.opening.apply(this, arguments); this.parent.opening.apply(this, arguments);
if (this.showSearchInput !== false) { if (this.showSearchInput !== false) {
// IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
// all other browsers handle this just fine // all other browsers handle this just fine
@ -1728,13 +1784,16 @@ the specific language governing permissions and limitations under the Apache Lic
this.search.val(this.focusser.val()); this.search.val(this.focusser.val());
} }
this.search.focus(); this.search.focus();
// in IE we have to move the cursor to the end after focussing, otherwise it will be at the beginning and // move the cursor to the end after focussing, otherwise it will be at the beginning and
// new text will appear *before* focusser.val() // new text will appear *before* focusser.val()
el = this.search.get(0); el = this.search.get(0);
if (el.createTextRange) { if (el.createTextRange) {
range = el.createTextRange(); range = el.createTextRange();
range.collapse(false); range.collapse(false);
range.select(); range.select();
} else if (el.setSelectionRange) {
len = this.search.val().length;
el.setSelectionRange(len, len);
} }
this.focusser.prop("disabled", true).val(""); this.focusser.prop("disabled", true).val("");
@ -1779,7 +1838,11 @@ the specific language governing permissions and limitations under the Apache Lic
container = this.container, container = this.container,
dropdown = this.dropdown; dropdown = this.dropdown;
this.showSearch(false); if (this.opts.minimumResultsForSearch < 0) {
this.showSearch(false);
} else {
this.showSearch(true);
}
this.selection = selection = container.find(".select2-choice"); this.selection = selection = container.find(".select2-choice");
@ -1846,6 +1909,9 @@ the specific language governing permissions and limitations under the Apache Lic
if (e.which == KEY.DOWN || e.which == KEY.UP if (e.which == KEY.DOWN || e.which == KEY.UP
|| (e.which == KEY.ENTER && this.opts.openOnEnter)) { || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
this.open(); this.open();
killEvent(e); killEvent(e);
return; return;
@ -1863,9 +1929,11 @@ the specific language governing permissions and limitations under the Apache Lic
installKeyUpChangeEvent(this.focusser); installKeyUpChangeEvent(this.focusser);
this.focusser.on("keyup-change input", this.bind(function(e) { this.focusser.on("keyup-change input", this.bind(function(e) {
e.stopPropagation(); if (this.opts.minimumResultsForSearch >= 0) {
if (this.opened()) return; e.stopPropagation();
this.open(); if (this.opened()) return;
this.open();
}
})); }));
selection.on("mousedown", "abbr", this.bind(function (e) { selection.on("mousedown", "abbr", this.bind(function (e) {
@ -1925,7 +1993,8 @@ the specific language governing permissions and limitations under the Apache Lic
clear: function(triggerChange) { clear: function(triggerChange) {
var data=this.selection.data("select2-data"); var data=this.selection.data("select2-data");
if (data) { // guard against queued quick consecutive clicks if (data) { // guard against queued quick consecutive clicks
this.opts.element.val(""); var placeholderOption = this.getPlaceholderOption();
this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
this.selection.find("span").empty(); this.selection.find("span").empty();
this.selection.removeData("select2-data"); this.selection.removeData("select2-data");
this.setPlaceholder(); this.setPlaceholder();
@ -1943,7 +2012,7 @@ the specific language governing permissions and limitations under the Apache Lic
// single // single
initSelection: function () { initSelection: function () {
var selected; var selected;
if (this.opts.element.val() === "" && this.opts.element.text() === "") { if (this.isPlaceholderOptionSelected()) {
this.updateSelection([]); this.updateSelection([]);
this.close(); this.close();
this.setPlaceholder(); this.setPlaceholder();
@ -1959,6 +2028,14 @@ the specific language governing permissions and limitations under the Apache Lic
} }
}, },
isPlaceholderOptionSelected: function() {
var placeholderOption;
return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.is(':selected')) ||
(this.opts.element.val() === "") ||
(this.opts.element.val() === undefined) ||
(this.opts.element.val() === null);
},
// single // single
prepareOpts: function () { prepareOpts: function () {
var opts = this.parent.prepareOpts.apply(this, arguments), var opts = this.parent.prepareOpts.apply(this, arguments),
@ -1997,9 +2074,9 @@ the specific language governing permissions and limitations under the Apache Lic
// single // single
getPlaceholder: function() { getPlaceholder: function() {
// if a placeholder is specified on a single select without the first empty option ignore it // if a placeholder is specified on a single select without a valid placeholder option ignore it
if (this.select) { if (this.select) {
if (this.select.find("option").first().text() !== "") { if (this.getPlaceholderOption() === undefined) {
return undefined; return undefined;
} }
} }
@ -2011,10 +2088,10 @@ the specific language governing permissions and limitations under the Apache Lic
setPlaceholder: function () { setPlaceholder: function () {
var placeholder = this.getPlaceholder(); var placeholder = this.getPlaceholder();
if (this.opts.element.val() === "" && placeholder !== undefined) { if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
// check for a first blank option if attached to a select // check for a placeholder option if attached to a select
if (this.select && this.select.find("option:first").text() !== "") return; if (this.select && this.getPlaceholderOption() === undefined) return;
this.selection.find("span").html(this.opts.escapeMarkup(placeholder)); this.selection.find("span").html(this.opts.escapeMarkup(placeholder));
@ -2039,22 +2116,27 @@ the specific language governing permissions and limitations under the Apache Lic
// and highlight it // and highlight it
if (noHighlightUpdate !== false) { if (noHighlightUpdate !== false) {
this.highlight(selected); if (initial === true && selected >= 0) {
} this.highlight(selected);
} else {
// show the search box if this is the first we got the results and there are enough of them for search this.highlight(0);
if (initial === true && this.showSearchInput === false) {
var min=this.opts.minimumResultsForSearch;
if (min>=0) {
this.showSearch(countResults(data.results)>=min);
} }
} }
// hide the search box if this is the first we got the results and there are enough of them for search
if (initial === true) {
var min = this.opts.minimumResultsForSearch;
if (min >= 0) {
this.showSearch(countResults(data.results) >= min);
}
}
}, },
// single // single
showSearch: function(showSearchInput) { showSearch: function(showSearchInput) {
if (this.showSearchInput === showSearchInput) return;
this.showSearchInput = showSearchInput; this.showSearchInput = showSearchInput;
this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput); this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
@ -2087,7 +2169,7 @@ the specific language governing permissions and limitations under the Apache Lic
// single // single
updateSelection: function (data) { updateSelection: function (data) {
var container=this.selection.find("span"), formatted; var container=this.selection.find("span"), formatted, cssClass;
this.selection.data("select2-data", data); this.selection.data("select2-data", data);
@ -2096,6 +2178,10 @@ the specific language governing permissions and limitations under the Apache Lic
if (formatted !== undefined) { if (formatted !== undefined) {
container.append(this.opts.escapeMarkup(formatted)); container.append(this.opts.escapeMarkup(formatted));
} }
cssClass=this.opts.formatSelectionCssClass(data, container);
if (cssClass !== undefined) {
container.addClass(cssClass);
}
this.selection.removeClass("select2-default"); this.selection.removeClass("select2-default");
@ -2135,14 +2221,14 @@ the specific language governing permissions and limitations under the Apache Lic
this.triggerChange({added: data, removed:oldData}); this.triggerChange({added: data, removed:oldData});
} }
} else { } 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,'',0] - 0 is legal // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
if (!val && val !== 0) { if (!val && val !== 0) {
this.clear(triggerChange); this.clear(triggerChange);
return; return;
} }
if (this.opts.initSelection === undefined) {
throw new Error("cannot call val() if initSelection() is not defined");
}
this.opts.element.val(val); this.opts.element.val(val);
this.opts.initSelection(this.opts.element, function(data){ this.opts.initSelection(this.opts.element, function(data){
self.opts.element.val(!data ? "" : self.id(data)); self.opts.element.val(!data ? "" : self.id(data));
@ -2374,6 +2460,7 @@ the specific language governing permissions and limitations under the Apache Lic
return; return;
case KEY.TAB: case KEY.TAB:
this.selectHighlighted({noFocus:true}); this.selectHighlighted({noFocus:true});
this.close();
return; return;
case KEY.ESC: case KEY.ESC:
this.cancel(e); this.cancel(e);
@ -2424,7 +2511,7 @@ the specific language governing permissions and limitations under the Apache Lic
this.opts.element.trigger($.Event("select2-blur")); this.opts.element.trigger($.Event("select2-blur"));
})); }));
this.container.on("mousedown", selector, this.bind(function (e) { this.container.on("click", selector, this.bind(function (e) {
if (!this.isInterfaceEnabled()) return; if (!this.isInterfaceEnabled()) return;
if ($(e.target).closest(".select2-search-choice").length > 0) { if ($(e.target).closest(".select2-search-choice").length > 0) {
// clicked inside a select2 search choice, do not open // clicked inside a select2 search choice, do not open
@ -2632,11 +2719,16 @@ the specific language governing permissions and limitations under the Apache Lic
var choice = enableChoice ? enabledItem : disabledItem, var choice = enableChoice ? enabledItem : disabledItem,
id = this.id(data), id = this.id(data),
val = this.getVal(), val = this.getVal(),
formatted; formatted,
cssClass;
formatted=this.opts.formatSelection(data, choice.find("div")); formatted=this.opts.formatSelection(data, choice.find("div"));
if (formatted != undefined) { if (formatted != undefined) {
choice.find("div").replaceWith("<div title='"+this.opts.escapeMarkup(formatted)+"'>"+this.opts.escapeMarkup(formatted)+"</div>"); choice.find("div").replaceWith("<div>"+this.opts.escapeMarkup(formatted)+"</div>");
}
cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
if (cssClass != undefined) {
choice.addClass(cssClass);
} }
if(enableChoice){ if(enableChoice){
@ -2729,7 +2821,11 @@ the specific language governing permissions and limitations under the Apache Lic
//If all results are chosen render formatNoMAtches //If all results are chosen render formatNoMAtches
if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){ if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>"); if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>");
}
}
} }
}, },
@ -2852,7 +2948,7 @@ the specific language governing permissions and limitations under the Apache Lic
} }
this.opts.initSelection(this.opts.element, function(data){ this.opts.initSelection(this.opts.element, function(data){
var ids=$(data).map(self.id); var ids=$.map(data, self.id);
self.setVal(ids); self.setVal(ids);
self.updateSelection(data); self.updateSelection(data);
self.clearSearch(); self.clearSearch();
@ -2924,9 +3020,10 @@ the specific language governing permissions and limitations under the Apache Lic
var args = Array.prototype.slice.call(arguments, 0), var args = Array.prototype.slice.call(arguments, 0),
opts, opts,
select2, select2,
value, multiple, method, value, multiple,
allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "readonly", "positionDropdown", "data"], allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "readonly", "positionDropdown", "data", "search"],
valueMethods = ["val", "opened", "isFocused", "container", "data"]; valueMethods = ["val", "opened", "isFocused", "container", "data"],
methodsMap = { search: "externalSearch" };
this.each(function () { this.each(function () {
if (args.length === 0 || typeof(args[0]) === "object") { if (args.length === 0 || typeof(args[0]) === "object") {
@ -2951,10 +3048,17 @@ the specific language governing permissions and limitations under the Apache Lic
value = undefined; value = undefined;
select2 = $(this).data("select2"); select2 = $(this).data("select2");
if (select2 === undefined) return; if (select2 === undefined) return;
if (args[0] === "container") {
value=select2.container; method=args[0];
if (method === "container") {
value = select2.container;
} else if (method === "dropdown") {
value = select2.dropdown;
} else { } else {
value = select2[args[0]].apply(select2, args.slice(1)); if (methodsMap[method]) method = methodsMap[method];
value = select2[method].apply(select2, args.slice(1));
} }
if (indexOf(args[0], valueMethods) >= 0) { if (indexOf(args[0], valueMethods) >= 0) {
return false; return false;
@ -2988,6 +3092,7 @@ the specific language governing permissions and limitations under the Apache Lic
return results; return results;
}, },
formatResultCssClass: function(data) {return undefined;}, formatResultCssClass: function(data) {return undefined;},
formatSelectionCssClass: function(data, container) {return undefined;},
formatNoMatches: function () { return "No matches found"; }, formatNoMatches: function () { return "No matches found"; },
formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); }, formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); }, formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
@ -3005,21 +3110,7 @@ the specific language governing permissions and limitations under the Apache Lic
separator: ",", separator: ",",
tokenSeparators: [], tokenSeparators: [],
tokenizer: defaultTokenizer, tokenizer: defaultTokenizer,
escapeMarkup: function (markup) { escapeMarkup: defaultEscapeMarkup,
var replace_map = {
'\\': '&#92;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
"/": '&#47;'
};
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
return replace_map[match];
});
},
blurOnChange: false, blurOnChange: false,
selectOnBlur: false, selectOnBlur: false,
adaptContainerCssClass: function(c) { return c; }, adaptContainerCssClass: function(c) { return c; },
@ -3043,7 +3134,8 @@ the specific language governing permissions and limitations under the Apache Lic
tags: tags tags: tags
}, util: { }, util: {
debounce: debounce, debounce: debounce,
markMatch: markMatch markMatch: markMatch,
escapeMarkup: defaultEscapeMarkup
}, "class": { }, "class": {
"abstract": AbstractSelect2, "abstract": AbstractSelect2,
"single": SingleSelect2, "single": SingleSelect2,

@ -6,19 +6,19 @@ You should manually include select2 distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script> <script src="select2/select2.js"></script>
For make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): To make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link> <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
**Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state. **Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
The solution is to load source manually and assign statically. You need initially put both `data-value` and element's text youself.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@since 1.4.1 @since 1.4.1
@final @final
@example @example
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a> <a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
<script> <script>
$(function(){ $(function(){
$('#country').editable({ $('#country').editable({
@ -39,56 +39,42 @@ $(function(){
var Constructor = function (options) { var Constructor = function (options) {
this.init('select2', options, Constructor.defaults); this.init('select2', options, Constructor.defaults);
options.select2 = options.select2 || {}; options.select2 = options.select2 || {};
var that = this,
mixin = { //mixin to select2 options
placeholder: options.placeholder
};
//detect whether it is multi-valued
this.isMultiple = options.select2.tags || options.select2.multiple;
//if not `tags` mode, we need define initSelection to 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 this.sourceData = null;
mixin.initSelection = function (element, callback) {
//temp: try update results //if not `tags` mode, use source
/* if(!options.select2.tags && options.source) {
if(options.select2 && options.select2.ajax) { var source = options.source;
console.log('attached'); //if source is function, call it (once!)
var original = $(element).data('select2').postprocessResults; if ($.isFunction(options.source)) {
console.log(original); source = options.source.call(options.scope);
$(element).data('select2').postprocessResults = function(data, initial) { }
console.log('postprocess');
// this.element.triggerHandler('loaded', [data]);
original.apply(this, arguments);
}
// $(element).on('loaded', function(){console.log('loaded');}); if (typeof source === 'string') {
$(element).data('select2').updateResults(true); options.select2.ajax = options.select2.ajax || {};
//some default ajax params
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
} }
*/ if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
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];
} }
options.select2.ajax.url = source;
callback(data); } else {
}; //todo: possible convert x-editable source to select2 source format
} options.select2.data = source;
this.sourceData = source;
}
}
//overriding objects in config (as by default jQuery extend() is not recursive) //overriding objects in config (as by default jQuery extend() is not recursive)
this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2); this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
//detect whether it is multi-valued
this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2);
}; };
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@ -96,21 +82,17 @@ $(function(){
$.extend(Constructor.prototype, { $.extend(Constructor.prototype, {
render: function() { render: function() {
this.setClass(); this.setClass();
//apply select2 //apply select2
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
//when data is loaded via ajax, we need to know when it's done //when data is loaded via ajax, we need to know when it's done to populate listData
if('ajax' in this.options.select2) { if(this.isRemote) {
/* //listen to loaded event to populate data
console.log('attached'); this.$input.on('select2-loaded', $.proxy(function(e) {
var original = this.$input.data('select2').postprocessResults; this.sourceData = e.items.results;
this.$input.data('select2').postprocessResults = function(data, initial) { }, this));
this.element.triggerHandler('loaded', [data]);
original.apply(this, arguments);
}
*/
} }
//trigger resize of editableform to re-position container in multi-valued mode //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@ -122,20 +104,16 @@ $(function(){
value2html: function(value, element) { value2html: function(value, element) {
var text = '', data; var text = '', data;
if(this.$input) { //called when submitting form and select2 already exists
data = this.$input.select2('data'); if(this.options.select2.tags) { //in tags mode just assign value
} else { //on init (autotext) data = value;
//here select2 instance not created yet and data may be even not loaded. } else if(this.sourceData) {
//we can check data/tags property of select config and if exist lookup text data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
if(this.options.select2.tags) { } else {
data = value; //can not get list of possible values (e.g. autotext for select2 with ajax source)
} else if(this.options.select2.data) {
data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
} else {
//if('ajax' in this.options.select2) {
}
} }
//data may be array (when multiple values allowed)
if($.isArray(data)) { if($.isArray(data)) {
//collect selected data and show with separator //collect selected data and show with separator
text = []; text = [];
@ -156,7 +134,26 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit) //for remote source .val() is not working, need to look in sourceData
if(this.isRemote) {
//todo: check value for array
var item, items;
//if sourceData loaded, use it to get text for display
if(this.sourceData) {
items = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
if(items.length) {
item = items[0];
}
}
//if item not found by sourceData, use element text (e.g. for the first show)
if(!item) {
item = {id: value, text: $(this.options.scope).text()};
}
//select2('data', ...) allows to set both id and text --> usefull for initial show when items are not loaded
this.$input.select2('data', item).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
} else {
this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
}
}, },
input2value: function() { input2value: function() {

@ -21,6 +21,7 @@
<div> <div>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
<div id="qunit-fixture1"></div>
<div id="async-fixture" style="padding-left:300px"></div> <div id="async-fixture" style="padding-left:300px"></div>
</div> </div>
</body> </body>

@ -36,8 +36,10 @@ $(function () {
//groups as array //groups as array
window.groupsArr = []; window.groupsArr = [];
window.groupsArr2 = [];
for(var i in groups) { for(var i in groups) {
groupsArr.push({value: i, text: groups[i]}); groupsArr.push({value: i, text: groups[i]});
groupsArr2.push({id: i, text: groups[i]});
} }
window.size = groupsArr.length; window.size = groupsArr.length;
@ -46,6 +48,16 @@ $(function () {
url: 'groups.php', url: 'groups.php',
responseText: groups responseText: groups
}); });
$.mockjax({
url: 'groupsArr',
responseText: groupsArr
});
$.mockjax({
url: 'groupsArr2',
responseText: groupsArr2
});
$.mockjax({ $.mockjax({
url: 'groups-error.php', url: 'groups-error.php',

@ -8,7 +8,7 @@ $(function () {
} }
}); });
asyncTest("data (single)", function () { asyncTest("init-change-save (not multiple, local)", function () {
var s = 2, text = 'text2', var s = 2, text = 'text2',
e = $('<a href="#" data-type="select2" data-name="select2" data-value="'+s+'"></a>').appendTo(fx).editable({ e = $('<a href="#" data-type="select2" data-name="select2" data-value="'+s+'"></a>').appendTo(fx).editable({
source: [{id: 1, text: 'text1'}, {id: s, text: text}, {id: 3, text: 'text3'}], source: [{id: 1, text: 'text1'}, {id: s, text: text}, {id: 3, text: 'text3'}],
@ -49,7 +49,7 @@ $(function () {
}, timeout); }, timeout);
}); });
asyncTest("data (multiple)", function () { asyncTest("init-change-save (multiple, local)", function () {
var s = '2,3', text = 'text2, text3', var s = '2,3', text = 'text2, text3',
e = $('<a href="#" data-type="select2" data-name="select2" data-value="'+s+'"></a>').appendTo(fx).editable({ e = $('<a href="#" data-type="select2" data-name="select2" data-value="'+s+'"></a>').appendTo(fx).editable({
source: [{id: 1, text: 'text1'}, {id: 2, text: 'text2'}, {id: 3, text: 'text3'}], source: [{id: 1, text: 'text1'}, {id: 2, text: 'text2'}, {id: 3, text: 'text3'}],
@ -97,7 +97,7 @@ $(function () {
}, timeout); }, timeout);
}); });
asyncTest("tags", function () { asyncTest("tags (local)", function () {
var s = 'text2,abc', text = 'text2, abc', var s = 'text2,abc', text = 'text2, abc',
e = $('<a href="#" data-type="select2" data-name="select2">'+text+'</a>').appendTo(fx).editable({ e = $('<a href="#" data-type="select2" data-name="select2">'+text+'</a>').appendTo(fx).editable({
viewseparator: ', ', viewseparator: ', ',
@ -141,6 +141,84 @@ $(function () {
start(); start();
}, timeout); }, timeout);
}); });
test("setValue (local)", function () {
var e = $('<a href="#" data-type="select2" data-name="select2" data-value="1">test2</a>').appendTo('#qunit-fixture').editable({
source: [{id: 1, text: 'text1'}, {id: 2, text: 'text2'}, {id: 3, text: 'text3'}]
});
//autotext
equal(e.data('editable').value, 1, 'initial value ok');
//setValue before open
e.editable('setValue', 2);
equal(e.data('editable').value, 2, 'value ok');
equal(e.text(), 'text2', 'text ok');
//open
e.click();
var p = tip(e);
p.find('.editable-cancel').click();
//setValue after hide
e.editable('setValue', 3);
equal(e.data('editable').value, 3, 'value ok');
equal(e.text(), 'text3', 'text ok');
});
asyncTest("init-change-save (not multiple, remote)", function () {
var s = 2, text = groups[s],
newVal = 0, newText = groups[newVal],
e = $('<a href="#" data-type="select2" data-name="select2" data-value="'+s+'">'+text+'</a>').appendTo(fx).editable({
source: 'groupsArr2'
});
e.click();
var p = tip(e);
ok(p.is(':visible'), 'popover visible');
equal(p.find('.select2-choice span').text(), text, 'selected text correct');
var $input = p.find('input[type="hidden"]');
ok($input.length, 'input exists');
ok($input.select2, 'select2 applied');
equal($input.val(), e.data('editable').value, 'selected value correct');
//click to load items
p.find('.select2-choice').mousedown();
setTimeout(function() {
equal($('.select2-results li').length, groupsArr2.length, 'items loaded');
equal($('.select2-results .select2-highlighted > .select2-result-label').text(), text, 'highlight ok');
//select new value (0)
$('.select2-results li').eq(newVal).mouseup();
equal(p.find('.select2-choice span').text(), newText, 'new selected text ok');
//submit
p.find('form').submit();
setTimeout(function() {
ok(!p.is(':visible'), 'popover closed');
equal(e.data('editable').value, newVal, 'new value ok');
equal(e.text(), newText, 'new text ok');
//open again
e.click();
p = tip(e);
equal(p.find('.select2-choice span').text(), newText, 'text ok on second open');
equal($input.val(), newVal, 'selected value ok on second open');
//setValue in closed state
p.find('.editable-cancel').click();
e.editable('setValue', 1);
equal(e.data('editable').value, 1, 'setValue: value ok');
equal(e.text(), groups[1], 'setValue: text ok');
e.remove();
start();
}, timeout);
}, timeout);
});
}); });