select2 with ajax source, fixes #264
This commit is contained in:
parent
80ce493ac0
commit
a0fbbaf0af
262
src/inputs/select2/lib/select2.js
vendored
262
src/inputs/select2/lib/select2.js
vendored
@ -1,7 +1,7 @@
|
||||
/*
|
||||
/*
|
||||
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
|
||||
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,
|
||||
lastMousePosition, $document, scrollBarDimensions,
|
||||
lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
|
||||
|
||||
KEY = {
|
||||
TAB: 9,
|
||||
@ -172,7 +172,8 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
}
|
||||
|
||||
$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)));
|
||||
}
|
||||
|
||||
function defaultEscapeMarkup(markup) {
|
||||
var replace_map = {
|
||||
'\\': '\',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"/": '/'
|
||||
};
|
||||
|
||||
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
|
||||
return replace_map[match];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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 ($.isFunction(options.params)) {
|
||||
@ -526,7 +543,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
function checkFormatter(formatter, formatterName) {
|
||||
if ($.isFunction(formatter)) return true;
|
||||
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) {
|
||||
@ -746,21 +763,24 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
// abstract
|
||||
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 (select2 !== undefined) {
|
||||
|
||||
select2.container.remove();
|
||||
select2.dropdown.remove();
|
||||
select2.opts.element
|
||||
element
|
||||
.removeClass("select2-offscreen")
|
||||
.removeData("select2")
|
||||
.off(".select2")
|
||||
.attr({"tabindex": this.elementTabIndex})
|
||||
.prop("autofocus", this.autofocus||false)
|
||||
.show();
|
||||
.prop("autofocus", this.autofocus || false);
|
||||
if (this.elementTabIndex) {
|
||||
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) {
|
||||
var data = { results: [], more: false },
|
||||
term = query.term,
|
||||
children, firstChild, process;
|
||||
children, placeholderOption, process;
|
||||
|
||||
process=function(element, collection) {
|
||||
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
|
||||
if (this.getPlaceholder() !== undefined && children.length > 0) {
|
||||
firstChild = children[0];
|
||||
if ($(firstChild).text() === "") {
|
||||
children=children.not(firstChild);
|
||||
placeholderOption = this.getPlaceholderOption();
|
||||
if (placeholderOption) {
|
||||
children=children.not(placeholderOption);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1201,7 +1221,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
scroll = "scroll." + cid,
|
||||
resize = "resize."+cid,
|
||||
orient = "orientationchange."+cid,
|
||||
mask;
|
||||
mask, maskCss;
|
||||
|
||||
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.hide();
|
||||
mask.appendTo(this.body());
|
||||
mask.on("mousedown touchstart", function (e) {
|
||||
mask.on("mousedown touchstart click", function (e) {
|
||||
var dropdown = $("#select2-drop"), self;
|
||||
if (dropdown.length > 0) {
|
||||
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");
|
||||
|
||||
// show the elements
|
||||
mask.css(_makeMaskCss());
|
||||
mask.show();
|
||||
maskCss=_makeMaskCss();
|
||||
|
||||
mask.css(maskCss).show();
|
||||
|
||||
this.dropdown.show();
|
||||
this.positionDropdown();
|
||||
|
||||
this.dropdown.addClass("select2-drop-active");
|
||||
this.ensureHighlightVisible();
|
||||
|
||||
// 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
|
||||
var that = this;
|
||||
this.container.parents().add(window).each(function () {
|
||||
$(this).on(resize+" "+scroll+" "+orient, function (e) {
|
||||
$("#select2-drop-mask").css(_makeMaskCss());
|
||||
var maskCss=_makeMaskCss();
|
||||
$("#select2-drop-mask").css(maskCss);
|
||||
that.positionDropdown();
|
||||
});
|
||||
});
|
||||
@ -1294,6 +1316,16 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
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
|
||||
clearSearch: function () {
|
||||
|
||||
@ -1487,7 +1519,6 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
}
|
||||
|
||||
function postRender() {
|
||||
results.scrollTop(0);
|
||||
search.removeClass("select2-active");
|
||||
self.positionDropdown();
|
||||
}
|
||||
@ -1512,7 +1543,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
} else {
|
||||
render("");
|
||||
}
|
||||
if (initial) this.showSearch(true);
|
||||
if (initial && this.showSearch) this.showSearch(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1586,7 +1617,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
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) {
|
||||
this.highlight(index);
|
||||
this.onSelect(data, options);
|
||||
} else if (options.noFocus) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
|
||||
// abstract
|
||||
getPlaceholder: function () {
|
||||
var placeholderOption;
|
||||
return this.opts.element.attr("placeholder") ||
|
||||
this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
|
||||
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") {
|
||||
// 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(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'/>",
|
||||
"<div class='select2-drop select2-display-none'>" ,
|
||||
" <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>" ,
|
||||
" <ul class='select2-results'>" ,
|
||||
" </ul>" ,
|
||||
@ -1719,8 +1769,14 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
// single
|
||||
opening: function () {
|
||||
var el, range;
|
||||
var el, range, len;
|
||||
|
||||
if (this.opts.minimumResultsForSearch >= 0) {
|
||||
this.showSearch(true);
|
||||
}
|
||||
|
||||
this.parent.opening.apply(this, arguments);
|
||||
|
||||
if (this.showSearchInput !== false) {
|
||||
// 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
|
||||
@ -1728,13 +1784,16 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
this.search.val(this.focusser.val());
|
||||
}
|
||||
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()
|
||||
el = this.search.get(0);
|
||||
if (el.createTextRange) {
|
||||
range = el.createTextRange();
|
||||
range.collapse(false);
|
||||
range.select();
|
||||
} else if (el.setSelectionRange) {
|
||||
len = this.search.val().length;
|
||||
el.setSelectionRange(len, len);
|
||||
}
|
||||
|
||||
this.focusser.prop("disabled", true).val("");
|
||||
@ -1779,7 +1838,11 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
container = this.container,
|
||||
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");
|
||||
|
||||
@ -1846,6 +1909,9 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
if (e.which == KEY.DOWN || e.which == KEY.UP
|
||||
|| (e.which == KEY.ENTER && this.opts.openOnEnter)) {
|
||||
|
||||
if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
|
||||
|
||||
this.open();
|
||||
killEvent(e);
|
||||
return;
|
||||
@ -1863,9 +1929,11 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
installKeyUpChangeEvent(this.focusser);
|
||||
this.focusser.on("keyup-change input", this.bind(function(e) {
|
||||
e.stopPropagation();
|
||||
if (this.opened()) return;
|
||||
this.open();
|
||||
if (this.opts.minimumResultsForSearch >= 0) {
|
||||
e.stopPropagation();
|
||||
if (this.opened()) return;
|
||||
this.open();
|
||||
}
|
||||
}));
|
||||
|
||||
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) {
|
||||
var data=this.selection.data("select2-data");
|
||||
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.removeData("select2-data");
|
||||
this.setPlaceholder();
|
||||
@ -1943,7 +2012,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
// single
|
||||
initSelection: function () {
|
||||
var selected;
|
||||
if (this.opts.element.val() === "" && this.opts.element.text() === "") {
|
||||
if (this.isPlaceholderOptionSelected()) {
|
||||
this.updateSelection([]);
|
||||
this.close();
|
||||
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
|
||||
prepareOpts: function () {
|
||||
var opts = this.parent.prepareOpts.apply(this, arguments),
|
||||
@ -1997,9 +2074,9 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
// single
|
||||
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.find("option").first().text() !== "") {
|
||||
if (this.getPlaceholderOption() === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -2011,10 +2088,10 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
setPlaceholder: function () {
|
||||
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
|
||||
if (this.select && this.select.find("option:first").text() !== "") return;
|
||||
// check for a placeholder option if attached to a select
|
||||
if (this.select && this.getPlaceholderOption() === undefined) return;
|
||||
|
||||
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
|
||||
if (noHighlightUpdate !== false) {
|
||||
this.highlight(selected);
|
||||
}
|
||||
|
||||
// show the search box if this is the first we got the results and there are enough of them for search
|
||||
|
||||
if (initial === true && this.showSearchInput === false) {
|
||||
var min=this.opts.minimumResultsForSearch;
|
||||
if (min>=0) {
|
||||
this.showSearch(countResults(data.results)>=min);
|
||||
if (initial === true && selected >= 0) {
|
||||
this.highlight(selected);
|
||||
} else {
|
||||
this.highlight(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
showSearch: function(showSearchInput) {
|
||||
if (this.showSearchInput === showSearchInput) return;
|
||||
|
||||
this.showSearchInput = 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
|
||||
updateSelection: function (data) {
|
||||
|
||||
var container=this.selection.find("span"), formatted;
|
||||
var container=this.selection.find("span"), formatted, cssClass;
|
||||
|
||||
this.selection.data("select2-data", data);
|
||||
|
||||
@ -2096,6 +2178,10 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
if (formatted !== undefined) {
|
||||
container.append(this.opts.escapeMarkup(formatted));
|
||||
}
|
||||
cssClass=this.opts.formatSelectionCssClass(data, container);
|
||||
if (cssClass !== undefined) {
|
||||
container.addClass(cssClass);
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
} 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
|
||||
if (!val && val !== 0) {
|
||||
this.clear(triggerChange);
|
||||
return;
|
||||
}
|
||||
if (this.opts.initSelection === undefined) {
|
||||
throw new Error("cannot call val() if initSelection() is not defined");
|
||||
}
|
||||
this.opts.element.val(val);
|
||||
this.opts.initSelection(this.opts.element, function(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;
|
||||
case KEY.TAB:
|
||||
this.selectHighlighted({noFocus:true});
|
||||
this.close();
|
||||
return;
|
||||
case KEY.ESC:
|
||||
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.container.on("mousedown", selector, this.bind(function (e) {
|
||||
this.container.on("click", selector, this.bind(function (e) {
|
||||
if (!this.isInterfaceEnabled()) return;
|
||||
if ($(e.target).closest(".select2-search-choice").length > 0) {
|
||||
// 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,
|
||||
id = this.id(data),
|
||||
val = this.getVal(),
|
||||
formatted;
|
||||
formatted,
|
||||
cssClass;
|
||||
|
||||
formatted=this.opts.formatSelection(data, choice.find("div"));
|
||||
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){
|
||||
@ -2729,7 +2821,11 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
|
||||
//If all results are chosen render formatNoMAtches
|
||||
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){
|
||||
var ids=$(data).map(self.id);
|
||||
var ids=$.map(data, self.id);
|
||||
self.setVal(ids);
|
||||
self.updateSelection(data);
|
||||
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),
|
||||
opts,
|
||||
select2,
|
||||
value, multiple,
|
||||
allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "readonly", "positionDropdown", "data"],
|
||||
valueMethods = ["val", "opened", "isFocused", "container", "data"];
|
||||
method, value, multiple,
|
||||
allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "readonly", "positionDropdown", "data", "search"],
|
||||
valueMethods = ["val", "opened", "isFocused", "container", "data"],
|
||||
methodsMap = { search: "externalSearch" };
|
||||
|
||||
this.each(function () {
|
||||
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;
|
||||
select2 = $(this).data("select2");
|
||||
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 {
|
||||
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) {
|
||||
return false;
|
||||
@ -2988,6 +3092,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
return results;
|
||||
},
|
||||
formatResultCssClass: function(data) {return undefined;},
|
||||
formatSelectionCssClass: function(data, container) {return undefined;},
|
||||
formatNoMatches: function () { return "No matches found"; },
|
||||
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"); },
|
||||
@ -3005,21 +3110,7 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
separator: ",",
|
||||
tokenSeparators: [],
|
||||
tokenizer: defaultTokenizer,
|
||||
escapeMarkup: function (markup) {
|
||||
var replace_map = {
|
||||
'\\': '\',
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"/": '/'
|
||||
};
|
||||
|
||||
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
|
||||
return replace_map[match];
|
||||
});
|
||||
},
|
||||
escapeMarkup: defaultEscapeMarkup,
|
||||
blurOnChange: false,
|
||||
selectOnBlur: false,
|
||||
adaptContainerCssClass: function(c) { return c; },
|
||||
@ -3043,7 +3134,8 @@ the specific language governing permissions and limitations under the Apache Lic
|
||||
tags: tags
|
||||
}, util: {
|
||||
debounce: debounce,
|
||||
markMatch: markMatch
|
||||
markMatch: markMatch,
|
||||
escapeMarkup: defaultEscapeMarkup
|
||||
}, "class": {
|
||||
"abstract": AbstractSelect2,
|
||||
"single": SingleSelect2,
|
||||
|
139
src/inputs/select2/select2.js
vendored
139
src/inputs/select2/select2.js
vendored
@ -6,19 +6,19 @@ You should manually include select2 distributive:
|
||||
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
|
||||
<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>
|
||||
|
||||
**Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
|
||||
The solution is to load source manually and assign statically.
|
||||
**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
|
||||
You need initially put both `data-value` and element's text youself.
|
||||
|
||||
@class select2
|
||||
@extends abstractinput
|
||||
@since 1.4.1
|
||||
@final
|
||||
@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>
|
||||
$(function(){
|
||||
$('#country').editable({
|
||||
@ -39,56 +39,42 @@ $(function(){
|
||||
|
||||
var Constructor = function (options) {
|
||||
this.init('select2', options, Constructor.defaults);
|
||||
|
||||
|
||||
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
|
||||
mixin.initSelection = function (element, callback) {
|
||||
//temp: try update results
|
||||
/*
|
||||
if(options.select2 && options.select2.ajax) {
|
||||
console.log('attached');
|
||||
var original = $(element).data('select2').postprocessResults;
|
||||
console.log(original);
|
||||
$(element).data('select2').postprocessResults = function(data, initial) {
|
||||
console.log('postprocess');
|
||||
// this.element.triggerHandler('loaded', [data]);
|
||||
original.apply(this, arguments);
|
||||
}
|
||||
this.sourceData = null;
|
||||
|
||||
//if not `tags` mode, use source
|
||||
if(!options.select2.tags && options.source) {
|
||||
var source = options.source;
|
||||
//if source is function, call it (once!)
|
||||
if ($.isFunction(options.source)) {
|
||||
source = options.source.call(options.scope);
|
||||
}
|
||||
|
||||
// $(element).on('loaded', function(){console.log('loaded');});
|
||||
$(element).data('select2').updateResults(true);
|
||||
if (typeof source === 'string') {
|
||||
options.select2.ajax = options.select2.ajax || {};
|
||||
//some default ajax params
|
||||
if(!options.select2.ajax.data) {
|
||||
options.select2.ajax.data = function(term) {return { query:term };};
|
||||
}
|
||||
*/
|
||||
|
||||
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];
|
||||
if(!options.select2.ajax.results) {
|
||||
options.select2.ajax.results = function(data) { return {results:data };};
|
||||
}
|
||||
|
||||
callback(data);
|
||||
};
|
||||
}
|
||||
options.select2.ajax.url = source;
|
||||
} 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)
|
||||
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);
|
||||
@ -96,21 +82,17 @@ $(function(){
|
||||
$.extend(Constructor.prototype, {
|
||||
render: function() {
|
||||
this.setClass();
|
||||
|
||||
//apply select2
|
||||
this.$input.select2(this.options.select2);
|
||||
|
||||
//when data is loaded via ajax, we need to know when it's done
|
||||
if('ajax' in this.options.select2) {
|
||||
/*
|
||||
console.log('attached');
|
||||
var original = this.$input.data('select2').postprocessResults;
|
||||
this.$input.data('select2').postprocessResults = function(data, initial) {
|
||||
this.element.triggerHandler('loaded', [data]);
|
||||
original.apply(this, arguments);
|
||||
}
|
||||
*/
|
||||
//when data is loaded via ajax, we need to know when it's done to populate listData
|
||||
if(this.isRemote) {
|
||||
//listen to loaded event to populate data
|
||||
this.$input.on('select2-loaded', $.proxy(function(e) {
|
||||
this.sourceData = e.items.results;
|
||||
}, this));
|
||||
}
|
||||
|
||||
|
||||
//trigger resize of editableform to re-position container in multi-valued mode
|
||||
if(this.isMultiple) {
|
||||
@ -122,20 +104,16 @@ $(function(){
|
||||
|
||||
value2html: function(value, element) {
|
||||
var text = '', data;
|
||||
if(this.$input) { //called when submitting form and select2 already exists
|
||||
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');
|
||||
} else {
|
||||
//if('ajax' in this.options.select2) {
|
||||
}
|
||||
|
||||
if(this.options.select2.tags) { //in tags mode just assign value
|
||||
data = value;
|
||||
} else if(this.sourceData) {
|
||||
data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
|
||||
} else {
|
||||
//can not get list of possible values (e.g. autotext for select2 with ajax source)
|
||||
}
|
||||
|
||||
//data may be array (when multiple values allowed)
|
||||
if($.isArray(data)) {
|
||||
//collect selected data and show with separator
|
||||
text = [];
|
||||
@ -156,7 +134,26 @@ $(function(){
|
||||
},
|
||||
|
||||
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() {
|
||||
|
@ -21,6 +21,7 @@
|
||||
<div>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
<div id="qunit-fixture1"></div>
|
||||
<div id="async-fixture" style="padding-left:300px"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -36,8 +36,10 @@ $(function () {
|
||||
|
||||
//groups as array
|
||||
window.groupsArr = [];
|
||||
window.groupsArr2 = [];
|
||||
for(var i in groups) {
|
||||
groupsArr.push({value: i, text: groups[i]});
|
||||
groupsArr2.push({id: i, text: groups[i]});
|
||||
}
|
||||
|
||||
window.size = groupsArr.length;
|
||||
@ -46,6 +48,16 @@ $(function () {
|
||||
url: 'groups.php',
|
||||
responseText: groups
|
||||
});
|
||||
|
||||
$.mockjax({
|
||||
url: 'groupsArr',
|
||||
responseText: groupsArr
|
||||
});
|
||||
|
||||
$.mockjax({
|
||||
url: 'groupsArr2',
|
||||
responseText: groupsArr2
|
||||
});
|
||||
|
||||
$.mockjax({
|
||||
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',
|
||||
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'}],
|
||||
@ -49,7 +49,7 @@ $(function () {
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
asyncTest("data (multiple)", function () {
|
||||
asyncTest("init-change-save (multiple, local)", function () {
|
||||
var s = '2,3', text = 'text2, text3',
|
||||
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'}],
|
||||
@ -97,7 +97,7 @@ $(function () {
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
asyncTest("tags", function () {
|
||||
asyncTest("tags (local)", function () {
|
||||
var s = 'text2,abc', text = 'text2, abc',
|
||||
e = $('<a href="#" data-type="select2" data-name="select2">'+text+'</a>').appendTo(fx).editable({
|
||||
viewseparator: ', ',
|
||||
@ -141,6 +141,84 @@ $(function () {
|
||||
start();
|
||||
}, 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);
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user