From fe1fb4fd765ef7dbdca1b27843a44b513d5bbe2f Mon Sep 17 00:00:00 2001 From: "AppleGrew (applegrew)" Date: Sat, 6 Jul 2013 10:04:18 +0530 Subject: [PATCH] Updating Select2 JS to 3.4.1 --- README | 8 + README.md | 8 + django_select2/__init__.py | 2 +- django_select2/static/css/select2.css | 129 ++- django_select2/static/js/select2.js | 1240 +++++++++++++++-------- django_select2/static/js/select2.min.js | 23 +- testapp/test.db | Bin 2412544 -> 2412544 bytes 7 files changed, 975 insertions(+), 435 deletions(-) mode change 100644 => 100755 django_select2/static/js/select2.min.js 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([ "", - " ", - "
    " , + "  ", + " ", "
    ", "", - "