";
$document = $(document);
@@ -109,6 +110,19 @@ the specific language governing permissions and limitations under the Apache Lic
return -1;
}
+ function measureScrollbar () {
+ var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
+ $template.appendTo('body');
+
+ var dim = {
+ width: $template.width() - $template[0].clientWidth,
+ height: $template.height() - $template[0].clientHeight
+ };
+ $template.remove();
+
+ return dim;
+ }
+
/**
* Compares equality of a and b
* @param a
@@ -143,12 +157,12 @@ the specific language governing permissions and limitations under the Apache Lic
function installKeyUpChangeEvent(element) {
var key="keyup-change-value";
- element.bind("keydown", function () {
+ element.on("keydown", function () {
if ($.data(element, key) === undefined) {
$.data(element, key, element.val());
}
});
- element.bind("keyup", function () {
+ element.on("keyup", function () {
var val= $.data(element, key);
if (val !== undefined && element.val() !== val) {
$.removeData(element, key);
@@ -157,7 +171,7 @@ the specific language governing permissions and limitations under the Apache Lic
});
}
- $document.bind("mousemove", function (e) {
+ $document.on("mousemove", function (e) {
lastMousePosition = {x: e.pageX, y: e.pageY};
});
@@ -168,7 +182,7 @@ the specific language governing permissions and limitations under the Apache Lic
* the elements under the pointer are scrolled.
*/
function installFilteredMouseMove(element) {
- element.bind("mousemove", function (e) {
+ element.on("mousemove", function (e) {
var lastpos = lastMousePosition;
if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
$(e.target).trigger("mousemove-filtered", e);
@@ -213,7 +227,7 @@ the specific language governing permissions and limitations under the Apache Lic
function installDebouncedScroll(threshold, element) {
var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
- element.bind("scroll", function (e) {
+ element.on("scroll", function (e) {
if (indexOf(e.target, element.get()) >= 0) notify(e);
});
}
@@ -248,6 +262,23 @@ the specific language governing permissions and limitations under the Apache Lic
}, 0);
}
+ function getCursorInfo(el) {
+ el = $(el)[0];
+ var offset = 0;
+ var length = 0;
+ if ('selectionStart' in el) {
+ offset = el.selectionStart;
+ length = el.selectionEnd - offset;
+ } else if ('selection' in document) {
+ el.focus();
+ var sel = document.selection.createRange();
+ length = document.selection.createRange().text.length;
+ sel.moveStart('character', -el.value.length);
+ offset = sel.text.length - length;
+ }
+ return { offset: offset, length: length };
+ }
+
function killEvent(event) {
event.preventDefault();
event.stopPropagation();
@@ -328,11 +359,11 @@ the specific language governing permissions and limitations under the Apache Lic
* Produces an ajax-based query function
*
* @param options object containing configuration paramters
+ * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
* @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
* @param options.url url for the data
* @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
* @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
- * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request
* @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
* @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
* The expected format is an object containing the following keys:
@@ -355,9 +386,15 @@ the specific language governing permissions and limitations under the Apache Lic
var requestNumber = requestSequence, // this request's sequence number
data = options.data, // ajax data function
url = ajaxUrl, // ajax url string or function
- transport = options.transport || $.ajax,
- type = options.type || 'GET', // set type of request (GET or POST)
- params = {};
+ transport = options.transport || $.fn.select2.ajaxDefaults.transport,
+ // deprecated - to be removed in 4.0 - use params instead
+ deprecated = {
+ type: options.type || 'GET', // set type of request (GET or POST)
+ cache: options.cache || false,
+ jsonpCallback: options.jsonpCallback||undefined,
+ dataType: options.dataType||"json"
+ },
+ params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
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;
@@ -376,8 +413,6 @@ the specific language governing permissions and limitations under the Apache Lic
url: url,
dataType: options.dataType,
data: data,
- type: type,
- cache: false,
success: function (data) {
if (requestNumber < requestSequence) {
return;
@@ -427,7 +462,7 @@ the specific language governing permissions and limitations under the Apache Lic
text = dataItem.text;
// if text is not a function we assume it to be a key name
if (!$.isFunction(text)) {
- dataText = data.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
+ dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
text = function (item) { return item[dataText]; };
}
}
@@ -590,7 +625,7 @@ the specific language governing permissions and limitations under the Apache Lic
// abstract
init: function (opts) {
- var results, search, resultsSelector = ".select2-results", mask;
+ var results, search, resultsSelector = ".select2-results", disabled, readonly;
// prepare options
this.opts = opts = this.prepareOpts(opts);
@@ -603,7 +638,6 @@ the specific language governing permissions and limitations under the Apache Lic
this.destroy();
}
- this.enabled=true;
this.container = this.createContainer();
this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
@@ -618,14 +652,12 @@ the specific language governing permissions and limitations under the Apache Lic
this.container.css(evaluate(opts.containerCss));
this.container.addClass(evaluate(opts.containerCssClass));
- this.elementTabIndex = this.opts.element.attr("tabIndex");
+ this.elementTabIndex = this.opts.element.attr("tabindex");
// swap container for the element
this.opts.element
.data("select2", this)
- .addClass("select2-offscreen")
- .bind("focus.select2", function() { $(this).select2("focus"); })
- .attr("tabIndex", "-1")
+ .attr("tabindex", "-1")
.before(this.container);
this.container.data("select2", this);
@@ -636,8 +668,6 @@ the specific language governing permissions and limitations under the Apache Lic
this.results = results = this.container.find(resultsSelector);
this.search = search = this.container.find("input.select2-input");
- search.attr("tabIndex", this.elementTabIndex);
-
this.resultsPage = 0;
this.context = null;
@@ -645,10 +675,14 @@ the specific language governing permissions and limitations under the Apache Lic
this.initContainer();
installFilteredMouseMove(this.results);
- this.dropdown.delegate(resultsSelector, "mousemove-filtered touchstart touchmove touchend", this.bind(this.highlightUnderEvent));
+ this.dropdown.on("mousemove-filtered touchstart touchmove touchend", resultsSelector, this.bind(this.highlightUnderEvent));
installDebouncedScroll(80, this.results);
- this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
+ this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
+
+ // do not propagate change event from the search field out of the component
+ $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
+ $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
// if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
if ($.fn.mousewheel) {
@@ -665,11 +699,11 @@ the specific language governing permissions and limitations under the Apache Lic
}
installKeyUpChangeEvent(search);
- search.bind("keyup-change input paste", this.bind(this.updateResults));
- search.bind("focus", function () { search.addClass("select2-focused"); });
- search.bind("blur", function () { search.removeClass("select2-focused");});
+ search.on("keyup-change input paste", this.bind(this.updateResults));
+ search.on("focus", function () { search.addClass("select2-focused"); });
+ search.on("blur", function () { search.removeClass("select2-focused");});
- this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) {
+ this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
if ($(e.target).closest(".select2-result-selectable").length > 0) {
this.highlightUnderEvent(e);
this.selectHighlighted(e);
@@ -679,7 +713,7 @@ the specific language governing permissions and limitations under the Apache Lic
// trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
// for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
// dom it will trigger the popup close, which is not what we want
- this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); });
+ this.dropdown.on("click mouseup mousedown", function (e) { e.stopPropagation(); });
if ($.isFunction(this.opts.initSelection)) {
// initialize selection based on the current value of the source element
@@ -690,7 +724,24 @@ the specific language governing permissions and limitations under the Apache Lic
this.monitorSource();
}
- if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable();
+ if (opts.maximumInputLength !== null) {
+ this.search.attr("maxlength", opts.maximumInputLength);
+ }
+
+ var disabled = opts.element.prop("disabled");
+ if (disabled === undefined) disabled = false;
+ this.enable(!disabled);
+
+ var readonly = opts.element.prop("readonly");
+ if (readonly === undefined) readonly = false;
+ this.readonly(readonly);
+
+ // Calculate size of scrollbar
+ scrollBarDimensions = scrollBarDimensions || measureScrollbar();
+
+ this.autofocus = opts.element.prop("autofocus")
+ opts.element.prop("autofocus", false);
+ if (this.autofocus) this.focus();
},
// abstract
@@ -706,15 +757,37 @@ the specific language governing permissions and limitations under the Apache Lic
select2.opts.element
.removeClass("select2-offscreen")
.removeData("select2")
- .unbind(".select2")
- .attr({"tabIndex": this.elementTabIndex})
+ .off(".select2")
+ .attr({"tabindex": this.elementTabIndex})
+ .prop("autofocus", this.autofocus||false)
.show();
}
},
+ // abstract
+ optionToData: function(element) {
+ if (element.is("option")) {
+ return {
+ id:element.prop("value"),
+ text:element.text(),
+ element: element.get(),
+ css: element.attr("class"),
+ disabled: element.prop("disabled"),
+ locked: equal(element.attr("locked"), "locked")
+ };
+ } else if (element.is("optgroup")) {
+ return {
+ text:element.attr("label"),
+ children:[],
+ element: element.get(),
+ css: element.attr("class")
+ };
+ }
+ },
+
// abstract
prepareOpts: function (opts) {
- var element, select, idKey, ajaxUrl;
+ var element, select, idKey, ajaxUrl, self = this;
element = opts.element;
@@ -733,7 +806,7 @@ the specific language governing permissions and limitations under the Apache Lic
opts = $.extend({}, {
populateResults: function(container, results, query) {
- var populate, data, result, children, id=this.opts.id, self=this;
+ var populate, data, result, children, id=this.opts.id;
populate=function(results, container, depth) {
@@ -807,10 +880,10 @@ the specific language governing permissions and limitations under the Apache Lic
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"), disabled: equal(element.attr("disabled"), "disabled") });
+ collection.push(self.optionToData(element));
}
} else if (element.is("optgroup")) {
- group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")};
+ group=self.optionToData(element);
element.children().each2(function(i, elm) { process(elm, group.children); });
if (group.children.length>0) {
collection.push(group);
@@ -881,7 +954,7 @@ the specific language governing permissions and limitations under the Apache Lic
monitorSource: function () {
var el = this.opts.element, sync;
- el.bind("change.select2", this.bind(function (e) {
+ el.on("change.select2", this.bind(function (e) {
if (this.opts.element.data("select2-change-triggered") !== true) {
this.initSelection();
}
@@ -893,19 +966,13 @@ the specific language governing permissions and limitations under the Apache Lic
// sync enabled state
- enabled = this.opts.element.attr("disabled") !== "disabled";
- readonly = this.opts.element.attr("readonly") === "readonly";
-
- enabled = enabled && !readonly;
-
- if (this.enabled !== enabled) {
- if (enabled) {
- this.enable();
- } else {
- this.disable();
- }
- }
+ var disabled = el.prop("disabled");
+ if (disabled === undefined) disabled = false;
+ this.enable(!disabled);
+ var readonly = el.prop("readonly");
+ if (readonly === undefined) readonly = false;
+ this.readonly(readonly);
syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
this.container.addClass(evaluate(this.opts.containerCssClass));
@@ -916,17 +983,31 @@ the specific language governing permissions and limitations under the Apache Lic
});
// mozilla and IE
- el.bind("propertychange.select2 DOMAttrModified.select2", sync);
+ el.on("propertychange.select2 DOMAttrModified.select2", sync);
+
+
+ // hold onto a reference of the callback to work around a chromium bug
+ if (this.mutationCallback === undefined) {
+ this.mutationCallback = function (mutations) {
+ mutations.forEach(sync);
+ }
+ }
+
// safari and chrome
if (typeof WebKitMutationObserver !== "undefined") {
if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
- this.propertyObserver = new WebKitMutationObserver(function (mutations) {
- mutations.forEach(sync);
- });
+ this.propertyObserver = new WebKitMutationObserver(this.mutationCallback);
this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
}
},
+ // abstract
+ triggerSelect: function(data) {
+ var evt = $.Event("select2-selecting", { val: this.id(data), object: data });
+ this.opts.element.trigger(evt);
+ return !evt.isDefaultPrevented();
+ },
+
/**
* Triggers the change event on the source element
*/
@@ -950,24 +1031,46 @@ the specific language governing permissions and limitations under the Apache Lic
this.opts.element.blur();
},
- // abstract
- enable: function() {
- if (this.enabled) return;
-
- this.enabled=true;
- this.container.removeClass("select2-container-disabled");
- this.opts.element.removeAttr("disabled");
+ //abstract
+ isInterfaceEnabled: function()
+ {
+ return this.enabledInterface === true;
},
// abstract
- disable: function() {
- if (!this.enabled) return;
+ enableInterface: function() {
+ var enabled = this._enabled && !this._readonly,
+ disabled = !enabled;
+ if (enabled === this.enabledInterface) return false;
+
+ this.container.toggleClass("select2-container-disabled", disabled);
this.close();
+ this.enabledInterface = enabled;
- this.enabled=false;
- this.container.addClass("select2-container-disabled");
- this.opts.element.attr("disabled", "disabled");
+ return true;
+ },
+
+ // abstract
+ enable: function(enabled) {
+ if (enabled === undefined) enabled = true;
+ if (this._enabled === enabled) return false;
+ this._enabled = enabled;
+
+ this.opts.element.prop("disabled", !enabled);
+ this.enableInterface();
+ return true;
+ },
+
+ // abstract
+ readonly: function(enabled) {
+ if (enabled === undefined) enabled = false;
+ if (this._readonly === enabled) return false;
+ this._readonly = enabled;
+
+ this.opts.element.prop("readonly", enabled);
+ this.enableInterface();
+ return true;
},
// abstract
@@ -977,22 +1080,37 @@ the specific language governing permissions and limitations under the Apache Lic
// abstract
positionDropdown: function() {
- var offset = this.container.offset(),
+ var $dropdown = this.dropdown,
+ offset = this.container.offset(),
height = this.container.outerHeight(false),
width = this.container.outerWidth(false),
- dropHeight = this.dropdown.outerHeight(false),
+ dropHeight = $dropdown.outerHeight(false),
viewPortRight = $(window).scrollLeft() + $(window).width(),
viewportBottom = $(window).scrollTop() + $(window).height(),
dropTop = offset.top + height,
dropLeft = offset.left,
enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
- dropWidth = this.dropdown.outerWidth(false),
+ dropWidth = $dropdown.outerWidth(false),
enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
- aboveNow = this.dropdown.hasClass("select2-drop-above"),
+ aboveNow = $dropdown.hasClass("select2-drop-above"),
bodyOffset,
above,
- css;
+ css,
+ resultsListNode;
+
+ if (this.opts.dropdownAutoWidth) {
+ resultsListNode = $('.select2-results', $dropdown)[0];
+ $dropdown.addClass('select2-drop-auto-width');
+ $dropdown.css('width', '');
+ // Add scrollbar width to dropdown if vertical scrollbar is present
+ dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
+ dropWidth > width ? width = dropWidth : dropWidth = width;
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
+ }
+ else {
+ this.container.removeClass('select2-drop-auto-width');
+ }
//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);
@@ -1022,11 +1140,11 @@ the specific language governing permissions and limitations under the Apache Lic
if (above) {
dropTop = offset.top - dropHeight;
this.container.addClass("select2-drop-above");
- this.dropdown.addClass("select2-drop-above");
+ $dropdown.addClass("select2-drop-above");
}
else {
this.container.removeClass("select2-drop-above");
- this.dropdown.removeClass("select2-drop-above");
+ $dropdown.removeClass("select2-drop-above");
}
css = $.extend({
@@ -1035,7 +1153,7 @@ the specific language governing permissions and limitations under the Apache Lic
width: width
}, evaluate(this.opts.dropdownCss));
- this.dropdown.css(css);
+ $dropdown.css(css);
},
// abstract
@@ -1044,7 +1162,9 @@ the specific language governing permissions and limitations under the Apache Lic
if (this.opened()) return false;
- event = $.Event("opening");
+ if (this._enabled === false || this._readonly === true) return false;
+
+ event = $.Event("select2-opening");
this.opts.element.trigger(event);
return !event.isDefaultPrevented();
},
@@ -1067,7 +1187,7 @@ the specific language governing permissions and limitations under the Apache Lic
if (!this.shouldOpen()) return false;
- window.setTimeout(this.bind(this.opening), 1);
+ this.opening();
return true;
},
@@ -1083,17 +1203,14 @@ the specific language governing permissions and limitations under the Apache Lic
orient = "orientationchange."+cid,
mask;
- this.clearDropdownAlignmentPreference();
-
this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
+ this.clearDropdownAlignmentPreference();
if(this.dropdown[0] !== this.body().children().last()[0]) {
this.dropdown.detach().appendTo(this.body());
}
- this.updateResults(true);
-
// create the dropdown mask if doesnt already exist
mask = $("#select2-drop-mask");
if (mask.length == 0) {
@@ -1101,7 +1218,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.bind("mousedown touchstart", function (e) {
+ mask.on("mousedown touchstart", function (e) {
var dropdown = $("#select2-drop"), self;
if (dropdown.length > 0) {
self=dropdown.data("select2");
@@ -1109,6 +1226,8 @@ the specific language governing permissions and limitations under the Apache Lic
self.selectHighlighted({noFocus: true});
}
self.close();
+ e.preventDefault();
+ e.stopPropagation();
}
});
}
@@ -1135,14 +1254,12 @@ the specific language governing permissions and limitations under the Apache Lic
// 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).bind(resize+" "+scroll+" "+orient, function (e) {
+ $(this).on(resize+" "+scroll+" "+orient, function (e) {
$("#select2-drop-mask").css(_makeMaskCss());
that.positionDropdown();
});
});
- this.focusSearch();
-
function _makeMaskCss() {
return {
width : Math.max(document.documentElement.scrollWidth, $(window).width()),
@@ -1161,7 +1278,7 @@ the specific language governing permissions and limitations under the Apache Lic
orient = "orientationchange."+cid;
// unbind event listeners
- this.container.parents().add(window).each(function () { $(this).unbind(scroll).unbind(resize).unbind(orient); });
+ this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
this.clearDropdownAlignmentPreference();
@@ -1170,9 +1287,11 @@ the specific language governing permissions and limitations under the Apache Lic
this.dropdown.hide();
this.container.removeClass("select2-dropdown-open");
this.results.empty();
+
+
this.clearSearch();
this.search.removeClass("select2-active");
- this.opts.element.trigger($.Event("close"));
+ this.opts.element.trigger($.Event("select2-close"));
},
// abstract
@@ -1203,7 +1322,7 @@ the specific language governing permissions and limitations under the Apache Lic
return;
}
- children = this.findHighlightableChoices();
+ children = this.findHighlightableChoices().find('.select2-result-label');
child = $(children[index]);
@@ -1231,7 +1350,6 @@ the specific language governing permissions and limitations under the Apache Lic
// abstract
findHighlightableChoices: function() {
- var h=this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)");
return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)");
},
@@ -1272,7 +1390,7 @@ the specific language governing permissions and limitations under the Apache Lic
data = choice.data("select2-data");
if (data) {
- this.opts.element.trigger({ type: "highlight", val: this.id(data), choice: data });
+ this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
}
},
@@ -1394,6 +1512,7 @@ the specific language governing permissions and limitations under the Apache Lic
} else {
render("");
}
+ if (initial) this.showSearch(true);
return;
}
@@ -1467,7 +1586,7 @@ the specific language governing permissions and limitations under the Apache Lic
postRender();
- this.opts.element.trigger({ type: "loaded", data:data });
+ this.opts.element.trigger({ type: "select2-loaded", data:data });
})});
},
@@ -1537,18 +1656,18 @@ the specific language governing permissions and limitations under the Apache Lic
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))/);
+ .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
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;
+ // 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") {
// finally, fallback on the calculated width of the element
return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
}
@@ -1577,13 +1696,13 @@ the specific language governing permissions and limitations under the Apache Lic
"class": "select2-container"
}).html([
"",
- " ",
+ " ",
"
" ,
@@ -1592,29 +1711,35 @@ the specific language governing permissions and limitations under the Apache Lic
},
// single
- disable: function() {
- if (!this.enabled) return;
-
- this.parent.disable.apply(this, arguments);
-
- this.focusser.attr("disabled", "disabled");
- },
-
- // single
- enable: function() {
- if (this.enabled) return;
-
- this.parent.enable.apply(this, arguments);
-
- this.focusser.removeAttr("disabled");
+ enableInterface: function() {
+ if (this.parent.enableInterface.apply(this, arguments)) {
+ this.focusser.prop("disabled", !this.isInterfaceEnabled());
+ }
},
// single
opening: function () {
+ var el, range;
this.parent.opening.apply(this, arguments);
- this.focusser.attr("disabled", "disabled");
+ 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
- this.opts.element.trigger($.Event("open"));
+ 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
+ // new text will appear *before* focusser.val()
+ el = this.search.get(0);
+ if (el.createTextRange) {
+ range = el.createTextRange();
+ range.collapse(false);
+ range.select();
+ }
+
+ this.focusser.prop("disabled", true).val("");
+ this.updateResults(true);
+ this.opts.element.trigger($.Event("select2-open"));
},
// single
@@ -1622,7 +1747,7 @@ the specific language governing permissions and limitations under the Apache Lic
if (!this.opened()) return;
this.parent.close.apply(this, arguments);
this.focusser.removeAttr("disabled");
- focus(this.focusser);
+ this.focusser.focus();
},
// single
@@ -1652,10 +1777,9 @@ the specific language governing permissions and limitations under the Apache Lic
var selection,
container = this.container,
- dropdown = this.dropdown,
- clickingInside = false;
+ dropdown = this.dropdown;
- this.showSearch(this.opts.minimumResultsForSearch >= 0);
+ this.showSearch(false);
this.selection = selection = container.find(".select2-choice");
@@ -1663,11 +1787,14 @@ the specific language governing permissions and limitations under the Apache Lic
// rewrite labels from original element to focusser
this.focusser.attr("id", "s2id_autogen"+nextUid());
+
$("label[for='" + this.opts.element.attr("id") + "']")
.attr('for', this.focusser.attr('id'));
- this.search.bind("keydown", this.bind(function (e) {
- if (!this.enabled) return;
+ this.focusser.attr("tabindex", this.elementTabIndex);
+
+ this.search.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
// prevent the page from scrolling
@@ -1681,11 +1808,13 @@ the specific language governing permissions and limitations under the Apache Lic
this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
killEvent(e);
return;
- case KEY.TAB:
case KEY.ENTER:
this.selectHighlighted();
killEvent(e);
return;
+ case KEY.TAB:
+ this.selectHighlighted({noFocus: true});
+ return;
case KEY.ESC:
this.cancel(e);
killEvent(e);
@@ -1693,7 +1822,7 @@ the specific language governing permissions and limitations under the Apache Lic
}
}));
- this.search.bind("blur", this.bind(function(e) {
+ this.search.on("blur", this.bind(function(e) {
// a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
// without this the search field loses focus which is annoying
if (document.activeElement === this.body().get(0)) {
@@ -1703,8 +1832,8 @@ the specific language governing permissions and limitations under the Apache Lic
}
}));
- this.focusser.bind("keydown", this.bind(function (e) {
- if (!this.enabled) return;
+ this.focusser.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
return;
@@ -1733,56 +1862,61 @@ the specific language governing permissions and limitations under the Apache Lic
installKeyUpChangeEvent(this.focusser);
- this.focusser.bind("keyup-change input", this.bind(function(e) {
+ this.focusser.on("keyup-change input", this.bind(function(e) {
+ e.stopPropagation();
if (this.opened()) return;
this.open();
- if (this.showSearchInput !== false) {
- this.search.val(this.focusser.val());
- }
- this.focusser.val("");
- killEvent(e);
}));
- selection.delegate("abbr", "mousedown", this.bind(function (e) {
- if (!this.enabled) return;
+ selection.on("mousedown", "abbr", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
this.clear();
killEventImmediately(e);
this.close();
this.selection.focus();
}));
- selection.bind("mousedown", this.bind(function (e) {
- clickingInside = true;
+ selection.on("mousedown", this.bind(function (e) {
+
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
if (this.opened()) {
this.close();
- } else if (this.enabled) {
+ } else if (this.isInterfaceEnabled()) {
this.open();
}
killEvent(e);
-
- clickingInside = false;
}));
- dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
+ dropdown.on("mousedown", this.bind(function() { this.search.focus(); }));
- selection.bind("focus", this.bind(function(e) {
+ selection.on("focus", this.bind(function(e) {
killEvent(e);
}));
- this.focusser.bind("focus", this.bind(function(){
+ this.focusser.on("focus", this.bind(function(){
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
this.container.addClass("select2-container-active");
- })).bind("blur", this.bind(function() {
+ })).on("blur", this.bind(function() {
if (!this.opened()) {
this.container.removeClass("select2-container-active");
+ this.opts.element.trigger($.Event("select2-blur"));
}
}));
- this.search.bind("focus", this.bind(function(){
+ this.search.on("focus", this.bind(function(){
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
this.container.addClass("select2-container-active");
- }))
+ }));
this.initContainerWidth();
+ this.opts.element.addClass("select2-offscreen");
this.setPlaceholder();
},
@@ -1797,7 +1931,7 @@ the specific language governing permissions and limitations under the Apache Lic
this.setPlaceholder();
if (triggerChange !== false){
- this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
+ this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
this.triggerChange({removed:data});
}
}
@@ -1810,6 +1944,7 @@ the specific language governing permissions and limitations under the Apache Lic
initSelection: function () {
var selected;
if (this.opts.element.val() === "" && this.opts.element.text() === "") {
+ this.updateSelection([]);
this.close();
this.setPlaceholder();
} else {
@@ -1826,15 +1961,15 @@ the specific language governing permissions and limitations under the Apache Lic
// single
prepareOpts: function () {
- var opts = this.parent.prepareOpts.apply(this, arguments);
+ var opts = this.parent.prepareOpts.apply(this, arguments),
+ self=this;
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(), element:selected});
+ callback(self.optionToData(selected));
};
} else if ("data" in opts) {
// install default initSelection when applied to hidden input and data is local
@@ -1885,7 +2020,7 @@ the specific language governing permissions and limitations under the Apache Lic
this.selection.addClass("select2-default");
- this.selection.find("abbr").hide();
+ this.container.removeClass("select2-allowclear");
}
},
@@ -1907,12 +2042,13 @@ the specific language governing permissions and limitations under the Apache Lic
this.highlight(selected);
}
- // hide the search box if this is the first we got the results and there are a few of them
+ // show the search box if this is the first we got the results and there are enough of them for search
- if (initial === true) {
+ if (initial === true && this.showSearchInput === false) {
var min=this.opts.minimumResultsForSearch;
- showSearchInput = min < 0 ? false : countResults(data.results) >= min;
- this.showSearch(showSearchInput);
+ if (min>=0) {
+ this.showSearch(countResults(data.results)>=min);
+ }
}
},
@@ -1921,26 +2057,31 @@ the specific language governing permissions and limitations under the Apache Lic
showSearch: function(showSearchInput) {
this.showSearchInput = showSearchInput;
- this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden");
+ this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
+ this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
//add "select2-with-searchbox" to the container if search box is shown
- $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
+ $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
},
// single
onSelect: function (data, options) {
- var old = this.opts.element.val();
+
+ if (!this.triggerSelect(data)) { return; }
+
+ var old = this.opts.element.val(),
+ oldData = this.data();
this.opts.element.val(this.id(data));
this.updateSelection(data);
- this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
+ this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
this.close();
if (!options || !options.noFocus)
this.selection.focus();
- if (!equal(old, this.id(data))) { this.triggerChange(); }
+ if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); }
},
// single
@@ -1959,13 +2100,17 @@ the specific language governing permissions and limitations under the Apache Lic
this.selection.removeClass("select2-default");
if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
- this.selection.find("abbr").show();
+ this.container.addClass("select2-allowclear");
}
},
// single
val: function () {
- var val, triggerChange = false, data = null, self = this;
+ var val,
+ triggerChange = false,
+ data = null,
+ self = this,
+ oldData = this.data();
if (arguments.length === 0) {
return this.opts.element.val();
@@ -1981,13 +2126,13 @@ the specific language governing permissions and limitations under the Apache Lic
this.select
.val(val)
.find(":selected").each2(function (i, elm) {
- data = {id: elm.attr("value"), text: elm.text(), element: elm.get(0)};
+ data = self.optionToData(elm);
return false;
});
this.updateSelection(data);
this.setPlaceholder();
if (triggerChange) {
- this.triggerChange();
+ this.triggerChange({added: data, removed:oldData});
}
} else {
if (this.opts.initSelection === undefined) {
@@ -1996,9 +2141,6 @@ the specific language governing permissions and limitations under the Apache Lic
// val is an id. !val is true for [undefined,null,'',0] - 0 is legal
if (!val && val !== 0) {
this.clear(triggerChange);
- if (triggerChange) {
- this.triggerChange();
- }
return;
}
this.opts.element.val(val);
@@ -2007,7 +2149,7 @@ the specific language governing permissions and limitations under the Apache Lic
self.updateSelection(data);
self.setPlaceholder();
if (triggerChange) {
- self.triggerChange();
+ self.triggerChange({added: data, removed:oldData});
}
});
}
@@ -2020,7 +2162,7 @@ the specific language governing permissions and limitations under the Apache Lic
},
// single
- data: function(value) {
+ data: function(value, triggerChange) {
var data;
if (arguments.length === 0) {
@@ -2029,10 +2171,14 @@ the specific language governing permissions and limitations under the Apache Lic
return data;
} else {
if (!value || value === "") {
- this.clear();
+ this.clear(triggerChange);
} else {
+ data = this.data();
this.opts.element.val(!value ? "" : this.id(value));
this.updateSelection(value);
+ if (triggerChange) {
+ this.triggerChange({added: value, removed:data});
+ }
}
}
}
@@ -2048,10 +2194,10 @@ the specific language governing permissions and limitations under the Apache Lic
"
",
//"
California
" ,
"
" ,
- " " ,
+ " " ,
"
" ,
"
" ,
- "
" ,
+ "
" ,
"
" ,
"
" ,
"
"].join(""));
@@ -2060,7 +2206,8 @@ the specific language governing permissions and limitations under the Apache Lic
// multi
prepareOpts: function () {
- var opts = this.parent.prepareOpts.apply(this, arguments);
+ var opts = this.parent.prepareOpts.apply(this, arguments),
+ self=this;
// TODO validate placeholder is a string if specified
@@ -2071,7 +2218,7 @@ the specific language governing permissions and limitations under the Apache Lic
var data = [];
element.find(":selected").each2(function (i, elm) {
- data.push({id: elm.attr("value"), text: elm.text(), element: elm[0]});
+ data.push(self.optionToData(elm));
});
callback(data);
};
@@ -2092,7 +2239,21 @@ the specific language governing permissions and limitations under the Apache Lic
return is_match;
},
callback: !$.isFunction(callback) ? $.noop : function() {
- callback(matches);
+ // reorder matches based on the order they appear in the ids array because right now
+ // they are in the order in which they appear in data array
+ var ordered = [];
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i];
+ for (var j = 0; j < matches.length; j++) {
+ var match = matches[j];
+ if (equal(id, opts.id(match))) {
+ ordered.push(match);
+ matches.splice(j, 1);
+ break;
+ }
+ }
+ }
+ callback(ordered);
}
});
};
@@ -2101,6 +2262,24 @@ the specific language governing permissions and limitations under the Apache Lic
return opts;
},
+ selectChoice: function (choice) {
+
+ var selected = this.container.find(".select2-search-choice-focus");
+ if (selected.length && choice && choice[0] == selected[0]) {
+
+ } else {
+ if (selected.length) {
+ this.opts.element.trigger("choice-deselected", selected);
+ }
+ selected.removeClass("select2-search-choice-focus");
+ if (choice && choice.length) {
+ this.close();
+ choice.addClass("select2-search-choice-focus");
+ this.opts.element.trigger("choice-selected", choice);
+ }
+ }
+ },
+
// multi
initContainer: function () {
@@ -2109,39 +2288,77 @@ the specific language governing permissions and limitations under the Apache Lic
this.searchContainer = this.container.find(".select2-search-field");
this.selection = selection = this.container.find(selector);
+ var _this = this;
+ this.selection.on("mousedown", ".select2-search-choice", function (e) {
+ //killEvent(e);
+ _this.search[0].focus();
+ _this.selectChoice($(this));
+ })
+ //.sortable({
+ // items: " > li",
+ // tolerance: "pointer",
+ // revert: 100
+ //});
+
// rewrite labels from original element to focusser
this.search.attr("id", "s2id_autogen"+nextUid());
$("label[for='" + this.opts.element.attr("id") + "']")
.attr('for', this.search.attr('id'));
- this.search.bind("input paste", this.bind(function() {
- if (!this.enabled) return;
+ this.search.on("input paste", this.bind(function() {
+ if (!this.isInterfaceEnabled()) return;
if (!this.opened()) {
this.open();
}
}));
- this.search.bind("keydown", this.bind(function (e) {
- if (!this.enabled) return;
+ this.search.attr("tabindex", this.elementTabIndex);
- if (e.which === KEY.BACKSPACE && this.search.val() === "") {
- this.close();
+ this.keydowns = 0;
+ this.search.on("keydown", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
- var choices,
- selected = selection.find(".select2-search-choice-focus");
- if (selected.length > 0) {
+ ++this.keydowns;
+ var selected = selection.find(".select2-search-choice-focus");
+ var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
+ var next = selected.next(".select2-search-choice:not(.select2-locked)");
+ var pos = getCursorInfo(this.search);
+
+ if (selected.length &&
+ (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
+ var selectedChoice = selected;
+ if (e.which == KEY.LEFT && prev.length) {
+ selectedChoice = prev;
+ }
+ else if (e.which == KEY.RIGHT) {
+ selectedChoice = next.length ? next : null;
+ }
+ else if (e.which === KEY.BACKSPACE) {
this.unselect(selected.first());
this.search.width(10);
- killEvent(e);
- return;
+ selectedChoice = prev.length ? prev : next;
+ } else if (e.which == KEY.DELETE) {
+ this.unselect(selected.first());
+ this.search.width(10);
+ selectedChoice = next.length ? next : null;
+ } else if (e.which == KEY.ENTER) {
+ selectedChoice = null;
}
- choices = selection.find(".select2-search-choice:not(.select2-locked)");
- if (choices.length > 0) {
- choices.last().addClass("select2-search-choice-focus");
+ this.selectChoice(selectedChoice);
+ killEvent(e);
+ if (!selectedChoice || !selectedChoice.length) {
+ this.open();
}
+ return;
+ } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
+ || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
+
+ this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
+ killEvent(e);
+ return;
} else {
- selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+ this.selectChoice(null);
}
if (this.opened()) {
@@ -2152,10 +2369,12 @@ the specific language governing permissions and limitations under the Apache Lic
killEvent(e);
return;
case KEY.ENTER:
- case KEY.TAB:
this.selectHighlighted();
killEvent(e);
return;
+ case KEY.TAB:
+ this.selectHighlighted({noFocus:true});
+ return;
case KEY.ESC:
this.cancel(e);
killEvent(e);
@@ -2190,56 +2409,59 @@ the specific language governing permissions and limitations under the Apache Lic
}));
- this.search.bind("keyup", this.bind(this.resizeSearch));
+ this.search.on("keyup", this.bind(function (e) {
+ this.keydowns = 0;
+ this.resizeSearch();
+ })
+ );
- this.search.bind("blur", this.bind(function(e) {
+ this.search.on("blur", this.bind(function(e) {
this.container.removeClass("select2-container-active");
this.search.removeClass("select2-focused");
+ this.selectChoice(null);
if (!this.opened()) this.clearSearch();
e.stopImmediatePropagation();
+ this.opts.element.trigger($.Event("select2-blur"));
}));
- this.container.delegate(selector, "mousedown", this.bind(function (e) {
- if (!this.enabled) return;
+ this.container.on("mousedown", 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
return;
}
+ this.selectChoice(null);
this.clearPlaceholder();
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
this.open();
this.focusSearch();
e.preventDefault();
}));
- this.container.delegate(selector, "focus", this.bind(function () {
- if (!this.enabled) return;
+ this.container.on("focus", selector, this.bind(function () {
+ if (!this.isInterfaceEnabled()) return;
+ if (!this.container.hasClass("select2-container-active")) {
+ this.opts.element.trigger($.Event("select2-focus"));
+ }
this.container.addClass("select2-container-active");
this.dropdown.addClass("select2-drop-active");
this.clearPlaceholder();
}));
this.initContainerWidth();
+ this.opts.element.addClass("select2-offscreen");
// 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);
+ enableInterface: function() {
+ if (this.parent.enableInterface.apply(this, arguments)) {
+ this.search.prop("disabled", !this.isInterfaceEnabled());
+ }
},
// multi
@@ -2266,13 +2488,14 @@ the specific language governing permissions and limitations under the Apache Lic
// multi
clearSearch: function () {
- var placeholder = this.getPlaceholder();
+ var placeholder = this.getPlaceholder(),
+ maxWidth = this.getMaxSearchWidth();
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
// we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
- this.search.width(this.getMaxSearchWidth());
+ this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
} else {
this.search.val("").width(10);
}
@@ -2294,7 +2517,9 @@ the specific language governing permissions and limitations under the Apache Lic
this.focusSearch();
- this.opts.element.trigger($.Event("open"));
+ this.updateResults(true);
+ this.search.focus();
+ this.opts.element.trigger($.Event("select2-open"));
},
// multi
@@ -2350,6 +2575,9 @@ the specific language governing permissions and limitations under the Apache Lic
// multi
onSelect: function (data, options) {
+
+ if (!this.triggerSelect(data)) { return; }
+
this.addSelectedChoice(data);
this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
@@ -2408,14 +2636,14 @@ the specific language governing permissions and limitations under the Apache Lic
formatted=this.opts.formatSelection(data, choice.find("div"));
if (formatted != undefined) {
- choice.find("div").replaceWith("
"+this.opts.escapeMarkup(formatted)+"
");
+ choice.find("div").replaceWith("
"+this.opts.escapeMarkup(formatted)+"
");
}
if(enableChoice){
choice.find(".select2-search-choice-close")
- .bind("mousedown", killEvent)
- .bind("click dblclick", this.bind(function (e) {
- if (!this.enabled) return;
+ .on("mousedown", killEvent)
+ .on("click dblclick", this.bind(function (e) {
+ if (!this.isInterfaceEnabled()) return;
$(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
this.unselect($(e.target));
@@ -2424,8 +2652,8 @@ the specific language governing permissions and limitations under the Apache Lic
this.focusSearch();
})).dequeue();
killEvent(e);
- })).bind("focus", this.bind(function () {
- if (!this.enabled) return;
+ })).on("focus", this.bind(function () {
+ if (!this.isInterfaceEnabled()) return;
this.container.addClass("select2-container-active");
this.dropdown.addClass("select2-drop-active");
}));
@@ -2472,7 +2700,7 @@ the specific language governing permissions and limitations under the Apache Lic
},
// multi
- postprocessResults: function () {
+ postprocessResults: function (data, initial, noHighlightUpdate) {
var val = this.getVal(),
choices = this.results.find(".select2-result"),
compound = this.results.find(".select2-result-with-children"),
@@ -2495,10 +2723,15 @@ the specific language governing permissions and limitations under the Apache Lic
}
});
- if (this.highlight() == -1){
+ if (this.highlight() == -1 && noHighlightUpdate !== false){
self.highlight(0);
}
+ //If all results are chosen render formatNoMAtches
+ if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
+ this.results.append("