diff --git a/README b/README
index 3f986bc..bfe1752 100644
--- a/README
+++ b/README
@@ -49,6 +49,14 @@ Special Thanks
Changelog Summary
=================
+### v4.1.0
+
+* Updated Select2 to version 3.4.1. **Please note**, that if you need any of the Select2 locale files, then you need to download them yourself from http://ivaynberg.github.com/select2/ and add to your project.
+* Address isssue[#36](https://github.com/applegrew/django-select2/issues/36) - Fix importerror under django1.6.
+* Fuxed the way `setup.py` handles unicode files while minfying them during package build.
+* Address isssue[#39](https://github.com/applegrew/django-select2/issues/39) - MultipleSelect2HiddenInput.render() should use mark_safe().
+* Address isssue[#45](https://github.com/applegrew/django-select2/issues/45) - MultipleSelect2HiddenInput returns bad has_changed value.
+
### v4.0.0
* Main version number bumped to bring your attention to the fact that the default Id generation scheme has now changed. Now Django Select2 will use hashed paths of fields to generate their Ids. The old scheme of generating random Ids are still there. You can enable that by setting `GENERATE_RANDOM_SELECT2_ID` to `True`.
diff --git a/README.md b/README.md
index 3f986bc..bfe1752 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,14 @@ Special Thanks
Changelog Summary
=================
+### v4.1.0
+
+* Updated Select2 to version 3.4.1. **Please note**, that if you need any of the Select2 locale files, then you need to download them yourself from http://ivaynberg.github.com/select2/ and add to your project.
+* Address isssue[#36](https://github.com/applegrew/django-select2/issues/36) - Fix importerror under django1.6.
+* Fuxed the way `setup.py` handles unicode files while minfying them during package build.
+* Address isssue[#39](https://github.com/applegrew/django-select2/issues/39) - MultipleSelect2HiddenInput.render() should use mark_safe().
+* Address isssue[#45](https://github.com/applegrew/django-select2/issues/45) - MultipleSelect2HiddenInput returns bad has_changed value.
+
### v4.0.0
* Main version number bumped to bring your attention to the fact that the default Id generation scheme has now changed. Now Django Select2 will use hashed paths of fields to generate their Ids. The old scheme of generating random Ids are still there. You can enable that by setting `GENERATE_RANDOM_SELECT2_ID` to `True`.
diff --git a/django_select2/__init__.py b/django_select2/__init__.py
index 44c7da7..6290a96 100644
--- a/django_select2/__init__.py
+++ b/django_select2/__init__.py
@@ -77,7 +77,7 @@ The view - `Select2View`, exposed here is meant to be used with 'Heavy' fields a
import logging
logger = logging.getLogger(__name__)
-__version__ = "4.0.0"
+__version__ = "4.1.0"
__RENDER_SELECT2_STATICS = False
__ENABLE_MULTI_PROCESS_SUPPORT = False
diff --git a/django_select2/static/css/select2.css b/django_select2/static/css/select2.css
index 1ff2abb..48c85b4 100755
--- a/django_select2/static/css/select2.css
+++ b/django_select2/static/css/select2.css
@@ -1,13 +1,14 @@
/*
-Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
+Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
*/
.select2-container {
+ margin: 0;
position: relative;
display: inline-block;
/* inline-block for ie7 */
zoom: 1;
*display: inline;
- vertical-align: top;
+ vertical-align: middle;
}
.select2-container,
@@ -81,7 +82,11 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
}
-.select2-container .select2-choice span {
+.select2-container.select2-allowclear .select2-choice .select2-chosen {
+ margin-right: 42px;
+}
+
+.select2-container .select2-choice > .select2-chosen {
margin-right: 26px;
display: block;
overflow: hidden;
@@ -94,11 +99,11 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
}
.select2-container .select2-choice abbr {
- display: block;
+ display: none;
width: 12px;
height: 12px;
position: absolute;
- right: 26px;
+ right: 24px;
top: 8px;
font-size: 1px;
@@ -109,22 +114,45 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
cursor: pointer;
outline: 0;
}
+
+.select2-container.select2-allowclear .select2-choice abbr {
+ display: inline-block;
+}
+
.select2-container .select2-choice abbr:hover {
background-position: right -11px;
cursor: pointer;
}
-.select2-drop-mask {
+.select2-drop-undermask {
+ border: 0;
+ margin: 0;
+ padding: 0;
position: absolute;
left: 0;
top: 0;
z-index: 9998;
+ background-color: transparent;
+ filter: alpha(opacity=0);
+}
+
+.select2-drop-mask {
+ border: 0;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 9998;
+ /* styles required for IE to work */
+ background-color: #fff;
opacity: 0;
+ filter: alpha(opacity=0);
}
.select2-drop {
width: 100%;
- margin-top:-1px;
+ margin-top: -1px;
position: absolute;
z-index: 9999;
top: 100%;
@@ -143,6 +171,15 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
}
+.select2-drop-auto-width {
+ border-top: 1px solid #aaa;
+ width: auto;
+}
+
+.select2-drop-auto-width .select2-search {
+ padding-top: 4px;
+}
+
.select2-drop.select2-drop-above {
margin-top: 1px;
border-top: 1px solid #aaa;
@@ -157,8 +194,17 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
}
-.select2-container .select2-choice div {
- display: block;
+.select2-drop-active {
+ border: 1px solid #5897fb;
+ border-top: none;
+}
+
+.select2-drop.select2-drop-above.select2-drop-active {
+ border-top: 1px solid #5897fb;
+}
+
+.select2-container .select2-choice .select2-arrow {
+ display: inline-block;
width: 18px;
height: 100%;
position: absolute;
@@ -184,7 +230,7 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
}
-.select2-container .select2-choice div b {
+.select2-container .select2-choice .select2-arrow b {
display: block;
width: 100%;
height: 100%;
@@ -205,12 +251,6 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
white-space: nowrap;
}
-.select2-search-hidden {
- display: block;
- position: absolute;
- left: -10000px;
-}
-
.select2-search input {
width: 100%;
height: auto !important;
@@ -288,12 +328,26 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
}
-.select2-dropdown-open .select2-choice div {
+.select2-dropdown-open.select2-drop-above .select2-choice,
+.select2-dropdown-open.select2-drop-above .select2-choices {
+ border: 1px solid #5897fb;
+ border-top-color: transparent;
+
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, white), color-stop(0.5, #eeeeee));
+ background-image: -webkit-linear-gradient(center top, white 0%, #eeeeee 50%);
+ background-image: -moz-linear-gradient(center top, white 0%, #eeeeee 50%);
+ background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
+ background-image: -ms-linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
+ background-image: linear-gradient(bottom, #ffffff 0%,#eeeeee 50%);
+}
+
+.select2-dropdown-open .select2-choice .select2-arrow {
background: transparent;
border-left: none;
filter: none;
}
-.select2-dropdown-open .select2-choice div b {
+.select2-dropdown-open .select2-choice .select2-arrow b {
background-position: -18px 1px;
}
@@ -310,6 +364,7 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
.select2-results ul.select2-result-sub {
margin: 0;
+ padding-left: 0;
}
.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
@@ -335,6 +390,8 @@ Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
margin: 0;
cursor: pointer;
+ min-height: 1em;
+
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
@@ -407,14 +464,14 @@ disabled look for disabled choices in the results dropdown
cursor: default;
}
-.select2-container.select2-container-disabled .select2-choice div {
+.select2-container.select2-container-disabled .select2-choice .select2-arrow {
background-color: #f4f4f4;
background-image: none;
border-left: 0;
}
.select2-container.select2-container-disabled .select2-choice abbr {
- display: none
+ display: none;
}
@@ -527,7 +584,7 @@ disabled look for disabled choices in the results dropdown
background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
}
-.select2-container-multi .select2-choices .select2-search-choice span {
+.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
cursor: default;
}
.select2-container-multi .select2-choices .select2-search-choice-focus {
@@ -573,8 +630,8 @@ disabled look for disabled choices in the results dropdown
background-color: #f4f4f4;
}
-.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {
- display: none;
+.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
+ background:none;
}
/* end multiselect */
@@ -584,15 +641,35 @@ disabled look for disabled choices in the results dropdown
text-decoration: underline;
}
-.select2-offscreen {
+.select2-offscreen, .select2-offscreen:focus {
+ clip: rect(0 0 0 0);
+ width: 1px;
+ height: 1px;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
position: absolute;
- left: -10000px;
+ outline: 0;
+ left: 0px;
}
+.select2-display-none {
+ display: none;
+}
+
+.select2-measure-scrollbar {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+}
/* Retina-ize icons */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
- .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b {
+ .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice .select2-arrow b {
background-image: url('select2x2.png') !important;
background-repeat: no-repeat !important;
background-size: 60px 40px !important;
diff --git a/django_select2/static/js/select2.js b/django_select2/static/js/select2.js
index 8be2c7e..a496071 100755
--- a/django_select2/static/js/select2.js
+++ b/django_select2/static/js/select2.js
@@ -1,7 +1,7 @@
-/*
+/*
Copyright 2012 Igor Vaynberg
-Version: 3.3.1 Timestamp: Wed Feb 20 09:57:22 PST 2013
+Version: 3.4.1 Timestamp: Thu Jun 27 18:02:10 PDT 2013
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
@@ -18,24 +18,24 @@ Apache License or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT W
CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
the specific language governing permissions and limitations under the Apache License and the GPL License.
*/
- (function ($) {
- if(typeof $.fn.each2 == "undefined"){
- $.fn.extend({
- /*
- * 4-10 times faster .each replacement
- * use it carefully, as it overrides jQuery context of element on each iteration
- */
- each2 : function (c) {
- var j = $([0]), i = -1, l = this.length;
- while (
- ++i < l
- && (j.context = j[0] = this[i])
- && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
- );
- return this;
- }
- });
- }
+(function ($) {
+ if(typeof $.fn.each2 == "undefined") {
+ $.fn.extend({
+ /*
+ * 4-10 times faster .each replacement
+ * use it carefully, as it overrides jQuery context of element on each iteration
+ */
+ each2 : function (c) {
+ var j = $([0]), i = -1, l = this.length;
+ while (
+ ++i < l
+ && (j.context = j[0] = this[i])
+ && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
+ );
+ return this;
+ }
+ });
+ }
})(jQuery);
(function ($, undefined) {
@@ -47,7 +47,7 @@ the specific language governing permissions and limitations under the Apache Lic
}
var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
- lastMousePosition, $document;
+ lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
KEY = {
TAB: 9,
@@ -95,7 +95,8 @@ the specific language governing permissions and limitations under the Apache Lic
k = k.which ? k.which : k;
return k >= 112 && k <= 123;
}
- };
+ },
+ MEASURE_SCROLLBAR_TEMPLATE = "
";
$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
@@ -118,8 +132,10 @@ the specific language governing permissions and limitations under the Apache Lic
if (a === b) return true;
if (a === undefined || b === undefined) return false;
if (a === null || b === null) return false;
- if (a.constructor === String) return a === b+'';
- if (b.constructor === String) return b === a+'';
+ // Check whether 'a' or 'b' is a string (primitive or object).
+ // The concatenation of an empty string (+'') converts its argument to a string's primitive.
+ if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
+ if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
return false;
}
@@ -143,12 +159,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,8 +173,9 @@ the specific language governing permissions and limitations under the Apache Lic
});
}
- $document.bind("mousemove", function (e) {
- lastMousePosition = {x: e.pageX, y: e.pageY};
+ $document.on("mousemove", function (e) {
+ lastMousePosition.x = e.pageX;
+ lastMousePosition.y = e.pageY;
});
/**
@@ -168,7 +185,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 +230,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);
});
}
@@ -229,23 +246,42 @@ the specific language governing permissions and limitations under the Apache Lic
$el.focus();
- /* after the focus is set move the caret to the end, necessary when we val()
- just before setting focus */
- if(el.setSelectionRange)
- {
- el.setSelectionRange(pos, pos);
- }
- else if (el.createTextRange) {
- range = el.createTextRange();
- range.collapse(true);
- range.moveEnd('character', pos);
- range.moveStart('character', pos);
- range.select();
- }
+ /* make sure el received focus so we do not error out when trying to manipulate the caret.
+ sometimes modals or others listeners may steal it after its set */
+ if ($el.is(":visible") && el === document.activeElement) {
+ /* after the focus is set move the caret to the end, necessary when we val()
+ just before setting focus */
+ if(el.setSelectionRange)
+ {
+ el.setSelectionRange(pos, pos);
+ }
+ else if (el.createTextRange) {
+ range = el.createTextRange();
+ range.collapse(false);
+ range.select();
+ }
+ }
}, 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();
@@ -257,22 +293,22 @@ the specific language governing permissions and limitations under the Apache Lic
function measureTextWidth(e) {
if (!sizer){
- var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
- sizer = $(document.createElement("div")).css({
- position: "absolute",
- left: "-10000px",
- top: "-10000px",
- display: "none",
- fontSize: style.fontSize,
- fontFamily: style.fontFamily,
- fontStyle: style.fontStyle,
- fontWeight: style.fontWeight,
- letterSpacing: style.letterSpacing,
- textTransform: style.textTransform,
- whiteSpace: "nowrap"
- });
+ var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
+ sizer = $(document.createElement("div")).css({
+ position: "absolute",
+ left: "-10000px",
+ top: "-10000px",
+ display: "none",
+ fontSize: style.fontSize,
+ fontFamily: style.fontFamily,
+ fontStyle: style.fontStyle,
+ fontWeight: style.fontWeight,
+ letterSpacing: style.letterSpacing,
+ textTransform: style.textTransform,
+ whiteSpace: "nowrap"
+ });
sizer.attr("class","select2-sizer");
- $("body").append(sizer);
+ $("body").append(sizer);
}
sizer.text(e.val());
return sizer.width();
@@ -282,7 +318,8 @@ the specific language governing permissions and limitations under the Apache Lic
var classes, replacements = [], adapted;
classes = dest.attr("class");
- if (typeof classes === "string") {
+ if (classes) {
+ classes = '' + classes; // for IE which returns object
$(classes.split(" ")).each2(function() {
if (this.indexOf("select2-") === 0) {
replacements.push(this);
@@ -290,11 +327,12 @@ the specific language governing permissions and limitations under the Apache Lic
});
}
classes = src.attr("class");
- if (typeof classes === "string") {
+ if (classes) {
+ classes = '' + classes; // for IE which returns object
$(classes.split(" ")).each2(function() {
if (this.indexOf("select2-") !== 0) {
adapted = adapter(this);
- if (typeof adapted === "string" && adapted.length > 0) {
+ if (adapted) {
replacements.push(this);
}
}
@@ -320,15 +358,31 @@ 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
*
* @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:
@@ -351,14 +405,20 @@ 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;
- if( null !== handler) { handler.abort(); }
+ if (handler) { handler.abort(); }
if (options.params) {
if ($.isFunction(options.params)) {
@@ -372,8 +432,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;
@@ -408,12 +466,12 @@ the specific language governing permissions and limitations under the Apache Lic
tmp,
text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
- if ($.isArray(data)) {
+ if ($.isArray(data)) {
tmp = data;
data = { results: tmp };
}
- if ($.isFunction(data) === false) {
+ if ($.isFunction(data) === false) {
tmp = data;
data = function() { return tmp; };
}
@@ -423,7 +481,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]; };
}
}
@@ -487,7 +545,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) {
@@ -542,7 +600,7 @@ the specific language governing permissions and limitations under the Apache Lic
input = input.substring(index + separator.length);
if (token.length > 0) {
- token = opts.createSearchChoice(token, selection);
+ token = opts.createSearchChoice.call(this, token, selection);
if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
dupe = false;
for (i = 0, l = selection.length; i < l; i++) {
@@ -586,7 +644,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);
@@ -596,10 +654,9 @@ the specific language governing permissions and limitations under the Apache Lic
// destroy if called on an existing component
if (opts.element.data("select2") !== undefined &&
opts.element.data("select2") !== null) {
- this.destroy();
+ opts.element.data("select2").destroy();
}
- this.enabled=true;
this.container = this.createContainer();
this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
@@ -614,14 +671,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);
@@ -632,8 +687,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;
@@ -641,10 +694,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) {
@@ -661,11 +718,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);
@@ -675,7 +732,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
@@ -686,31 +743,73 @@ 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
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")
- .unbind(".select2")
- .attr({"tabIndex": this.elementTabIndex})
- .show();
+ .off(".select2")
+ .prop("autofocus", this.autofocus || false);
+ if (this.elementTabIndex) {
+ element.attr({tabindex: this.elementTabIndex});
+ } else {
+ element.removeAttr("tabindex");
+ }
+ element.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") || equal(element.data("locked"), true)
+ };
+ } 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;
@@ -729,7 +828,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) {
@@ -790,23 +889,23 @@ the specific language governing permissions and limitations under the Apache Lic
if ("tags" in opts) {
throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
}
- opts.tags=opts.element.attr("data-select2-tags");
+ opts.tags=opts.element.data("select2Tags");
}
if (select) {
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;
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);
@@ -818,9 +917,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);
}
}
@@ -877,7 +976,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();
}
@@ -888,20 +987,13 @@ the specific language governing permissions and limitations under the Apache Lic
var enabled, readonly, self = this;
// sync enabled state
+ var disabled = el.prop("disabled");
+ if (disabled === undefined) disabled = false;
+ this.enable(!disabled);
- 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 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));
@@ -912,17 +1004,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
*/
@@ -946,24 +1052,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
@@ -973,28 +1101,42 @@ 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),
- viewPortRight = $(window).scrollLeft() + $(window).width(),
+ 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),
- enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
- aboveNow = this.dropdown.hasClass("select2-drop-above"),
+ dropWidth = $dropdown.outerWidth(false),
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
+ 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);
// fix positioning when body has an offset and is not position: static
-
if (this.body().css('position') !== 'static') {
bodyOffset = this.body().offset();
dropTop -= bodyOffset.top;
@@ -1002,7 +1144,6 @@ the specific language governing permissions and limitations under the Apache Lic
}
// always prefer the current above/below alignment, unless there is not enough room
-
if (aboveNow) {
above = true;
if (!enoughRoomAbove && enoughRoomBelow) above = false;
@@ -1018,11 +1159,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({
@@ -1031,7 +1172,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
@@ -1040,7 +1181,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();
},
@@ -1063,7 +1206,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;
},
@@ -1077,19 +1220,16 @@ the specific language governing permissions and limitations under the Apache Lic
scroll = "scroll." + cid,
resize = "resize."+cid,
orient = "orientationchange."+cid,
- mask;
-
- this.clearDropdownAlignmentPreference();
+ mask, maskCss;
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) {
@@ -1097,7 +1237,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 click", function (e) {
var dropdown = $("#select2-drop"), self;
if (dropdown.length > 0) {
self=dropdown.data("select2");
@@ -1105,6 +1245,8 @@ the specific language governing permissions and limitations under the Apache Lic
self.selectHighlighted({noFocus: true});
}
self.close();
+ e.preventDefault();
+ e.stopPropagation();
}
});
}
@@ -1119,29 +1261,32 @@ the specific language governing permissions and limitations under the Apache Lic
this.dropdown.attr("id", "select2-drop");
// show the elements
- mask.css({
- width: document.documentElement.scrollWidth,
- height: document.documentElement.scrollHeight});
- 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).bind(resize+" "+scroll+" "+orient, function (e) {
- $("#select2-drop-mask").css({
- width:document.documentElement.scrollWidth,
- height:document.documentElement.scrollHeight});
+ $(this).on(resize+" "+scroll+" "+orient, function (e) {
+ var maskCss=_makeMaskCss();
+ $("#select2-drop-mask").css(maskCss);
that.positionDropdown();
});
});
- this.focusSearch();
+ function _makeMaskCss() {
+ return {
+ width : Math.max(document.documentElement.scrollWidth, $(window).width()),
+ height : Math.max(document.documentElement.scrollHeight, $(window).height())
+ }
+ }
},
// abstract
@@ -1154,7 +1299,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();
@@ -1163,9 +1308,21 @@ 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.opts.element.trigger($.Event("close"));
+
+ this.clearSearch();
+ this.search.removeClass("select2-active");
+ 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
@@ -1196,7 +1353,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]);
@@ -1224,7 +1381,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)");
},
@@ -1265,7 +1421,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 });
}
},
@@ -1278,7 +1434,7 @@ the specific language governing permissions and limitations under the Apache Lic
highlightUnderEvent: function (event) {
var el = $(event.target).closest(".select2-result-selectable");
if (el.length > 0 && !el.is(".select2-highlighted")) {
- var choices = this.findHighlightableChoices();
+ var choices = this.findHighlightableChoices();
this.highlight(choices.index(el));
} else if (el.length == 0) {
// if we are over an unselectable item remove al highlights
@@ -1315,6 +1471,7 @@ the specific language governing permissions and limitations under the Apache Lic
self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
+ self.postprocessResults(data, false, false);
if (data.more===true) {
more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
@@ -1341,17 +1498,26 @@ the specific language governing permissions and limitations under the Apache Lic
*/
// abstract
updateResults: function (initial) {
- var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
+ var search = this.search,
+ results = this.results,
+ opts = this.opts,
+ data,
+ self = this,
+ input,
+ term = search.val(),
+ lastTerm=$.data(this.container, "select2-last-term");
+
+ // prevent duplicate queries against the same term
+ if (initial !== true && lastTerm && equal(term, lastTerm)) return;
+
+ $.data(this.container, "select2-last-term", term);
// if the search is currently hidden we do not alter the results
if (initial !== true && (this.showSearchInput === false || !this.opened())) {
return;
}
- search.addClass("select2-active");
-
function postRender() {
- results.scrollTop(0);
search.removeClass("select2-active");
self.positionDropdown();
}
@@ -1365,8 +1531,8 @@ the specific language governing permissions and limitations under the Apache Lic
if (maxSelSize >=1) {
data = this.data();
if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
- render("
" + opts.formatSelectionTooBig(maxSelSize) + "
");
- return;
+ render("
" + opts.formatSelectionTooBig(maxSelSize) + "
");
+ return;
}
}
@@ -1376,11 +1542,9 @@ the specific language governing permissions and limitations under the Apache Lic
} else {
render("");
}
+ if (initial && this.showSearch) this.showSearch(true);
return;
}
- else if (opts.formatSearching() && initial===true) {
- render("
" + opts.formatSearching() + "
");
- }
if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
@@ -1391,6 +1555,12 @@ the specific language governing permissions and limitations under the Apache Lic
return;
}
+ if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
+ render("
" + opts.formatSearching() + "
");
+ }
+
+ search.addClass("select2-active");
+
// give the tokenizer a chance to pre-process the input
input = this.tokenize();
if (input != undefined && input != null) {
@@ -1409,13 +1579,16 @@ the specific language governing permissions and limitations under the Apache Lic
var def; // default choice
// ignore a response if the select2 has been closed before it was received
- if (!this.opened()) return;
+ if (!this.opened()) {
+ this.search.removeClass("select2-active");
+ return;
+ }
// save context, if any
this.context = (data.context===undefined) ? null : data.context;
// create a default choice and prepend it to the list
if (this.opts.createSearchChoice && search.val() !== "") {
- def = this.opts.createSearchChoice.call(null, search.val(), data.results);
+ def = this.opts.createSearchChoice.call(self, search.val(), data.results);
if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
if ($(data.results).filter(
function () {
@@ -1442,6 +1615,8 @@ the specific language governing permissions and limitations under the Apache Lic
this.postprocessResults(data, initial);
postRender();
+
+ this.opts.element.trigger({ type: "select2-loaded", items: data });
})});
},
@@ -1478,15 +1653,34 @@ the specific language governing permissions and limitations under the Apache Lic
if (data) {
this.highlight(index);
this.onSelect(data, options);
+ } else if (options && 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;
+ }
+ }
},
/**
@@ -1511,7 +1705,7 @@ 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];
}
@@ -1546,49 +1740,64 @@ the specific language governing permissions and limitations under the Apache Lic
// single
- createContainer: function () {
+ createContainer: function () {
var container = $(document.createElement("div")).attr({
"class": "select2-container"
}).html([
"",
- " ",
- "
"].join(""));
return container;
},
// 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 () {
- this.parent.opening.apply(this, arguments);
- this.focusser.attr("disabled", "disabled");
+ var el, range, len;
- this.opts.element.trigger($.Event("open"));
+ 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
+
+ this.search.val(this.focusser.val());
+ }
+ this.search.focus();
+ // 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("");
+ this.updateResults(true);
+ this.opts.element.trigger($.Event("select2-open"));
},
// single
@@ -1596,7 +1805,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
@@ -1626,17 +1835,28 @@ 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);
+ if (this.opts.minimumResultsForSearch < 0) {
+ this.showSearch(false);
+ } else {
+ this.showSearch(true);
+ }
this.selection = selection = container.find(".select2-choice");
this.focusser = container.find(".select2-focusser");
- this.search.bind("keydown", this.bind(function (e) {
- if (!this.enabled) return;
+ // 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.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
@@ -1650,11 +1870,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);
@@ -1662,8 +1884,18 @@ the specific language governing permissions and limitations under the Apache Lic
}
}));
- this.focusser.bind("keydown", this.bind(function (e) {
- if (!this.enabled) return;
+ 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)) {
+ window.setTimeout(this.bind(function() {
+ this.search.focus();
+ }), 0);
+ }
+ }));
+
+ 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;
@@ -1676,6 +1908,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;
@@ -1692,70 +1927,82 @@ the specific language governing permissions and limitations under the Apache Lic
installKeyUpChangeEvent(this.focusser);
- this.focusser.bind("keyup-change input", this.bind(function(e) {
- if (this.opened()) return;
- this.open();
- if (this.showSearchInput !== false) {
- this.search.val(this.focusser.val());
+ this.focusser.on("keyup-change input", this.bind(function(e) {
+ if (this.opts.minimumResultsForSearch >= 0) {
+ e.stopPropagation();
+ if (this.opened()) return;
+ this.open();
}
- 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();
},
// single
- clear: function() {
+ clear: function(triggerChange) {
var data=this.selection.data("select2-data");
- this.opts.element.val("");
- this.selection.find("span").empty();
- this.selection.removeData("select2-data");
- this.setPlaceholder();
+ if (data) { // guard against queued quick consecutive clicks
+ var placeholderOption = this.getPlaceholderOption();
+ this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
+ this.selection.find(".select2-chosen").empty();
+ this.selection.removeData("select2-data");
+ this.setPlaceholder();
- this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
- this.triggerChange({removed:data});
+ if (triggerChange !== false){
+ this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
+ this.triggerChange({removed:data});
+ }
+ }
},
/**
@@ -1764,7 +2011,8 @@ 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();
} else {
@@ -1779,29 +2027,42 @@ 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);
+ 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
opts.initSelection = opts.initSelection || function (element, callback) {
var id = element.val();
- //search in data by id
+ //search in data by id, storing the actual matching item
+ var match = null;
opts.query({
matcher: function(term, text, el){
- return equal(id, opts.id(el));
+ var is_match = equal(id, opts.id(el));
+ if (is_match) {
+ match = el;
+ }
+ return is_match;
},
- callback: !$.isFunction(callback) ? $.noop : function(filtered) {
- callback(filtered.results.length ? filtered.results[0] : null);
+ callback: !$.isFunction(callback) ? $.noop : function() {
+ callback(match);
}
});
};
@@ -1812,9 +2073,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;
}
}
@@ -1826,21 +2087,21 @@ 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));
+ this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
this.selection.addClass("select2-default");
- this.selection.find("abbr").hide();
+ this.container.removeClass("select2-allowclear");
}
},
// single
- postprocessResults: function (data, initial) {
+ postprocessResults: function (data, initial, noHighlightUpdate) {
var selected = 0, self = this, showSearchInput = true;
// find the selected element in the result list
@@ -1853,68 +2114,88 @@ the specific language governing permissions and limitations under the Apache Lic
});
// and highlight it
-
- this.highlight(selected);
-
- // hide the search box if this is the first we got the results and there are a few of them
-
- if (initial === true) {
- var min=this.opts.minimumResultsForSearch;
- showSearchInput = min < 0 ? false : countResults(data.results) >= min;
- this.showSearch(showSearchInput);
+ if (noHighlightUpdate !== false) {
+ 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")[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
updateSelection: function (data) {
- var container=this.selection.find("span"), formatted;
+ var container=this.selection.find(".select2-chosen"), formatted, cssClass;
this.selection.data("select2-data", data);
container.empty();
- formatted=this.opts.formatSelection(data, container);
+ formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
if (formatted !== undefined) {
- container.append(this.opts.escapeMarkup(formatted));
+ container.append(formatted);
+ }
+ cssClass=this.opts.formatSelectionCssClass(data, container);
+ if (cssClass !== undefined) {
+ container.addClass(cssClass);
}
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();
@@ -1930,33 +2211,30 @@ 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()};
+ 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) {
- 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();
- if (triggerChange) {
- this.triggerChange();
- }
+ 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));
self.updateSelection(data);
self.setPlaceholder();
if (triggerChange) {
- self.triggerChange();
+ self.triggerChange({added: data, removed:oldData});
}
});
}
@@ -1969,7 +2247,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) {
@@ -1978,10 +2256,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});
+ }
}
}
}
@@ -1994,22 +2276,22 @@ the specific language governing permissions and limitations under the Apache Lic
var container = $(document.createElement("div")).attr({
"class": "select2-container select2-container-multi"
}).html([
- "
",
- //"
California
" ,
- "
" ,
- " " ,
- "
" ,
- "
" ,
- "
" ,
- "
" ,
- "
" ,
+ "
",
+ "
",
+ " ",
+ "
",
+ "
",
+ "
",
+ "
",
+ "
",
"
"].join(""));
- return container;
+ return container;
},
// 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
@@ -2020,7 +2302,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);
};
@@ -2028,15 +2310,34 @@ the specific language governing permissions and limitations under the Apache Lic
// install default initSelection when applied to hidden input and data is local
opts.initSelection = opts.initSelection || function (element, callback) {
var ids = splitVal(element.val(), opts.separator);
- //search in data by array of ids
+ //search in data by array of ids, storing matching items in a list
+ var matches = [];
opts.query({
matcher: function(term, text, el){
- return $.grep(ids, function(id) {
+ var is_match = $.grep(ids, function(id) {
return equal(id, opts.id(el));
}).length;
+ if (is_match) {
+ matches.push(el);
+ }
+ return is_match;
},
- callback: !$.isFunction(callback) ? $.noop : function(filtered) {
- callback(filtered.results);
+ callback: !$.isFunction(callback) ? $.noop : function() {
+ // 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);
}
});
};
@@ -2045,6 +2346,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 () {
@@ -2053,34 +2372,72 @@ 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);
- this.search.bind("input paste", this.bind(function() {
- if (!this.enabled) return;
+ var _this = this;
+ this.selection.on("mousedown", ".select2-search-choice", function (e) {
+ //killEvent(e);
+ _this.search[0].focus();
+ _this.selectChoice($(this));
+ })
+
+ // 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.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()) {
@@ -2091,10 +2448,13 @@ 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});
+ this.close();
+ return;
case KEY.ESC:
this.cancel(e);
killEvent(e);
@@ -2121,58 +2481,67 @@ the specific language governing permissions and limitations under the Apache Lic
// prevent the page from scrolling
killEvent(e);
}
+
+ if (e.which === KEY.ENTER) {
+ // prevent form from being submitted
+ killEvent(e);
+ }
+
}));
- 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("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
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
@@ -2199,12 +2568,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
- this.resizeSearch();
+ // 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(maxWidth > 0 ? maxWidth : this.container.css("width"));
} else {
this.search.val("").width(10);
}
@@ -2219,13 +2590,16 @@ the specific language governing permissions and limitations under the Apache Lic
// multi
opening: function () {
+ this.clearPlaceholder(); // should be done before super so placeholder is not used to search
+ this.resizeSearch();
+
this.parent.opening.apply(this, arguments);
- this.clearPlaceholder();
- this.resizeSearch();
this.focusSearch();
- this.opts.element.trigger($.Event("open"));
+ this.updateResults(true);
+ this.search.focus();
+ this.opts.element.trigger($.Event("select2-open"));
},
// multi
@@ -2238,7 +2612,6 @@ the specific language governing permissions and limitations under the Apache Lic
focus: function () {
this.close();
this.search.focus();
- this.opts.element.triggerHandler("focus");
},
// multi
@@ -2266,9 +2639,10 @@ the specific language governing permissions and limitations under the Apache Lic
self.postprocessResults();
},
+ // multi
tokenize: function() {
var input = this.search.val();
- input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
+ input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
if (input != null && input != undefined) {
this.search.val(input);
if (input.length > 0) {
@@ -2280,6 +2654,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 });
@@ -2293,7 +2670,7 @@ the specific language governing permissions and limitations under the Apache Lic
if (this.countSelectableResults()>0) {
this.search.width(10);
this.resizeSearch();
- if (this.val().length >= this.getMaximumSelectionSize()) {
+ if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
// if we reached max selection size repaint the results so choices
// are replaced with the max selection reached message
this.updateResults(true);
@@ -2334,18 +2711,23 @@ 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"));
+ formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
if (formatted != undefined) {
- choice.find("div").replaceWith("
"+this.opts.escapeMarkup(formatted)+"
");
+ choice.find("div").replaceWith("
"+formatted+"
");
+ }
+ cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
+ if (cssClass != undefined) {
+ choice.addClass(cssClass);
}
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));
@@ -2354,8 +2736,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");
}));
@@ -2402,7 +2784,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"),
@@ -2425,16 +2807,30 @@ 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){
+ if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
+ if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
+ this.results.append("