PageSourceSearch

https://ponyexpress.kz/local/templates/ponyexpress_main/js/select2.js

js ponyexpress.kz collected 2026-09-26 09:55:23 UTC 147,254 bytes, 3,508 lines download raw bytes

1/*
2Copyright 2012 Igor Vaynberg
3
4Version: 3.5.1 Timestamp: Tue Jul 22 18:58:56 EDT 2014
5
6This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
7General Public License version 2 (the "GPL License"). You may choose either license to govern your
8use of this software only upon the condition that you accept all of the terms of either the Apache
9License or the GPL License.
10
11You may obtain a copy of the Apache License and the GPL License at:
12
13    http://www.apache.org/licenses/LICENSE-2.0
14    http://www.gnu.org/licenses/gpl-2.0.html
15
16Unless required by applicable law or agreed to in writing, software distributed under the
17Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
18CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
19the specific language governing permissions and limitations under the Apache License and the GPL License.
20*/
21(function ($) {
22    if(typeof $.fn.each2 == "undefined") {
23        $.extend($.fn, {
24            /*
25            * 4-10 times faster .each replacement
26            * use it carefully, as it overrides jQuery context of element on each iteration
27            */
28            each2 : function (c) {
29                var j = $([0]), i = -1, l = this.length;
30                while (
31                    ++i < l
32                    && (j.context = j[0] = this[i])
33                    && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
34                );
35                return this;
36            }
37        });
38    }
39})(jQuery);
40
41(function ($, undefined) {
42    "use strict";
43    /*global document, window, jQuery, console */
44
45    if (window.Select2 !== undefined) {
46        return;
47    }
48
49    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
50        lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
51
52    KEY = {
53        TAB: 9,
54        ENTER: 13,
55        ESC: 27,
56        SPACE: 32,
57        LEFT: 37,
58        UP: 38,
59        RIGHT: 39,
60        DOWN: 40,
61        SHIFT: 16,
62        CTRL: 17,
63        ALT: 18,
64        PAGE_UP: 33,
65        PAGE_DOWN: 34,
66        HOME: 36,
67        END: 35,
68        BACKSPACE: 8,
69        DELETE: 46,
70        isArrow: function (k) {
71            k = k.which ? k.which : k;
72            switch (k) {
73            case KEY.LEFT:
74            case KEY.RIGHT:
75            case KEY.UP:
76            case KEY.DOWN:
77                return true;
78            }
79            return false;
80        },
81        isControl: function (e) {
82            var k = e.which;
83            switch (k) {
84            case KEY.SHIFT:
85            case KEY.CTRL:
86            case KEY.ALT:
87                return true;
88            }
89
90            if (e.metaKey) return true;
91
92            return false;
93        },
94        isFunctionKey: function (k) {
95            k = k.which ? k.which : k;
96            return k >= 112 && k <= 123;
97        }
98    },
99    MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>",
100
101    DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18"
101:"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC
1015":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"};
102
103    $document = $(document);
104
105    nextUid=(function() { var counter=1; return function() { return counter++; }; }());
106
107
108    function reinsertElement(element) {
109        var placeholder = $(document.createTextNode(''));
110
111        element.before(placeholder);
112        placeholder.before(element);
113        placeholder.remove();
114    }
115
116    function stripDiacritics(str) {
117        // Used 'uni range + named function' from http://jsperf.com/diacritics/18
118        function match(a) {
119            return DIACRITICS[a] || a;
120        }
121
122        return str.replace(/[^\u0000-\u007E]/g, match);
123    }
124
125    function indexOf(value, array) {
126        var i = 0, l = array.length;
127        for (; i < l; i = i + 1) {
128            if (equal(value, array[i])) return i;
129        }
130        return -1;
131    }
132
133    function measureScrollbar () {
134        var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
135        $template.appendTo('body');
136
137        var dim = {
138            width: $template.width() - $template[0].clientWidth,
139            height: $template.height() - $template[0].clientHeight
140        };
141        $template.remove();
142
143        return dim;
144    }
145
146    /**
147     * Compares equality of a and b
148     * @param a
149     * @param b
150     */
151    function equal(a, b) {
152        if (a === b) return true;
153        if (a === undefined || b === undefined) return false;
154        if (a === null || b === null) return false;
155        // Check whether 'a' or 'b' is a string (primitive or object).
156        // The concatenation of an empty string (+'') converts its argument to a string's primitive.
157        if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
158        if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
159        return false;
160    }
161
162    /**
163     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
164     * strings
165     * @param string
166     * @param separator
167     */
168    function splitVal(string, separator) {
169        var val, i, l;
170        if (string === null || string.length < 1) return [];
171        val = string.split(separator);
172        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
173        return val;
174    }
175
176    function getSideBorderPadding(element) {
177        return element.outerWidth(false) - element.width();
178    }
179
180    function installKeyUpChangeEvent(element) {
181        var key="keyup-change-value";
182        element.on("keydown", function () {
183            if ($.data(element, key) === undefined) {
184                $.data(element, key, element.val());
185            }
186        });
187        element.on("keyup", function () {
188            var val= $.data(element, key);
189            if (val !== undefined && element.val() !== val) {
190                $.removeData(element, key);
191                element.trigger("keyup-change");
192            }
193        });
194    }
195
196
197    /**
198     * filters mouse events so an event is fired only if the mouse moved.
199     *
200     * filters out mouse events that occur when mouse is stationary but
201     * the elements under the pointer are scrolled.
202     */
203    function installFilteredMouseMove(element) {
204        element.on("mousemove", function (e) {
205            var lastpos = lastMousePosition;
206            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
207                $(e.target).trigger("mousemove-filtered", e);
208            }
209        });
210    }
211
212    /**
213     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
214     * within the last quietMillis milliseconds.
215     *
216     * @param quietMillis number of milliseconds to wait before invoking fn
217     * @param fn function to be debounced
218     * @param ctx object to be used as this reference within fn
219     * @return debounced version of fn
220     */
221    function debounce(quietMillis, fn, ctx) {
222        ctx = ctx || undefined;
223        var timeout;
224        return function () {
225            var args = arguments;
226            window.clearTimeout(timeout);
227            timeout = window.setTimeout(function() {
228                fn.apply(ctx, args);
229            }, quietMillis);
230        };
231    }
232
233    function installDebouncedScroll(threshold, element) {
234        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
235        element.on("scroll", function (e) {
236            if (indexOf(e.target, element.get()) >= 0) notify(e);
237        });
238    }
239
240    function focus($el) {
241        if ($el[0] === document.activeElement) return;
242
243        /* set the focus in a 0 timeout - that way the focus is set after the processing
244            of the current event has finished - which seems like the only reliable way
245            to set focus */
246        window.setTimeout(function() {
247            var el=$el[0], pos=$el.val().length, range;
248
249            $el.focus();
250
251            /* make sure el received focus so we do not error out when trying to manipulate the caret.
252                sometimes modals or others listeners may steal it after its set */
253            var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
254            if (isVisible && el === document.activeElement) {
255
256                /* after the focus is set move the caret to the end, necessary when we val()
257                    just before setting focus */
258                if(el.setSelectionRange)
259                {
260                    el.setSelectionRange(pos, pos);
261                }
262                else if (el.createTextRange) {
263                    range = el.createTextRange();
264                    range.collapse(false);
265                    range.select();
266                }
267            }
268        }, 0);
269    }
270
271    function getCursorInfo(el) {
272        el = $(el)[0];
273        var offset = 0;
274        var length = 0;
275        if ('selectionStart' in el) {
276            offset = el.selectionStart;
277            length = el.selectionEnd - offset;
278        } else if ('selection' in document) {
279            el.focus();
280            var sel = document.selection.createRange();
281            length = document.selection.createRange().text.length;
282            sel.moveStart('character', -el.value.length);
283            offset = sel.text.length - length;
284        }
285        return { offset: offset, length: length };
286    }
287
288    function killEvent(event) {
289        event.preventDefault();
290        event.stopPropagation();
291    }
292    function killEventImmediately(event) {
293        event.preventDefault();
294        event.stopImmediatePropagation();
295    }
296
297    function measureTextWidth(e) {
298        if (!sizer){
299            var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
300            sizer = $(document.createElement("div")).css({
301                position: "absolute",
302                left: "-10000px",
303                top: "-10000px",
304                display: "none",
305                fontSize: style.fontSize,
306                fontFamily: style.fontFamily,
307                fontStyle: style.fontStyle,
308                fontWeight: style.fontWeight,
309                letterSpacing: style.letterSpacing,
310                textTransform: style.textTransform,
311                whiteSpace: "nowrap"
312            });
313            sizer.attr("class","select2-sizer");
314            $("body").append(sizer);
315        }
316        sizer.text(e.val());
317        return sizer.width();
318    }
319
320    function syncCssClasses(dest, src, adapter) {
321        var classes, replacements = [], adapted;
322
323        classes = $.trim(dest.attr("class"));
324
325        if (classes) {
326            classes = '' + classes; // for IE which returns object
327
328            $(classes.split(/\s+/)).each2(function() {
329                if (this.indexOf("select2-") === 0) {
330                    replacements.push(this);
331                }
332            });
333        }
334
335        classes = $.trim(src.attr("class"));
336
337        if (classes) {
338            classes = '' + classes; // for IE which returns object
339
340            $(classes.split(/\s+/)).each2(function() {
341                if (this.indexOf("select2-") !== 0) {
342                    adapted = adapter(this);
343
344                    if (adapted) {
345                        replacements.push(adapted);
346                    }
347                }
348            });
349        }
350
351        dest.attr("class", replacements.join(" "));
352    }
353
354
355    function markMatch(text, term, markup, escapeMarkup) {
356        var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
357            tl=term.length;
358
359        if (match<0) {
360            markup.push(escapeMarkup(text));
361            return;
362        }
363
364        markup.push(escapeMarkup(text.substring(0, match)));
365        markup.push("<span class='select2-match'>");
366        markup.push(escapeMarkup(text.substring(match, match + tl)));
367        markup.push("</span>");
368        markup.push(escapeMarkup(text.substring(match + tl, text.length)));
369    }
370
371    function defaultEscapeMarkup(markup) {
372        var replace_map = {
373            '\\': '&#92;',
374            '&': '&amp;',
375            '<': '&lt;',
376            '>': '&gt;',
377            '"': '&quot;',
378            "'": '&#39;',
379            "/": '&#47;'
380        };
381
382        return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
383            return replace_map[match];
384        });
385    }
386
387    /**
388     * Produces an ajax-based query function
389     *
390     * @param options object containing configuration parameters
391     * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
392     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
393     * @param options.url url for the data
394     * @param options.data a function(searchTerm, pageNumber, context) that should return an object c
394ontaining query string parameters for the above url.
395     * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
396     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
397     * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
398     *      The expected format is an object containing the following keys:
399     *      results array of objects that will be used as choices
400     *      more (optional) boolean indicating whether there are more results available
401     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
402     */
403    function ajax(options) {
404        var timeout, // current scheduled but not yet executed request
405            handler = null,
406            quietMillis = options.quietMillis || 100,
407            ajaxUrl = options.url,
408            self = this;
409
410        return function (query) {
411            window.clearTimeout(timeout);
412            timeout = window.setTimeout(function () {
413                var data = options.data, // ajax data function
414                    url = ajaxUrl, // ajax url string or function
415                    transport = options.transport || $.fn.select2.ajaxDefaults.transport,
416                    // deprecated - to be removed in 4.0  - use params instead
417                    deprecated = {
418                        type: options.type || 'GET', // set type of request (GET or POST)
419                        cache: options.cache || false,
420                        jsonpCallback: options.jsonpCallback||undefined,
421                        dataType: options.dataType||"json"
422                    },
423                    params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
424
425                data = data ? data.call(self, query.term, query.page, query.context) : null;
426                url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
427
428                if (handler && typeof handler.abort === "function") { handler.abort(); }
429
430                if (options.params) {
431                    if ($.isFunction(options.params)) {
432                        $.extend(params, options.params.call(self));
433                    } else {
434                        $.extend(params, options.params);
435                    }
436                }
437
438                $.extend(params, {
439                    url: url,
440                    dataType: options.dataType,
441                    data: data,
442                    success: function (data) {
443                        // TODO - replace query.page with query so users have access to term, page, etc.
444                        // added query as third paramter to keep backwards compatibility
445                        var results = options.results(data, query.page, query);
446                        query.callback(results);
447                    },
448                    error: function(jqXHR, textStatus, errorThrown){
449                        var results = {
450                            hasError: true,
451                            jqXHR: jqXHR,
452                            textStatus: textStatus,
453                            errorThrown: errorThrown,
454                        };
455
456                        query.callback(results);
457                    }
458                });
459                handler = transport.call(self, params);
460            }, quietMillis);
461        };
462    }
463
464    /**
465     * Produces a query function that works with a local array
466     *
467     * @param options object containing configuration parameters. The options parameter can either be an array or an
468     * object.
469     *
470     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
471     *
472     * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
473     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
474     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
475     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
476     * the text.
477     */
478    function local(options) {
479        var data = options, // data elements
480            dataText,
481            tmp,
482            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
483
484         if ($.isArray(data)) {
485            tmp = data;
486            data = { results: tmp };
487        }
488
489         if ($.isFunction(data) === false) {
490            tmp = data;
491            data = function() { return tmp; };
492        }
493
494        var dataItem = data();
495        if (dataItem.text) {
496            text = dataItem.text;
497            // if text is not a function we assume it to be a key name
498            if (!$.isFunction(text)) {
499                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
500                text = function (item) { return item[dataText]; };
501            }
502        }
503
504        return function (query) {
505            var t = query.term, filtered = { results: [] }, process;
506            if (t === "") {
507                query.callback(data());
508                return;
509            }
510
511            process = function(datum, collection) {
512                var group, attr;
513                datum = datum[0];
514                if (datum.children) {
515                    group = {};
516                    for (attr in datum) {
517                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
518                    }
519                    group.children=[];
520                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
521                    if (group.children.length || query.matcher(t, text(group), datum)) {
522                        collection.push(group);
523                    }
524                } else {
525                    if (query.matcher(t, text(datum), datum)) {
526                        collection.push(datum);
527                    }
528                }
529            };
530
531            $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
532            query.callback(filtered);
533        };
534    }
535
536    // TODO javadoc
537    function tags(data) {
538        var isFunc = $.isFunction(data);
539        return function (query) {
540            var t = query.term, filtered = {results: []};
541            var result = isFunc ? data(query) : data;
542            if ($.isArray(result)) {
543                $(result).each(function () {
544                    var isObject = this.text !== undefined,
545                        text = isObject ? this.text : this;
546                    if (t === "" || query.matcher(t, text)) {
547                        filtered.results.push(isObject ? this : {id: this, text: this});
548                    }
549                });
550                query.callback(filtered);
551            }
552        };
553    }
554
555    /**
556     * Checks if the formatter function should be used.
557     *
558     * Throws an error if it is not a function. Returns true if it should be used,
559     * false if no formatting should be performed.
560     *
561     * @param formatter
562     */
563    function checkFormatter(formatter, formatterName) {
564        if ($.isFunction(formatter)) return true;
565        if (!formatter) return false;
566        if (typeof(formatter) === 'string') return true;
567        throw new Error(formatterName +" must be a string, function, or falsy value");
568    }
569
570  /**
571   * Returns a given value
572   * If given a function, returns its output
573   *
574   * @param val string|function
575   * @param context value of "this" to be passed to function
576   * @returns {*}
577   */
578    function evaluate(val, context) {
579        if ($.isFunction(val)) {
580            var args = Array.prototype.slice.call(arguments, 2);
581            return val.apply(context, args);
582        }
583        return val;
584    }
585
586    function countResults(results) {
587        var count = 0;
588        $.each(results, function(i, item) {
589            if (item.children) {
590                count += countResults(item.children);
591            } else {
592                count++;
593            }
594        });
595        return count;
596    }
597
598    /**
599     * Default tokenizer. This function uses breaks the input on substring match of any string from the
600     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
601     * two options have to be defined in order for the tokenizer to work.
602     *
603     * @param input text user has typed so far or pasted into the search field
604     * @param selection currently selected choices
605     * @param selectCallback function(choice) callback tho add the choice to selection
606     * @param opts select2's opts
607     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
608     */
609    function defaultTokenizer(input, selection, selectCallback, opts) {
610        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
611            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
612            token, // token
613            index, // position at which the separator was found
614            i, l, // looping variables
615            separator; // the matched separator
616
617        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
618
619        while (true) {
620            index = -1;
621
622            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
623                separator = opts.tokenSeparators[i];
624                index = input.indexOf(separator);
625                if (index >= 0) break;
626            }
627
628            if (index < 0) break; // did not find any token separator in the input string, bail
629
630            token = input.substring(0, index);
631            input = input.substring(index + separator.length);
632
633            if (token.length > 0) {
634                token = opts.createSearchChoice.call(this, token, selection);
635                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
636                    dupe = false;
637                    for (i = 0, l = selection.length; i < l; i++) {
638                        if (equal(opts.id(token), opts.id(selection[i]))) {
639                            dupe = true; break;
640                        }
641                    }
642
643                    if (!dupe) selectCallback(token);
644                }
645            }
646        }
647
648        if (original!==input) return input;
649    }
650
651    function cleanupJQueryElements() {
652        var self = this;
653
654        $.each(arguments, function (i, element) {
655            self[element].remove();
656            self[element] = null;
657        });
658    }
659
660    /**
661     * Creates a new class
662     *
663     * @param superClass
664     * @param methods
665     */
666    function clazz(SuperClass, methods) {
667        var constructor = function () {};
668        constructor.prototype = new SuperClass;
669        constructor.prototype.constructor = constructor;
670        constructor.prototype.parent = SuperClass.prototype;
671        constructor.prototype = $.extend(constructor.prototype, methods);
672        return constructor;
673    }
674
675    AbstractSelect2 = clazz(Object, {
676
677        // abstract
678        bind: function (func) {
679            var self = this;
680            return function () {
681                func.apply(self, arguments);
682            };
683        },
684
685        // abstract
686        init: function (opts) {
687            var results, search, resultsSelector = ".select2-results";
688
689            // prepare options
690            this.opts = opts = this.prepareOpts(opts);
691
692            this.id=opts.id;
693
694            // destroy if called on an existing component
695            if (opts.element.data("select2") !== undefined &&
696                opts.element.data("select2") !== null) {
697                opts.element.data("select2").destroy();
698            }
699
700            this.container = this.createContainer();
701
702            this.liveRegion = $("<span>", {
703                    role: "status",
704                    "aria-live": "polite"
705                })
706                .addClass("select2-hidden-accessible")
707                .appendTo(document.body);
708
709            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
710            this.containerEventName= this.containerId
711                .replace(/([.])/g, '_')
712                .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
713            this.container.attr("id", this.containerId);
714
715            this.container.attr("title", opts.element.attr("title"));
716
717            this.body = $("body");
718
719            syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
720
721            this.container.attr("style", opts.element.attr("style"));
722            this.container.css(evaluate(opts.containerCss, this.opts.element));
723            this.container.addClass(evaluate(opts.containerCssClass, this.opts.element));
724
725            this.elementTabIndex = this.opts.element.attr("tabindex");
726
727            // swap container for the element
728            this.opts.element
729                .data("select2", this)
730                .attr("tabindex", "-1")
731                .before(this.container)
732                .on("click.select2", killEvent); // do not leak click events
733
734            this.container.data("select2", this);
735
736            this.dropdown = this.container.find(".select2-drop");
737
738            syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
739
740            this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element));
741            this.dropdown.data("select2", this);
742            this.dropdown.on("click", killEvent);
743
744            this.results = results = this.container.find(resultsSelector);
745            this.search = search = this.container.find("input.select2-input");
746
747            this.queryCount = 0;
748            this.resultsPage = 0;
749            this.context = null;
750
751            // initialize the container
752            this.initContainer();
753
754            this.container.on("click", killEvent);
755
756            installFilteredMouseMove(this.results);
757
758            this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
759            this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
760                this._touchEvent = true;
761                this.highlightUnderEvent(event);
762            }));
763            this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
764            this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
765
766            // Waiting for a click event on touch devices to select option and hide dropdown
767            // otherwise click will be triggered on an underlying element
768            this.dropdown.on('click', this.bind(function (event) {
769                if (this._touchEvent) {
770                    this._touchEvent = false;
771                    this.selectHighlighted();
772                }
773            }));
774
775            installDebouncedScroll(80, this.results);
776            this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
777
778            // do not propagate change event from the search field out of the component
779            $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
780            $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
781
782            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
783            if ($.fn.mousewheel) {
784                results.mousewheel(function (e, delta, deltaX, deltaY) {
785                    var top = results.scrollTop();
786                    if (deltaY > 0 && top - deltaY <= 0) {
787                        results.scrollTop(0);
788                        killEvent(e);
789                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
790                        results.scrollTop(results.get(0).scrollHeight - results.height());
791                        killEvent(e);
792                    }
793                });
794            }
795
796            installKeyUpChangeEvent(search);
797            search.on("keyup-change input paste", this.bind(this.updateResults));
798            search.on("focus", function () { search.addClass("select2-focused"); });
799            search.on("blur", function () { search.removeClass("select2-focused");});
800
801            this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
802                if ($(e.target).closest(".select2-result-selectable").length > 0) {
803                    this.highlightUnderEvent(e);
804                    this.selectHighlighted(e);
805                }
806            }));
807
808            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
809            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
810            // dom it will trigger the popup close, which is not what we want
811            // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
812            this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); });
813
814            this.nextSearchTerm = undefined;
815
816            if ($.isFunction(this.opts.initSelection)) {
817                // initialize selection based on the current value of the source element
818                this.initSelection();
819
820                // if the user has provided a function that can set selection based on the value of the source element
821                // we monitor the change event on the element and trigger it, allowing for two way synchronization
822                this.monitorSource();
823            }
824
825            if (opts.maximumInputLength !== null) {
826                this.search.attr("maxlength", opts.maximumInputLength);
827            }
828
829            var disabled = opts.element.prop("disabled");
830            if (disabled === undefined) disabled = false;
831            this.enable(!disabled);
832
833            var readonly = opts.element.prop("readonly");
834            if (readonly === undefined) readonly = false;
835            this.readonly(readonly);
836
837            // Calculate size of scrollbar
838            scrollBarDimensions = scrollBarDimensions || measureScrollbar();
839
840            this.autofocus = opts.element.prop("autofocus");
841            opts.element.prop("autofocus", false);
842            if (this.autofocus) this.focus();
843
844            this.search.attr("placeholder", opts.searchInputPlaceholder);
845        },
846
847        // abstract
848        destroy: function () {
849            var element=this.opts.element, select2 = element.data("select2"), self = this;
850
851            this.close();
852
853            if (element.length && element[0].detachEvent) {
854                element.each(function () {
855                    this.detachEvent("onpropertychange", self._sync);
856                });
857            }
858            if (this.propertyObserver) {
859                this.propertyObserver.disconnect();
860                this.propertyObserver = null;
861            }
862            this._sync = null;
863
864            if (select2 !== undefined) {
865                select2.container.remove();
866                select2.liveRegion.remove();
867                select2.dropdown.remove();
868                element
869                    .removeClass("select2-offscreen")
870                    .removeData("select2")
871                    .off(".select2")
872                    .prop("autofocus", this.autofocus || false);
873                if (this.elementTabIndex) {
874                    element.attr({tabindex: this.elementTabIndex});
875                } else {
876                    element.removeAttr("tabindex");
877                }
878                element.show();
879            }
880
881            cleanupJQueryElements.call(this,
882                "container",
883                "liveRegion",
884                "dropdown",
885                "results",
886                "search"
887            );
888        },
889
890        // abstract
891        optionToData: function(element) {
892            if (element.is("option")) {
893                return {
894                    id:element.prop("value"),
895                    text:element.text(),
896                    element: element.get(),
897                    css: element.attr("class"),
898                    disabled: element.prop("disabled"),
899                    locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
900                };
901            } else if (element.is("optgroup")) {
902                return {
903                    text:element.attr("label"),
904                    children:[],
905                    element: element.get(),
906                    css: element.attr("class")
907                };
908            }
909        },
910
911        // abstract
912        prepareOpts: function (opts) {
913            var element, select, idKey, ajaxUrl, self = this;
914
915            element = opts.element;
916
917            if (element.get(0).tagName.toLowerCase() === "select") {
918                this.select = select = opts.element;
919            }
920
921            if (select) {
922                // these options are not allowed when attached to a select because they are picked up off the element itself
923                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
924                    if (this in opts) {
925                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
926                    }
927                });
928            }
929
930            opts = $.extend({}, {
931                populateResults: function(container, results, query
931) {
932                    var populate, id=this.opts.id, liveRegion=this.liveRegion;
933
934                    populate=function(results, container, depth) {
935
936                        var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
937
938                        results = opts.sortResults(results, container, query);
939
940                        // collect the created nodes for bulk append
941                        var nodes = [];
942                        for (i = 0, l = results.length; i < l; i = i + 1) {
943
944                            result=results[i];
945
946                            disabled = (result.disabled === true);
947                            selectable = (!disabled) && (id(result) !== undefined);
948
949                            compound=result.children && result.children.length > 0;
950
951                            node=$("<li></li>");
952                            node.addClass("select2-results-dept-"+depth);
953                            node.addClass("select2-result");
954                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
955                            if (disabled) { node.addClass("select2-disabled"); }
956                            if (compound) { node.addClass("select2-result-with-children"); }
957                            node.addClass(self.opts.formatResultCssClass(result));
958                            node.attr("role", "presentation");
959
960                            label=$(document.createElement("div"));
961                            label.addClass("select2-result-label");
962                            label.attr("id", "select2-result-label-" + nextUid());
963                            label.attr("role", "option");
964
965                            formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
966                            if (formatted!==undefined) {
967                                label.html(formatted);
968                                node.append(label);
969                            }
970
971
972                            if (compound) {
973
974                                innerContainer=$("<ul></ul>");
975                                innerContainer.addClass("select2-result-sub");
976                                populate(result.children, innerContainer, depth+1);
977                                node.append(innerContainer);
978                            }
979
980                            node.data("select2-data", result);
981                            nodes.push(node[0]);
982                        }
983
984                        // bulk append the created nodes
985                        container.append(nodes);
986                        liveRegion.text(opts.formatMatches(results.length));
987                    };
988
989                    populate(results, container, 0);
990                }
991            }, $.fn.select2.defaults, opts);
992
993            if (typeof(opts.id) !== "function") {
994                idKey = opts.id;
995                opts.id = function (e) { return e[idKey]; };
996            }
997
998            if ($.isArray(opts.element.data("select2Tags"))) {
999                if ("tags" in opts) {
1000                    throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
1001                }
1002                opts.tags=opts.element.data("select2Tags");
1003            }
1004
1005            if (select) {
1006                opts.query = this.bind(function (query) {
1007                    var data = { results: [], more: false },
1008                        term = query.term,
1009                        children, placeholderOption, process;
1010
1011                    process=function(element, collection) {
1012                        var group;
1013                        if (element.is("option")) {
1014                            if (query.matcher(term, element.text(), element)) {
1015                                collection.push(self.optionToData(element));
1016                            }
1017                        } else if (element.is("optgroup")) {
1018                            group=self.optionToData(element);
1019                            element.children().each2(function(i, elm) { process(elm, group.children); });
1020                            if (group.children.length>0) {
1021                                collection.push(group);
1022                            }
1023                        }
1024                    };
1025
1026                    children=element.children();
1027
1028                    // ignore the placeholder option if there is one
1029                    if (this.getPlaceholder() !== undefined && children.length > 0) {
1030                        placeholderOption = this.getPlaceholderOption();
1031                        if (placeholderOption) {
1032                            children=children.not(placeholderOption);
1033                        }
1034                    }
1035
1036                    children.each2(function(i, elm) { process(elm, data.results); });
1037
1038                    query.callback(data);
1039                });
1040                // this is needed because inside val() we construct choices from options and their id is hardcoded
1041                opts.id=function(e) { return e.id; };
1042            } else {
1043                if (!("query" in opts)) {
1044
1045                    if ("ajax" in opts) {
1046                        ajaxUrl = opts.element.data("ajax-url");
1047                        if (ajaxUrl && ajaxUrl.length > 0) {
1048                            opts.ajax.url = ajaxUrl;
1049                        }
1050                        opts.query = ajax.call(opts.element, opts.ajax);
1051                    } else if ("data" in opts) {
1052                        opts.query = local(opts.data);
1053                    } else if ("tags" in opts) {
1054                        opts.query = tags(opts.tags);
1055                        if (opts.createSearchChoice === undefined) {
1056                            opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
1057                        }
1058                        if (opts.initSelection === undefined) {
1059                            opts.initSelection = function (element, callback) {
1060                                var data = [];
1061                                $(splitVal(element.val(), opts.separator)).each(function () {
1062                                    var obj = { id: this, text: this },
1063                                        tags = opts.tags;
1064                                    if ($.isFunction(tags)) tags=tags();
1065                                    $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
1066                                    data.push(obj);
1067                                });
1068
1069                                callback(data);
1070                            };
1071                        }
1072                    }
1073                }
1074            }
1075            if (typeof(opts.query) !== "function") {
1076                throw "query function not defined for Select2 " + opts.element.attr("id");
1077            }
1078
1079            if (opts.createSearchChoicePosition === 'top') {
1080                opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
1081            }
1082            else if (opts.createSearchChoicePosition === 'bottom') {
1083                opts.createSearchChoicePosition = function(list, item) { list.push(item); };
1084            }
1085            else if (typeof(opts.createSearchChoicePosition) !== "function")  {
1086                throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
1087            }
1088
1089            return opts;
1090        },
1091
1092        /**
1093         * Monitor the original element for changes and update select2 accordingly
1094         */
1095        // abstract
1096        monitorSource: function () {
1097            var el = this.opts.element, observer, self = this;
1098
1099            el.on("change.select2", this.bind(function (e) {
1100                if (this.opts.element.data("select2-change-triggered") !== true) {
1101                    this.initSelection();
1102                }
1103            }));
1104
1105            this._sync = this.bind(function () {
1106
1107                // sync enabled state
1108                var disabled = el.prop("disabled");
1109                if (disabled === undefined) disabled = false;
1110                this.enable(!disabled);
1111
1112                var readonly = el.prop("readonly");
1113                if (readonly === undefined) readonly = false;
1114                this.readonly(readonly);
1115
1116                syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
1117                this.container.addClass(evaluate(this.opts.containerCssClass, this.opts.element));
1118
1119                syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
1120                this.dropdown.addClass(evaluate(this.opts.dropdownCssClass, this.opts.element));
1121
1122            });
1123
1124            // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
1125            if (el.length && el[0].attachEvent) {
1126                el.each(function() {
1127                    this.attachEvent("onpropertychange", self._sync);
1128                });
1129            }
1130
1131            // safari, chrome, firefox, IE11
1132            observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
1133            if (observer !== undefined) {
1134                if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
1135                this.propertyObserver = new observer(function (mutations) {
1136                    $.each(mutations, self._sync);
1137                });
1138                this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
1139            }
1140        },
1141
1142        // abstract
1143        triggerSelect: function(data) {
1144            var evt = $.Event("select2-selecting", { val: this.id(data), object: data, choice: data });
1145            this.opts.element.trigger(evt);
1146            return !evt.isDefaultPrevented();
1147        },
1148
1149        /**
1150         * Triggers the change event on the source element
1151         */
1152        // abstract
1153        triggerChange: function (details) {
1154
1155            details = details || {};
1156            details= $.extend({}, details, { type: "change", val: this.val() });
1157            // prevents recursive triggering
1158            this.opts.element.data("select2-change-triggered", true);
1159            this.opts.element.trigger(details);
1160            this.opts.element.data("select2-change-triggered", false);
1161
1162            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
1163            // so here we trigger the click event manually
1164            this.opts.element.click();
1165
1166            // ValidationEngine ignores the change event and listens instead to blur
1167            // so here we trigger the blur event manually if so desired
1168            if (this.opts.blurOnChange)
1169                this.opts.element.blur();
1170        },
1171
1172        //abstract
1173        isInterfaceEnabled: function()
1174        {
1175            return this.enabledInterface === true;
1176        },
1177
1178        // abstract
1179        enableInterface: function() {
1180            var enabled = this._enabled && !this._readonly,
1181                disabled = !enabled;
1182
1183            if (enabled === this.enabledInterface) return false;
1184
1185            this.container.toggleClass("select2-container-disabled", disabled);
1186            this.close();
1187            this.enabledInterface = enabled;
1188
1189            return true;
1190        },
1191
1192        // abstract
1193        enable: function(enabled) {
1194            if (enabled === undefined) enabled = true;
1195            if (this._enabled === enabled) return;
1196            this._enabled = enabled;
1197
1198            this.opts.element.prop("disabled", !enabled);
1199            this.enableInterface();
1200        },
1201
1202        // abstract
1203        disable: function() {
1204            this.enable(false);
1205        },
1206
1207        // abstract
1208        readonly: function(enabled) {
1209            if (enabled === undefined) enabled = false;
1210            if (this._readonly === enabled) return;
1211            this._readonly = enabled;
1212
1213            this.opts.element.prop("readonly", enabled);
1214            this.enableInterface();
1215        },
1216
1217        // abstract
1218        opened: function () {
1219            return (this.container) ? this.container.hasClass("select2-dropdown-open") : false;
1220        },
1221
1222        // abstract
1223        positionDropdown: function() {
1224            var $dropdown = this.dropdown,
1225                offset = this.container.offset(),
1226                height = this.container.outerHeight(false),
1227                width = this.container.outerWidth(false),
1228                dropHeight = $dropdown.outerHeight(false),
1229                $window = $(window),
1230                windowWidth = $window.width(),
1231                windowHeight = $window.height(),
1232                viewPortRight = $window.scrollLeft() + windowWidth,
1233                viewportBottom = $window.scrollTop() + windowHeight,
1234                dropTop = offset.top + height,
1235                dropLeft = offset.left,
1236                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
1237                enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
1238                dropWidth = $dropdown.outerWidth(false),
1239                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
1240                aboveNow = $dropdown.hasClass("select2-drop-above"),
1241                bodyOffset,
1242                above,
1243                changeDirection,
1244                css,
1245                resultsListNode;
1246
1247            // always prefer the current above/below alignment, unless there is not enough room
1248            if (aboveNow) {
1249                above = true;
1250                if (!enoughRoomAbove && enoughRoomBelow) {
1251                    changeDirection = true;
1252                    above = false;
1253                }
1254            } else {
1255                above = false;
1256                if (!enoughRoomBelow && enoughRoomAbove) {
1257                    changeDirection = true;
1258                    above = true;
1259                }
1260            }
1261
1262            //if we are changing direction we need to get positions when dropdown is hidden;
1263            if (changeDirection) {
1264                $dropdown.hide();
1265                offset = this.container.offset();
1266                height = this.container.outerHeight(false);
1267                width = this.container.outerWidth(false);
1268                dropHeight = $dropdown.outerHeight(false);
1269                viewPortRight = $window.scrollLeft() + windowWidth;
1270                viewportBottom = $window.scrollTop() + windowHeight;
1271                dropTop = offset.top + height;
1272                dropLeft = offset.left;
1273                dropWidth = $dropdown.outerWidth(false);
1274                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1275                $dropdown.show();
1276
1277                // fix so the cursor does not move to the left within the search-textbox in IE
1278                this.focusSearch();
1279            }
1280
1281            if (this.opts.dropdownAutoWidth) {
1282                resultsListNode = $('.select2-results', $dropdown)[0];
1283                $dropdown.addClass('select2-drop-auto-width');
1284                $dropdown.css('width', '');
1285                // Add scrollbar width to dropdown if vertical scrollbar is present
1286                dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
1287                dropWidth > width ? width = dropWidth : dropWidth = width;
1288                dropHeight = $dropdown.outerHeight(false);
1289                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1290            }
1291            else {
1292                this.container.removeClass('select2-drop-auto-width');
1293            }
1294
1295            //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1296            //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
1297
1298            // fix positioning when body has an offset and is not position: static
1299            if (this.body.css('position') !== 'static') {
1300                bodyOffset = this.body.offset();
1301                dropTop -= bodyOffset.top;
1302                dropLeft -= bodyOffset.left;
1303            }
1304
1305            if (!enoughRoomOnRight) {
1306                dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
1307            }
1308
1309            css =  {
1310                left: dropLeft,
1311                width: width
1312            };
1313
1314            if (above) {
1315                css.top = offset.top - dropHeight;
1316                css.bottom = 'auto';
1317                this.container.addClass("select2-drop-above");
1318                $dropdown.addClass("select2-drop-above");
1319            }
1320            else {
1321                css.top = dropTop;
1322                css.bottom = 'auto';
1323                this.container.removeClass("select2-drop-above");
1324                $dropdown.removeClass("select2-drop-above");
1325            }
1326            css = $.extend(css, evaluate(this.opts.dropdownCss, this.opts.element));
1327
1328            $dropdown.css(css);
1329        },
1330
1331        // abstract
1332        shouldOpen: function() {
1333            var event;
1334
1335            if (this.opened()) return false;
1336
1337            if (this._enabled === false || this._readonly === true) return false;
1338
1339            event = $.Event("select2-opening");
1340            this.opts.element.trigger(event);
1341            return !event.isDefaultPrevented();
1342        },
1343
1344        // abstract
1345        clearDropdownAlignmentPreference: function() {
1346            // clear the classes used to figure out the preference of where the dropdown should be opened
1347            this.container.removeClass("select2-drop-above");
1348            this.dropdown.removeClass("select2-drop-above");
1349        },
1350
1351        /**
1352         * Opens the dropdown
1353         *
1354         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1355         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1356         */
1357        // abstract
1358        open: function () {
1359
1360            if (!this.shouldOpen()) return false;
1361
1362            this.opening();
1363
1364            // Only bind the document mousemove when the dropdown is visible
1365            $document.on("mousemove.select2Event", function (e) {
1366                lastMousePosition.x = e.pageX;
1367                lastMousePosition.y = e.pageY;
1368            });
1369
1370            return true;
1371        },
1372
1373        /**
1374         * Performs the opening of the dropdown
1375         */
1376        // abstract
1377        opening: function() {
1378            var cid = this.containerEventName,
1379                scroll = "scroll." + cid,
1380                resize = "resize."+cid,
1381                orient = "orientationchange."+cid,
1382                mask;
1383
1384            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1385
1386            this.clearDropdownAlignmentPreference();
1387
1388            if(this.dropdown[0] !== this.body.children().last()[0]) {
1389                this.dropdown.detach().appendTo(this.body);
1390            }
1391
1392            // create the dropdown mask if doesn't already exist
1393            mask = $("#select2-drop-mask");
1394            if (mask.length == 0) {
1395                mask = $(document.createElement("div"));
1396                mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
1397                mask.hide();
1398                mask.appendTo(this.body);
1399                mask.on("mousedown touchstart click", function (e) {
1400                    // Prevent IE from generating a click event on the body
1401                    reinsertElement(mask);
1402
1403                    var dropdown = $("#select2-drop"), self;
1404                    if (dropdown.length > 0) {
1405                        self=dropdown.data("select2");
1406                        if (self.opts.selectOnBlur) {
1407                            self.selectHighlighted({noFocus: true});
1408                        }
1409                        self.close();
1410                        e.preventDefault();
1411                        e.stopPropagation();
1412                    }
1413                });
1414            }
1415
1416            // ensure the mask is always right before the dropdown
1417            if (this.dropdown.prev()[0] !== mask[0]) {
1418                this.dropdown.before(mask);
1419            }
1420
1421            // move the global id to the correct dropdown
1422            $("#select2-drop").removeAttr("id");
1423            this.dropdown.attr("id", "select2-drop");
1424
1425            // show the elements
1426            mask.show();
1427
1428            this.positionDropdown();
1429            this.dropdown.show();
1430            this.positionDropdown();
1431
1432            this.dropdown.addClass("select2-drop-active");
1433
1434            // attach listeners to events that can change the position of the container and thus require
1435            // the position of the dropdown to be updated as well so it does not come unglued from the container
1436            var that = this;
1437            this.container.parents().add(window).each(function () {
1438                $(this).on(resize+" "+scroll+" "+orient, function (e) {
1439                    if (that.opened()) that.positionDropdown();
1440                });
1441            });
1442
1443
1444        },
1445
1446        // abstract
1447        close: function () {
1448            if (!this.opened()) return;
1449
1450            var cid = this.containerEventName,
1451                scroll = "scroll." + cid,
1452                resize = "resize."+cid,
1453                orient = "orientationchange."+cid;
1454
1455            // unbind event listeners
1456            this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
1457
1458            this.clearDropdownAlignmentPreference();
1459
1460            $("#select2-drop-mask").hide();
1461            this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1462            this.dropdown.hide();
1463            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
1464            this.results.empty();
1465
1466            // Now that the dropdown is closed, unbind the global document mousemove event
1467            $document.off("mousemove.select2Event");
1468
1469            this.clearSearch();
1470            this.search.removeClass("select2-active");
1471            this.opts.element.trigger($.Event("select2-close"));
1472        },
1473
1474        /**
1475         * Opens control, sets input value, and updates results.
1476         */
1477        // abstract
1478        externalSearch: function (term) {
1479            this.open();
1480            this.search.val(term);
1481            this.updateResults(false);
1482        },
1483
1484        // abstract
1485        clearSearch: function () {
1486
1487        },
1488
1489        //abstract
1490        getMaximumSelectionSize: function() {
1491            return evaluate(this.opts.maximumSelectionSize, this.opts.element);
1492        },
1493
1494        // abstract
1495        ensureHighlightVisible: function () {
1496            var results = this.results, children, index, child, hb, rb, y, more, topOffset;
1497
1498            index = this.highlight();
1499
1500            if (index < 0) return;
1501
1502            if (index == 0) {
1503
1504                // if the first element is highlighted scroll all the way to the top,
1505                // that way any unselectable headers above it will also be scrolled
1506                // into view
1507
1508                results.scrollTop(0);
1509                return;
1510            }
1511
1512            children = this.findHighlightableChoices().find('.select2-result-label');
1513
1514            child = $(children[index]);
1515
1516            topOffset = (child.offset() || {}).top || 0;
1517
1518            hb = topOffset + child.outerHeight(true);
1519
1520            // if this is the last child lets also make sure select2-more-results is visible
1521            if (index === children.length - 1) {
1522                more = results.find("li.select2-more-results");
1523                if (more.length > 0) {
1524                    hb = more.offset().top + more.outerHeight(true);
1525                }
1526            }
1527
1528            rb = results.offset().top + results.outerHeight(true);
1529            if (hb > rb) {
1530                results.scrollTop(results.scrollTop() + (hb - rb));
1531            }
1532            y = topOffset - results.offset().top;
1533
1534            // make sure the top of the element is visible
1535            if (y < 0 && child.css('display') != 'none' ) {
1536                results.scrollTop(results.scrollTop() + y); // y is negative
1537            }
1538        },
1539
1540        // abstract
1541        findHighlightableChoices: function() {
1542            return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
1543        },
1544
1545        // abstract
1546        moveHighlight: function (delta) {
1547            var choices = this.findHighlightableChoices(),
1548                index = this.highlight();
1549
1550            while (index > -1 && index < choices.length) {
1551                index += delta;
1552                var choice = $(choices[index]);
1553                if (choice.hasClass("select2-result-selectable") && !c
1553hoice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1554                    this.highlight(index);
1555                    break;
1556                }
1557            }
1558        },
1559
1560        // abstract
1561        highlight: function (index) {
1562            var choices = this.findHighlightableChoices(),
1563                choice,
1564                data;
1565
1566            if (arguments.length === 0) {
1567                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1568            }
1569
1570            if (index >= choices.length) index = choices.length - 1;
1571            if (index < 0) index = 0;
1572
1573            this.removeHighlight();
1574
1575            choice = $(choices[index]);
1576            choice.addClass("select2-highlighted");
1577
1578            // ensure assistive technology can determine the active choice
1579            this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
1580
1581            this.ensureHighlightVisible();
1582
1583            this.liveRegion.text(choice.text());
1584
1585            data = choice.data("select2-data");
1586            if (data) {
1587                this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1588            }
1589        },
1590
1591        removeHighlight: function() {
1592            this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1593        },
1594
1595        touchMoved: function() {
1596            this._touchMoved = true;
1597        },
1598
1599        clearTouchMoved: function() {
1600          this._touchMoved = false;
1601        },
1602
1603        // abstract
1604        countSelectableResults: function() {
1605            return this.findHighlightableChoices().length;
1606        },
1607
1608        // abstract
1609        highlightUnderEvent: function (event) {
1610            var el = $(event.target).closest(".select2-result-selectable");
1611            if (el.length > 0 && !el.is(".select2-highlighted")) {
1612                var choices = this.findHighlightableChoices();
1613                this.highlight(choices.index(el));
1614            } else if (el.length == 0) {
1615                // if we are over an unselectable item remove all highlights
1616                this.removeHighlight();
1617            }
1618        },
1619
1620        // abstract
1621        loadMoreIfNeeded: function () {
1622            var results = this.results,
1623                more = results.find("li.select2-more-results"),
1624                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1625                page = this.resultsPage + 1,
1626                self=this,
1627                term=this.search.val(),
1628                context=this.context;
1629
1630            if (more.length === 0) return;
1631            below = more.offset().top - results.offset().top - results.height();
1632
1633            if (below <= this.opts.loadMorePadding) {
1634                more.addClass("select2-active");
1635                this.opts.query({
1636                        element: this.opts.element,
1637                        term: term,
1638                        page: page,
1639                        context: context,
1640                        matcher: this.opts.matcher,
1641                        callback: this.bind(function (data) {
1642
1643                    // ignore a response if the select2 has been closed before it was received
1644                    if (!self.opened()) return;
1645
1646
1647                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1648                    self.postprocessResults(data, false, false);
1649
1650                    if (data.more===true) {
1651                        more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, self.opts.element, page+1));
1652                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1653                    } else {
1654                        more.remove();
1655                    }
1656                    self.positionDropdown();
1657                    self.resultsPage = page;
1658                    self.context = data.context;
1659                    this.opts.element.trigger({ type: "select2-loaded", items: data });
1660                })});
1661            }
1662        },
1663
1664        /**
1665         * Default tokenizer function which does nothing
1666         */
1667        tokenize: function() {
1668
1669        },
1670
1671        /**
1672         * @param initial whether or not this is the call to this method right after the dropdown has been opened
1673         */
1674        // abstract
1675        updateResults: function (initial) {
1676            var search = this.search,
1677                results = this.results,
1678                opts = this.opts,
1679                data,
1680                self = this,
1681                input,
1682                term = search.val(),
1683                lastTerm = $.data(this.container, "select2-last-term"),
1684                // sequence number used to drop out-of-order responses
1685                queryNumber;
1686
1687            // prevent duplicate queries against the same term
1688            if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1689
1690            $.data(this.container, "select2-last-term", term);
1691
1692            // if the search is currently hidden we do not alter the results
1693            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1694                return;
1695            }
1696
1697            function postRender() {
1698                search.removeClass("select2-active");
1699                self.positionDropdown();
1700                if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
1701                    self.liveRegion.text(results.text());
1702                }
1703                else {
1704                    self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length));
1705                }
1706            }
1707
1708            function render(html) {
1709                results.html(html);
1710                postRender();
1711            }
1712
1713            queryNumber = ++this.queryCount;
1714
1715            var maxSelSize = this.getMaximumSelectionSize();
1716            if (maxSelSize >=1) {
1717                data = this.data();
1718                if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1719                    render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, opts.element, maxSelSize) + "</li>");
1720                    return;
1721                }
1722            }
1723
1724            if (search.val().length < opts.minimumInputLength) {
1725                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1726                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, opts.element, search.val(), opts.minimumInputLength) + "</li>");
1727                } else {
1728                    render("");
1729                }
1730                if (initial && this.showSearch) this.showSearch(true);
1731                return;
1732            }
1733
1734            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1735                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1736                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, opts.element, search.val(), opts.maximumInputLength) + "</li>");
1737                } else {
1738                    render("");
1739                }
1740                return;
1741            }
1742
1743            if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1744                render("<li class='select2-searching'>" + evaluate(opts.formatSearching, opts.element) + "</li>");
1745            }
1746
1747            search.addClass("select2-active");
1748
1749            this.removeHighlight();
1750
1751            // give the tokenizer a chance to pre-process the input
1752            input = this.tokenize();
1753            if (input != undefined && input != null) {
1754                search.val(input);
1755            }
1756
1757            this.resultsPage = 1;
1758
1759            opts.query({
1760                element: opts.element,
1761                    term: search.val(),
1762                    page: this.resultsPage,
1763                    context: null,
1764                    matcher: opts.matcher,
1765                    callback: this.bind(function (data) {
1766                var def; // default choice
1767
1768                // ignore old responses
1769                if (queryNumber != this.queryCount) {
1770                  return;
1771                }
1772
1773                // ignore a response if the select2 has been closed before it was received
1774                if (!this.opened()) {
1775                    this.search.removeClass("select2-active");
1776                    return;
1777                }
1778
1779                // handle ajax error
1780                if(data.hasError !== undefined && checkFormatter(opts.formatAjaxError, "formatAjaxError")) {
1781                    render("<li class='select2-ajax-error'>" + evaluate(opts.formatAjaxError, opts.element, data.jqXHR, data.textStatus, data.errorThrown) + "</li>");
1782                    return;
1783                }
1784
1785                // save context, if any
1786                this.context = (data.context===undefined) ? null : data.context;
1787                // create a default choice and prepend it to the list
1788                if (this.opts.createSearchChoice && search.val() !== "") {
1789                    def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1790                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1791                        if ($(data.results).filter(
1792                            function () {
1793                                return equal(self.id(this), self.id(def));
1794                            }).length === 0) {
1795                            this.opts.createSearchChoicePosition(data.results, def);
1796                        }
1797                    }
1798                }
1799
1800                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1801                    render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, opts.element, search.val()) + "</li>");
1802                    return;
1803                }
1804
1805                results.empty();
1806                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1807
1808                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1809                    results.append("<li class='select2-more-results'>" + opts.escapeMarkup(evaluate(opts.formatLoadMore, opts.element, this.resultsPage)) + "</li>");
1810                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1811                }
1812
1813                this.postprocessResults(data, initial);
1814
1815                postRender();
1816
1817                this.opts.element.trigger({ type: "select2-loaded", items: data });
1818            })});
1819        },
1820
1821        // abstract
1822        cancel: function () {
1823            this.close();
1824        },
1825
1826        // abstract
1827        blur: function () {
1828            // if selectOnBlur == true, select the currently highlighted option
1829            if (this.opts.selectOnBlur)
1830                this.selectHighlighted({noFocus: true});
1831
1832            this.close();
1833            this.container.removeClass("select2-container-active");
1834            // synonymous to .is(':focus'), which is available in jquery >= 1.6
1835            if (this.search[0] === document.activeElement) { this.search.blur(); }
1836            this.clearSearch();
1837            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1838        },
1839
1840        // abstract
1841        focusSearch: function () {
1842            focus(this.search);
1843        },
1844
1845        // abstract
1846        selectHighlighted: function (options) {
1847            if (this._touchMoved) {
1848              this.clearTouchMoved();
1849              return;
1850            }
1851            var index=this.highlight(),
1852                highlighted=this.results.find(".select2-highlighted"),
1853                data = highlighted.closest('.select2-result').data("select2-data");
1854
1855            if (data) {
1856                this.highlight(index);
1857                this.onSelect(data, options);
1858            } else if (options && options.noFocus) {
1859                this.close();
1860            }
1861        },
1862
1863        // abstract
1864        getPlaceholder: function () {
1865            var placeholderOption;
1866            return this.opts.element.attr("placeholder") ||
1867                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1868                this.opts.element.data("placeholder") ||
1869                this.opts.placeholder ||
1870                ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
1871        },
1872
1873        // abstract
1874        getPlaceholderOption: function() {
1875            if (this.select) {
1876                var firstOption = this.select.children('option').first();
1877                if (this.opts.placeholderOption !== undefined ) {
1878                    //Determine the placeholder option based on the specified placeholderOption setting
1879                    return (this.opts.placeholderOption === "first" && firstOption) ||
1880                           (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
1881                } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
1882                    //No explicit placeholder option specified, use the first if it's blank
1883                    return firstOption;
1884                }
1885            }
1886        },
1887
1888        /**
1889         * Get the desired width for the container element.  This is
1890         * derived first from option `width` passed to select2, then
1891         * the inline 'style' on the original element, and finally
1892         * falls back to the jQuery calculated element width.
1893         */
1894        // abstract
1895        initContainerWidth: function () {
1896            function resolveContainerWidth() {
1897                var style, attrs, matches, i, l, attr;
1898
1899                if (this.opts.width === "off") {
1900                    return null;
1901                } else if (this.opts.width === "element"){
1902                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1903                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1904                    // check if there is inline style on the element that contains width
1905                    style = this.opts.element.attr('style');
1906                    if (style !== undefined) {
1907                        attrs = style.split(';');
1908                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
1909                            attr = attrs[i].replace(/\s/g, '');
1910                            matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
1911                            if (matches !== null && matches.length >= 1)
1912                                return matches[1];
1913                        }
1914                    }
1915
1916                    if (this.opts.width === "resolve") {
1917                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1918                        // when attached to input type=hidden or elements hidden via css
1919                        style = this.opts.element.css('width');
1920                        if (style.indexOf("%") > 0) return style;
1921
1922                        // finally, fallback on the calculated width of the element
1923                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1924                    }
1925
1926                    return null;
1927                } else if ($.isFunction(this.opts.width)) {
1928                    return this.opts.width();
1929                } else {
1930                    return this.opts.width;
1931               }
1932            };
1933
1934            var width = resolveContainerWidth.call(this);
1935            if (width !== null) {
1936                this.container.css("width", width);
1937            }
1938        }
1939    });
1940
1941    SingleSelect2 = clazz(AbstractSelect2, {
1942
1943        // single
1944
1945        createContainer: function () {
1946            var container = $(document.createElement("div")).attr({
1947                "class": "select2-container"
1948            }).html([
1949                "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
1950                "   <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
1951                "   <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
1952                "</a>",
1953                "<label for='' class='select2-offscreen'></label>",
1954                "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
1955                "<div class='select2-drop select2-display-none'>",
1956                "   <div class='select2-search'>",
1957                "       <label for='' class='select2-offscreen'></label>",
1958                "       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
1959                "       aria-autocomplete='list' />",
1960                "   </div>",
1961                "   <ul class='select2-results' role='listbox'>",
1962                "   </ul>",
1963                "</div>"].join(""));
1964            return container;
1965        },
1966
1967        // single
1968        enableInterface: function() {
1969            if (this.parent.enableInterface.apply(this, arguments)) {
1970                this.focusser.prop("disabled", !this.isInterfaceEnabled());
1971            }
1972        },
1973
1974        // single
1975        opening: function () {
1976            var el, range, len;
1977
1978            if (this.opts.minimumResultsForSearch >= 0) {
1979                this.showSearch(true);
1980            }
1981
1982            this.parent.opening.apply(this, arguments);
1983
1984            if (this.showSearchInput !== false) {
1985                // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
1986                // all other browsers handle this just fine
1987
1988                this.search.val(this.focusser.val());
1989            }
1990            if (this.opts.shouldFocusInput(this)) {
1991                this.search.focus();
1992                // move the cursor to the end after focussing, otherwise it will be at the beginning and
1993                // new text will appear *before* focusser.val()
1994                el = this.search.get(0);
1995                if (el.createTextRange) {
1996                    range = el.createTextRange();
1997                    range.collapse(false);
1998                    range.select();
1999                } else if (el.setSelectionRange) {
2000                    len = this.search.val().length;
2001                    el.setSelectionRange(len, len);
2002                }
2003            }
2004
2005            // initializes search's value with nextSearchTerm (if defined by user)
2006            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
2007            if(this.search.val() === "") {
2008                if(this.nextSearchTerm != undefined){
2009                    this.search.val(this.nextSearchTerm);
2010                    this.search.select();
2011                }
2012            }
2013
2014            this.focusser.prop("disabled", true).val("");
2015            this.updateResults(true);
2016            this.opts.element.trigger($.Event("select2-open"));
2017        },
2018
2019        // single
2020        close: function () {
2021            if (!this.opened()) return;
2022            this.parent.close.apply(this, arguments);
2023
2024            this.focusser.prop("disabled", false);
2025
2026            if (this.opts.shouldFocusInput(this)) {
2027                this.focusser.focus();
2028            }
2029        },
2030
2031        // single
2032        focus: function () {
2033            if (this.opened()) {
2034                this.close();
2035            } else {
2036                this.focusser.prop("disabled", false);
2037                if (this.opts.shouldFocusInput(this)) {
2038                    this.focusser.focus();
2039                }
2040            }
2041        },
2042
2043        // single
2044        isFocused: function () {
2045            return this.container.hasClass("select2-container-active");
2046        },
2047
2048        // single
2049        cancel: function () {
2050            this.parent.cancel.apply(this, arguments);
2051            this.focusser.prop("disabled", false);
2052
2053            if (this.opts.shouldFocusInput(this)) {
2054                this.focusser.focus();
2055            }
2056        },
2057
2058        // single
2059        destroy: function() {
2060            $("label[for='" + this.focusser.attr('id') + "']")
2061                .attr('for', this.opts.element.attr("id"));
2062            this.parent.destroy.apply(this, arguments);
2063
2064            cleanupJQueryElements.call(this,
2065                "selection",
2066                "focusser"
2067            );
2068        },
2069
2070        // single
2071        initContainer: function () {
2072
2073            var selection,
2074                container = this.container,
2075                dropdown = this.dropdown,
2076                idSuffix = nextUid(),
2077                elementLabel;
2078
2079            if (this.opts.minimumResultsForSearch < 0) {
2080                this.showSearch(false);
2081            } else {
2082                this.showSearch(true);
2083            }
2084
2085            this.selection = selection = container.find(".select2-choice");
2086
2087            this.focusser = container.find(".select2-focusser");
2088
2089            // add aria associations
2090            selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
2091            this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
2092            this.results.attr("id", "select2-results-"+idSuffix);
2093            this.search.attr("aria-owns", "select2-results-"+idSuffix);
2094
2095            // rewrite labels from original element to focusser
2096            this.focusser.attr("id", "s2id_autogen"+idSuffix);
2097
2098            elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
2099
2100            this.focusser.prev()
2101                .text(elementLabel.text())
2102                .attr('for', this.focusser.attr('id'));
2103
2104            // Ensure the original element retains an accessible name
2105            var originalTitle = this.opts.element.attr("title");
2106            this.opts.element.attr("title", (originalTitle || elementLabel.text()));
2107
2108            this.focusser.attr("tabindex", this.elementTabIndex);
2109
2110            // write label for search field using the label from the focusser element
2111            this.search.attr("id", this.focusser.attr('id') + '_search');
2112
2113            this.search.prev()
2114                .text($("label[for='" + this.focusser.attr('id') + "']").text())
2115                .attr('for', this.search.attr('id'));
2116
2117            this.search.on("keydown", this.bind(function (e) {
2118                if (!this.isInterfaceEnabled()) return;
2119
2120                // filter 229 keyCodes (input method editor is processing key input)
2121                if (229 == e.keyCode) return;
2122
2123                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2124                    // prevent the page from scrolling
2125                    killEvent(e);
2126                    return;
2127                }
2128
2129                switch (e.which) {
2130                    case KEY.UP:
2131                    case KEY.DOWN:
2132                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2133                        killEvent(e);
2134                        return;
2135                    case KEY.ENTER:
2136                        this.selectHighlighted();
2137                        killEvent(e);
2138                        return;
2139                    case KEY.TAB:
2140                        this.selectHighlighted({noFocus: true});
2141                        return;
2142                    case KEY.ESC:
2143                        this.cancel(e);
2144                        killEvent(e);
2145                        return;
2146                }
2147            }));
2148
2149            this.search.on("blur", this.bind(function(e) {
2150                // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
2151                // without this the search field loses focus which is annoying
2152                if (document.activeElement === this.body.get(0)) {
2153                    window.setTimeout(this.bind(function() {
2154                        if (this.opened()) {
2155                            this.search.focus();
2156                        }
2157                    }), 0);
2158                }
2159            }));
2160
2161            this.focusser.on("keydown", this.bind(function (e) {
2162                if (!this.isInterfaceEnabled()) return;
2163
2164                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
2165                    return;
2166                }
2167
2168                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
2169                    killEvent(e);
2170                    return;
2171                }
2172
2173                if (e.which == KEY.DOWN || e.which == KEY.UP
2174                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
2175
2176                    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
2177
2178                    this.open();
2179                    killEvent(e);
2180                    return;
2181                }
2182
2183                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
2184                    if (this.opts.allowClear) {
2185                        this.clear();
2186                    }
2187                    killEvent(e);
2188                    return;
2189                }
2190            }));
2191
2192
2193            installKeyUpChangeEvent(this.focusser);
2194            this.focusser.on("keyup-change input", this.bind(function(e) {
2195                if (this.opts.minimumResultsForSearch >= 0) {
2196                    e.stopPropagation();
2197                    if (this.opened()) return;
2198                    this.open();
2199                }
2200            }));
2201
2202            selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
2203                if (!this.isInterfaceEnabled()) return;
2204                this.clear();
2205                killEventImmediately(e);
2206                this.close();
2207                this.selection.focus();
2208            }));
2209
2210            selection.on("mousedown touchstart", this.bind(function (e) {
2211                // Prevent IE from generating a click event on the body
2212                reinsertElement(selection);
2213
2214                if (!this.container.hasClass("select2-container-active")) {
2215                    this.opts.element.trigger($.Event("select2-focus"));
2216                }
2217
2218                if (this.opened()) {
2219                    this.close();
2220                } else if (this.isInterfaceEnabled()) {
2221                    this.open();
2222                }
2223
2224                killEvent(e);
2225            }));
2226
2227            dropdown.on("mousedown touchstart", this.bind(function() {
2228                if (this.opts.shouldFocusInput(this)) {
2229                    this.search.focus();
2230                }
2231            }));
2232
2233            selection.on("focus", this.bind(function(e) {
2234                killEvent(e);
2235            }));
2236
2237            this.focusser.on("focus", this.bind(function(){
2238                if (!this.container.hasClass("select2-container-active")) {
2239                    this.opts.element.trigger($.Event("select2-focus"));
2240                }
2241                this.container.addClass("select2-container-active");
2242            })).on("blur", this.bind(function() {
2243                if (!this.opened()) {
2244                    this.container.removeClass("select2-container-active");
2245                    this.opts.element.trigger($.Event("select2-blur"));
2246                }
2247            }));
2248            this.search.on("focus", this.bind(function(){
2249                if (!this.container.hasClass("select2-container-active")) {
2250                    this.opts.element.trigger($.Event("select2-focus"));
2251                }
2252                this.container.addClass("select2-container-active");
2253            }));
2254
2255            this.initContainerWidth();
2256            this.opts.element.addClass("select2-offscreen");
2257            this.setPlaceholder();
2258
2259        },
2260
2261        // single
2262        clear: function(triggerChange) {
2263            var data=this.selection.data("select2-data");
2264            if (data) { // guard against queued quick consecutive clicks
2265                var evt = $.Event("select2-clearing");
2266                this.opts.element.trigger(evt);
2267                if (evt.isDefaultPrevented()) {
2268                    return;
2269                }
2270                var placeholderOption = this.getPlaceholderOption();
2271                this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2272                this.selection.find(".select2-chosen").empty();
2273                this.selection.removeData("select2-data");
2274                this.setPlaceholder();
2275
2276                if (triggerChange !== false){
2277                    this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2278                    this.triggerChange({removed:data});
2279                }
2280            }
2281        },
2282
2283        /**
2284         * Sets selection based on source element's value
2285         */
2286        // single
2287        initSelection: function () {
2288            var selected;
2289            if (this.isPlaceholderOptionSelected()) {
2290                this.updateSelection(null);
2291                this.close();
2292                this.setPlaceholder();
2293            } else {
2294                var self = this;
2295                this.opts.initSelection.call(null, this.opts.element, function(selected){
2296                    if (selected !== undefined && selected !== null) {
2297                        self.updateSelection(selected);
2298                        self.close();
2299                        self.setPlaceholder();
2300                        self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val());
2301                    }
2302                });
2303            }
2304        },
2305
2306        isPlaceholderOptionSelected: function() {
2307            var placeholderOption;
2308            if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
2309            return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
2310                || (this.opts.element.val() === "")
2311                || (this.opts.element.val() === undefined)
2312                || (this.opts.element.val() === null);
2313        },
2314
2315        // single
2316        prepareOpts: function () {
2317            var opts = this.parent.prepareOpts.apply(this, arguments),
2318                self=this;
2319
2320            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2321                // install the selection initializer
2322                opts.initSelection = function (element, callback) {
2323                    var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
2324                    // a single select box always has a value, no need to null check 'selected'
2325                    callback(self.optionToData(selected));
2326                };
2327            } else if ("data" in opts) {
2328                // install default initSelection when applied to hidden input and data is local
2329                opts.initSelection = opts.initSelection || function (element, callback) {
2330                    var id = element.val();
2331                    //search in data by id, storing the actual matching item
2332                    var match = null;
2333                    opts.query({
2334                        matcher: function(term, text, el){
2335                            var is_match = equal(id, opts.id(el));
2336                            if (is_match) {
2337                                match = el;
2338                            }
2339                            return is_match;
2340                        },
2341                        callback: !$.isFunction(callback) ? $.noop : function() {
2342                            callback(match);
2343                        }
2344                    });
2345                };
2346            }
2347
2348            return opts;
2349        },
2350
2351        // single
2352        getPlaceholder: function() {
2353            // if a placeholder is specified on a single select without a valid placeholder option ignore it
2354            if (this.select) {
2355                if (this.getPlaceholderOption() === undefined) {
2356                    return undefined;
2357                }
2358            }
2359
2360            return this.parent.getPlaceholder.apply(this, arguments);
2361        },
2362
2363        // single
2364        setPlaceholder: function () {
2365            var placeholder = this.getPlaceholder();
2366
2367            if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2368
2369                // check for a placeholder option if attached to a select
2370                if (this.select && this.getPlaceholderOption() === undefined) return;
2371
2372                this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2373
2374                this.selection.addClass("select2-default");
2375
2376                this.container.removeClass("select2-allowclear");
2377            }
2378        },
2379
2380        // single
2381        postprocessResults: function (data, initial, noHighlightUpdate) {
2382            var selected = 0, self = this, showSearchInput = true;
2383
2384            // find the selected element in the result list
2385
2386            this.findHighlightableChoices().each2(function (i, elm) {
2387                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2388                    selected = i;
2389                    return false;
2390                }
2391            });
2392
2393            // and highlight it
2394            if (noHighlightUpdate !== false) {
2395                if (initial === true && selected >= 0) {
2396                    this.highlight(selected);
2397                } else {
2398                    this.highlight(0);
2399                }
2400            }
2401
2402            // hide the search box if this is the first we got the results and there are enough of them for search
2403
2404            if (initial === true) {
2405                var min = this.opts.minimumResultsForSearch;
2406                if (min >= 0) {
2407                    this.showSearch(countResults(data.results) >= min);
2408                }
2409            }
2410        },
2411
2412        // single
2413        showSearch: function(showSearchInput) {
2414            if (this.showSearchInput === showSearchInput) return;
2415
2416            this.showSearchInput = showSearchInput;
2417
2418            this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2419            this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2420            //add "select2-with-searchbox" to the container if search box is shown
2421            $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2422        },
2423
2424        // single
2425        onSelect: function (data, options) {
2426
2427            if (!this.triggerSelect(data)) { return; }
2428
2429            var old = this.opts.element.val(),
2430                oldData = this.data();
2431
2432            this.opts.element.val(this.id(data));
2433            this.updateSelection(data);
2434
2435            this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2436
2437            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2438            this.close();
2439
2440            if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
2441                this.focusser.focus();
2442            }
2443
2444            if (!equal(old, this.id(data))) {
2445                this.triggerChange({ added: data, removed: oldData });
2446            }
2447        },
2448
2449        // single
2450        updateSelection: function (data) {
2451
2452            var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2453
2454            this.selection.data("select2-data", data);
2455
2456            container.empty();
2457            if (data !== null) {
2458                formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2459            }
2460            if (formatted !== undefined) {
2461                container.append(formatted);
2462            }
2463            cssClass=this.opts.formatSelectionCssClass(data, container);
2464            if (cssClass !== undefined) {
2465                container.addClass(cssClass);
2466            }
2467
2468            this.selection.removeClass("select2-default");
2469
2470            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2471                this.container.addClass("select2-allowclear");
2472            }
2473        },
2474
2475        // single
2476        val: function () {
2477            var val,
2478                triggerChange = false,
2479                data = null,
2480                self = this,
2481                oldData = this.data();
2482
2483            if (arguments.length === 0) {
2484                return this.opts.element.val();
2485            }
2486
2487            val = arguments[0];
2488
2489            if (arguments.length > 1) {
2490                triggerChange = arguments[1];
2491            }
2492
2493            if (this.select) {
2494                this.select
2495                    .val(val)
2496                    .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2497                        data = self.optionToData(elm);
2498                        return false;
2499                    });
2500                this.updateSelection(data);
2501                this.setPlaceholder();
2502                if (triggerChange) {
2503                    this.triggerChange({added: data, removed:oldData});
2504                }
2505            } else {
2506                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2507                if (!val && val !== 0) {
2508                    this.clear(triggerChange);
2509                    return;
2510                }
2511                if (this.opts.initSelection === undefined) {
2512                    throw new Error("cannot call val() if initSelection() is not defined");
2513                }
2514                this.opts.element.val(val);
2515                this.opts.initSelection(this.opts.element, function(data){
2516                    self.opts.element.val(!data ? "" : self.id(data));
2517                    self.updateSelection(data);
2518                    self.setPlaceholder();
2519                    if (triggerChange) {
2520                        self.triggerChange({added: data, removed:oldData});
2521                    }
2522                });
2523            }
2524        },
2525
2526        // single
2527        clearSearch: function () {
2528            this.search.val("");
2529            this.focusser.val("");
2530        },
2531
2532        // single
2533        data: function(value) {
2534            var data,
2535                triggerChange = false;
2536
2537            if (arguments.length === 0) {
2538                data = this.selection.data("select2-data");
2539                if (data == undefined) data = null;
2540                return data;
2541            } else {
2542                if (arguments.length > 1) {
2543                    triggerChange = arguments[1];
2544                }
2545                if (!value) {
2546                    this.clear(triggerChange);
2547                } else {
2548                    data = this.data();
2549                    this.opts.element.val(!value ? "" : this.id(value));
2550                    this.updateSelection(value);
2551                    if (triggerChange) {
2552                        this.triggerChange({added: value, removed:data});
2553                    }
2554                }
2555            }
2556        }
2557    });
2558
2559    MultiSelect2 = clazz(AbstractSelect2, {
2560
2561        // multi
2562        createContainer: function () {
2563            var container = $(document.createElement("div")).attr({
2564                "class": "select2-container select2-container-multi"
2565            }).html([
2566                "<ul class='select2-choices'>",
2567                "  <li class='select2-search-field'>",
2568                "    <label for='' class='select2-offscreen'></label>",
2569                "    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2570                "  </li>",
2571                "</ul>",
2572                "<div class='select2-drop select2-drop-multi select2-display-none'>",
2573                "   <ul class='select2-results'>",
2574                "   </ul>",
2575                "</div>"].join(""));
2576            return container;
2577        },
2578
2579        // multi
2580        prepareOpts: function () {
2581            var opts = this.parent.prepareOpts.apply(this, arguments),
2582                self=this;
2583
2584            // TODO validate placeholder is a string if specified
2585
2586            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2587                // install the selection initializer
2588                opts.initSelection = function (element, callback) {
2589
2590                    var data = [];
2591
2592                    element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
2593                        data.push(self.optionToData(elm));
2594                    });
2595                    callback(data);
2596                };
2597            } else if ("data" in opts) {
2598                // install default initSelection when applied to hidden input and data is local
2599                opts.initSelection = opts.initSelection || function (element, callback) {
2600                    var ids = splitVal(element.val(), opts.separator);
2601                    //search in data by array of ids, storing matching items in a list
2602                    var matches = [];
2603                    opts.query({
2604                        matcher: function(term, text, el){
2605                            var is_match = $.grep(ids, function(id) {
2606                                return equal(id, opts.id(el));
2607                            }).length;
2608                            if (is_match) {
2609                                matches.push(el);
2610                            }
2611                            return is_match;
2612                        },
2613                        callback: !$.isFunction(callback) ? $.noop : function() {
2614                            // reorder matches based on the order they appear in the ids array because right now
2615                            // they are in the order in which they appear in data array
2616                            var ordered = [];
2617                            for (var i = 0; i < ids.length; i++) {
2618                                var id = ids[i];
2619                                for (var j = 0; j < matches.length; j++) {
2620                                    var match = matches[j];
2621                                    if (equal(id, opts.id(match))) {
2622                                        ordered.push(match);
2623                                        matches.splice(j, 1);
2624                                        break;
2625                                    }
2626                                }
2627                            }
2628                            callback(ordered);
2629                        }
2630                    });
2631                };
2632            }
2633
2634            return opts;
2635        },
2636
2637        // multi
2638        selectChoice: function (choice) {
2639
2640            var selected = this.container.find(".select2-search-choice-focus");
2641            if (selected.length && choice && choice[0] == selected[0]) {
2642
2643            } else {
2644                if (selected.length) {
2645                    this.opts.element.trigger("choice-deselected", selected);
2646                }
2647                selected.removeClass("select2-search-choice-focus");
2648                if (choice && choice.length) {
2649                    this.close();
2650                    choice.addClass("select2-search-choice-focus");
2651                    this.opts.element.trigger("choice-selected", choice);
2652                }
2653            }
2654        },
2655
2656        // multi
2657        destroy: function() {
2658            $("label[for='" + this.search.attr('id') + "']")
2659                .attr('for', this.opts.element.attr("id"));
2660            this.parent.destroy.apply(this, arguments);
2661
2662            cleanupJQueryElements.call(this,
2663                "searchContainer",
2664                "selection"
2665            );
2666        },
2667
2668        // multi
2669        initContainer: function () {
2670
2671            var selector = ".select2-choices", selection;
2672
2673            this.searchContainer = this.container.find(".select2-search-field");
2674            this.selection = selection = this.container.find(selector);
2675
2676            var _this = this;
2677            this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
2678                //killEvent(e);
2679                _this.search[0].focus();
2680                _this.selectChoice($(this));
2681            });
2682
2683            // rewrite labels from original element to focusser
2684            this.search.attr("id", "s2id_autogen"+nextUid());
2685
2686            this.search.prev()
2687                .text($("label[for='" + this.opts.element.attr("id") + "']").text())
2688                .attr('for', this.search.attr('id'));
2689
2690            this.search.on("input paste", this.bind(function() {
2691                if (this.search.attr('placeholder') && this.search.val().length == 0) return;
2692                if (!this.isInterfaceEnabled()) return;
2693                if (!this.opened()) {
2694                    this.open();
2695                }
2696            }));
2697
2698            this.search.attr("tabindex", this.elementTabIndex);
2699
2700            this.keydowns = 0;
2701            this.search.on("keydown", this.bind(function (e) {
2702                if (!this.isInterfaceEnabled()) return;
2703
2704                ++this.keydowns;
2705                var selected = selection.find(".select2-search-choice-focus");
2706                var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2707                var next = selected.next(".select2-search-choice:not(.select2-locked)");
2708                var pos = getCursorInfo(this.search);
2709
2710                if (selected.length &&
2711                    (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2712                    var selectedChoice = selected;
2713                    if (e.which == KEY.LEFT && prev.length) {
2714                        selectedChoice = prev;
2715                    }
2716                    else if (e.which == KEY.RIGHT) {
2717                        selectedChoice = next.length ? next : null;
2718                    }
2719                    else if (e.which === KEY.BACKSPACE) {
2720                        if (this.unselect(selected.first())) {
2721                            this.search.width(10);
2722                            selectedChoice = prev.length ? prev : next;
2723                        }
2724                    } else if (e.which == KEY.DELETE) {
2725                        if (this.unselect(selected.first())) {
2726                            this.search.width(10);
2727                            selectedChoice = next.length ? next : null;
2728                        }
2729                    } else if (e.which == KEY.ENTER) {
2730                        selectedChoice = null;
2731                    }
2732
2733                    this.selectChoice(selectedChoice);
2734                    killEvent(e);
2735                    if (!selectedChoice || !selectedChoice.length) {
2736                        this.open();
2737                    }
2738                    return;
2739                } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2740                    || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2741
2742                    this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2743                    killEvent(e);
2744                    return;
2745                } else {
2746                    this.selectChoice(null);
2747                }
2748
2749                if (this.opened()) {
2750                    switch (e.which) {
2751                    case KEY.UP:
2752                    case KEY.DOWN:
2753                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2754                        killEvent(e);
2755                        return;
2756                    case KEY.ENTER:
2757                        this.selectHighlighted();
2758                        killEvent(e);
2759                        return;
2760                    case KEY.TAB:
2761                        this.selectHighlighted({noFocus:true});
2762                        this.close();
2763                        return;
2764                    case KEY.ESC:
2765                        this.cancel(e);
2766                        killEvent(e);
2767                        return;
2768                    }
2769                }
2770
2771                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2772                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2773                    return;
2774                }
2775
2776                if (e.which === KEY.ENTER) {
2777                    if (this.opts.openOnEnter === false) {
2778                        return;
2779                    } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2780                        return;
2781                    }
2782                }
2783
2784                this.open();
2785
2786                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2787                    // prevent the page from scrolling
2788                    killEvent(e);
2789                }
2790
2791                if (e.which === KEY.ENTER) {
2792                    // prevent form from being submitted
2793                    killEvent(e);
2794                }
2795
2796            }));
2797
2798            this.search.on("keyup", this.bind(function (e) {
2799                this.keydowns = 0;
2800                this.resizeSearch();
2801            })
2802            );
2803
2804            this.search.on("blur", this.bind(function(e) {
2805                this.container.removeClass("select2-container-active");
2806                this.search.removeClass("select2-focused");
2807                this.selectChoice(null);
2808                if (!this.opened()) this.clearSearch();
2809                e.stopImmediatePropagation();
2810                this.opts.element.trigger($.Event("select2-blur"));
2811            }));
2812
2813            this.container.on("click", selector, this.bind(function (e) {
2814                if (!this.isInterfaceEnabled()) return;
2815                if ($(e.target).closest(".select2-search-choice").length > 0) {
2816                    // clicked inside a select2 search choice, do not open
2817                    return;
2818                }
2819                this.selectChoice(null);
2820                this.clearPlaceholder();
2821                if (!this.container.hasClass("select2-container-active")) {
2822                    this.opts.element.trigger($.Event("select2-focus"));
2823                }
2824                this.open();
2825                this.focusSearch();
2826                e.preventDefault();
2827            }));
2828
2829            this.container.on("focus", selector, this.bind(function () {
2830                if (!this.isInterfaceEnabled()) return;
2831                if (!this.container.hasClass("select2-container-active")) {
2832                    this.opts.element.trigger($.Event("select2-focus"));
2833                }
2834                this.container.addClass("select2-container-active");
2835                this.dropdown.addClass("select2-drop-active");
2836                this.clearPlaceholder();
2837            }));
2838
2839            this.initContainerWidth();
2840            this.opts.element.addClass("select2-offscreen");
2841
2842            // set the placeholder if necessary
2843            this.clearSearch();
2844        },
2845
2846        // multi
2847        enableInterface: function() {
2848            if (this.parent.enableInterface.apply(this, arguments)) {
2849                this.search.prop("disabled", !this.isInterfaceEnabled());
2850            }
2851        },
2852
2853        // multi
2854        initSelection: function () {
2855            var data;
2856            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2857                this.updateSelection([]);
2858                this.close();
2859                // set the placeholder if necessary
2860                this.clearSearch();
2861            }
2862            if (this.select || this.opts.element.val() !== "") {
2863                var self = this;
2864                this.opts.initSelection.call(null, this.opts.element, function(data){
2865                    if (data !== undefined && data !== null) {
2866                        self.updateSelection(data);
2867                        self.close();
2868                        // set the placeholder if necessary
2869                        self.clearSearch();
2870                    }
2871                });
2872            }
2873        },
2874
2875        // multi
2876        clearSearch: function () {
2877            var placeholder = this.getPlaceholder(),
2878                maxWidth = this.getMaxSearchWidth();
2879
2880            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2881                this.search.val(placeholder).addClass("select2-default");
2882                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2883                // 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
2884                this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
2885            } else {
2886                this.search.val("").width(10);
2887            }
2888        },
2889
2890        // multi
2891        clearPlaceholder: function () {
2892            if (this.search.hasClass("select2-default")) {
2893                this.search.val("").removeClass("select2-default");
2894            }
2895        },
2896
2897        // multi
2898        opening: function () {
2899            this.clearPlaceholder(); // should be done before super so placeholder is not used to search
2900            this.resizeSearch();
2901
2902            this.parent.opening.apply(this, arguments);
2903
2904            this.focusSearch();
2905
2906            // initializes search's value with nextSearchTerm (if defined by user)
2907            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
2908            if(this.search.val() === "") {
2909                if(this.nextSearchTerm != undefined){
2910                    this.search.val(this.nextSearchTerm);
2911                    this.search.select();
2912                }
2913            }
2914
2915            this.updateResults(true);
2916            if (this.opts.shouldFocusInput(this)) {
2917                this.search.focus();
2918            }
2919            this.opts.element.trigger($.Event("select2-open"));
2920        },
2921
2922        // multi
2923        close: function () {
2924            if (!this.opened()) return;
2925            this.parent.close.apply(this, arguments);
2926        },
2927
2928        // multi
2929        focus: function () {
2930            this.close();
2931            this.search.focus();
2932        },
2933
2934        // multi
2935        isFocused: function () {
2936            return this.search.hasClass("select2-focused");
2937        },
2938
2939        // multi
2940        updateSelection: function (data) {
2941            var ids = [], filtered = [], self = this;
2942
2943            // filter out duplicates
2944            $(data).each(function () {
2945                if (indexOf(self.id(this), ids) < 0) {
2946                    ids.push(self.id(this));
2947                    filtered.push(this);
2948                }
2949            });
2950            data = filtered;
2951
2952            this.selection.find(".select2-search-choice").remove();
2953            $(data).each(function () {
2954                self.addSelectedChoice(this);
2955            });
2956            self.postprocessResults();
2957        },
2958
2959        // multi
2960        tokenize: function() {
2961            var input = this.search.val();
2962            input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
2963            if (input != null && input != undefined) {
2964                this.search.val(input);
2965                if (input.length > 0) {
2966                    this.open();
2967                }
2968            }
2969
2970        },
2971
2972        // multi
2973        onSelect: function (data, options) {
2974
2975            if (!this.triggerSelect(data) || data.text === "") { return; }
2976
2977            this.addSelectedChoice(data);
2978
2979            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2980
2981            // keep track of the search's value before it gets cleared
2982            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2983
2984            this.clearSearch();
2985            this.updateResults();
2986
2987            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
2988
2989            if (this.opts.closeOnSelect) {
2990                this.close();
2991                this.search.width(10);
2992            } else {
2993                if (this.countSelectableResults()>0) {
2994                    this.search.width(10);
2995                    this.resizeSearch();
2996                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
2997                        // if we reached max selection size repaint the results so choices
2998                        // are replaced with the max selection reached message
2999                        this.updateResults(true);
3000                    } else {
3001                        // initializes search's value with nextSearchTerm and update search result
3002                        if(this.nextSearchTerm != undefined){
3003                            this.search.val(this.nextSearchTerm);
3004                            this.updateResults();
3005                            this.search.select();
3006                        }
3007                    }
3008                    this.positionDropdown();
3009                } else {
3010                    // if nothing left to select close
3011                    this.close();
3012                    this.search.width(10);
3013                }
3014            }
3015
3016            // since its not possible to select an element that has already been
3017            // added we do not need to check if this is a new element before firing change
3018            this.triggerChange({ added: data });
3019
3020            if (!options || !options.noFocus)
3021                this.focusSearch();
3022        },
3023
3024        // multi
3025        cancel: function () {
3026            this.close();
3027            this.focusSearch();
3028        },
3029
3030        addSelectedChoice: function (data) {
3031            var enableChoice = !data.locked,
3032                enabledItem = $(
3033                    "<li class='select2-search-choice'>" +
3034                    "    <div></div>" +
3035                    "    <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
3036                    "</li>"),
3037                disabledItem = $(
3038                    "<li class='select2-search-choice select2-locked'>" +
3039                    "<div></div>" +
3040                    "</li>");
3041            var choice = enableChoice ? enabledItem : disabledItem,
3042                id = this.id(data),
3043                val = this.getVal(),
3044                formatted,
3045                cssClass;
3046
3047            formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
3048            if (formatted != undefined) {
3049                choice.find("div").replaceWith("<div>"+formatted+"</div>");
3050            }
3051            cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
3052            if (cssClass != undefined) {
3053                choice.addClass(cssClass);
3054            }
3055
3056            if(enableChoice){
3057              choice.find(".select2-search-choice-close")
3058                  .on("mousedown", killEvent)
3059                  .on("click dblclick", this.bind(function (e) {
3060                  if (!this.isInterfaceEnabled()) return;
3061
3062                  this.unselect($(e.target));
3063                  this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
3064                  killEvent(e);
3065                  this.close();
3066                  this.focusSearch();
3067              })).on("focus", this.bind(function () {
3068                  if (!this.isInterfaceEnabled()) return;
3069                  this.container.addClass("select2-container-active");
3070                  this.dropdown.addClass("select2-drop-active");
3071              }));
3072            }
3073
3074            choice.data("select2-data", data);
3075            choice.insertBefore(this.searchContainer);
3076
3077            val.push(id);
3078            this.setVal(val);
3079        },
3080
3081        // multi
3082        unselect: function (selected) {
3083            var val = this.getVal(),
3084                data,
3085                index;
3086            selected = selected.closest(".select2-search-choice");
3087
3088            if (selected.length === 0) {
3089                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
3090            }
3091
3092            data = selected.data("select2-data");
3093
3094            if (!data) {
3095                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
3096                // and invoked on an element already removed
3097                return;
3098            }
3099
3100            var evt = $.Event("select2-removing");
3101            evt.val = this.id(data);
3102            evt.choice = data;
3103            this.opts.element.trigger(evt);
3104
3105            if (evt.isDefaultPrevented()) {
3106                return false;
3107            }
3108
3109            while((index = indexOf(this.id(data), val)) >= 0) {
3110                val.splice(index, 1);
3111                this.setVal(val);
3112                if (this.select) this.postprocessResults();
3113            }
3114
3115            selected.remove();
3116
3117            this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
3118            this.triggerChange({ removed: data });
3119
3120            return true;
3121        },
3122
3123        // multi
3124        postprocessResults: function (data, initial, noHighlightUpdate) {
3125            var val = this.getVal(),
3126                choices = this.results.find(".select2-result"),
3127                compound = this.results.find(".select2-result-with-children"),
3128                self = this;
3129
3130            choices.each2(function (i, choice) {
3131                var id = self.id(choice.data("select2-data"));
3132                if (indexOf(id, val) >= 0) {
3133                    choice.addClass("select2-selected");
3134                    // mark all children of the selected parent as selected
3135                    choice.find(".select2-result-selectable").addClass("select2-selected");
3136                }
3137            });
3138
3139            compound.each2(function(i, choice) {
3140                // hide an optgroup if it doesn't have any selectable children
3141                if (!choice.is('.select2-result-selectable')
3142                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
3143                    choice.addClass("select2-selected");
3144                }
3145            });
3146
3147            if (this.highlight() == -1 && noHighlightUpdate !== false){
3148                self.highlight(0);
3149            }
3150
3151            //If all results are chosen render formatNoMatches
3152            if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
3153                if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
3154                    if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
3155                        this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "</li>");
3156                    }
3157                }
3158            }
3159
3160        },
3161
3162        // multi
3163        getMaxSearchWidth: function() {
3164            return this.selection.width() - getSideBorderPadding(this.search);
3165        },
3166
3167        // multi
3168        resizeSearch: function () {
3169            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
3170                sideBorderPadding = getSideBorderPadding(this.search);
3171
3172            minimumWidth = measureTextWidth(this.search) + 10;
3173
3174            left = this.search.offset().left;
3175
3176            maxWidth = this.selection.width();
3177            containerLeft = this.selection.offset().left;
3178
3179            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
3180
3181            if (searchWidth < minimumWidth) {
3182                searchWidth = maxWidth - sideBorderPadding;
3183            }
3184
3185            if (searchWidth < 40) {
3186                searchWidth = maxWidth - sideBorderPadding;
3187            }
3188
3189            if (searchWidth <= 0) {
3190              searchWidth = minimumWidth;
3191            }
3192
3193            this.search.width(Math.floor(searchWidth));
3194        },
3195
3196        // multi
3197        getVal: function () {
3198            var val;
3199            if (this.select) {
3200                val = this.select.val();
3201                return val === null ? [] : val;
3202            } else {
3203                val = this.opts.element.val();
3204                return splitVal(val, this.opts.separator);
3205            }
3206        },
3207
3208        // multi
3209        setVal: function (val) {
3210            var unique;
3211            if (this.select) {
3212                this.select.val(val);
3213            } else {
3214                unique = [];
3215                // filter out duplicates
3216                $(val).each(function () {
3217                    if (indexOf(this, unique) < 0) unique.push(this);
3218                });
3219                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
3220            }
3221        },
3222
3223        // multi
3224        buildChangeDetails: function (old, current) {
3225            var current = current.slice(0),
3226                old = old.slice(0);
3227
3228            // remove intersection from each array
3229            for (var i = 0; i < current.length; i++) {
3230                for (var j = 0; j < old.length; j++) {
3231                    if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
3232                        current.splice(i, 1);
3233                        if(i>0){
3234                        	i--;
3235                        }
3236                        old.splice(j, 1);
3237                        j--;
3238                    }
3239                }
3240            }
3241
3242            return {added: current, removed: old};
3243        },
3244
3245
3246        // multi
3247        val: function (val, triggerChange) {
3248            var oldData, self=this;
3249
3250            if (arguments.length === 0) {
3251                return this.getVal();
3252            }
3253
3254            oldData=this.data();
3255            if (!oldData.length) oldData=[];
3256
3257            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3258            if (!val && val !== 0) {
3259                this.opts.element.val("");
3260                this.updateSelection([]);
3261                this.clearSearch();
3262                if (triggerChange) {
3263                    this.triggerChange({added: this.data(), removed: oldData});
3264                }
3265                return;
3266            }
3267
3268            // val is a list of ids
3269            this.setVal(val);
3270
3271            if (this.select) {
3272                this.opts.initSelection(this.select, this.bind(this.updateSelection));
3273                if (triggerChange) {
3274                    this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3275                }
3276            } else {
3277                if (this.opts.initSelection === undefined) {
3278                    throw new Error("val() cannot be called if initSelection() is not defined");
3279                }
3280
3281                this.opts.initSelection(this.opts.element, function(data){
3282                    var ids=$.map(data, self.id);
3283                    self.setVal(ids);
3284                    self.updateSelection(data);
3285                    self.clearSearch();
3286                    if (triggerChange) {
3287                        self.triggerChange(self.buildChangeDetails(oldData, self.data()));
3288                    }
3289                });
3290            }
3291            this.clearSearch();
3292        },
3293
3294        // multi
3295        onSortStart: function() {
3296            if (this.select) {
3297                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3298            }
3299
3300            // collapse search field into 0 width so its container can be collapsed as well
3301            this.search.width(0);
3302            // hide the container
3303            this.searchContainer.hide();
3304        },
3305
3306        // multi
3307        onSortEnd:function() {
3308
3309            var val=[], self=this;
3310
3311            // show search and move it to the end of the list
3312            this.searchContainer.show();
3313            // make sure the search container is the last item in the list
3314            this.searchContainer.appendTo(this.searchContainer.parent());
3315            // since we collapsed the width in dragStarted, we resize it here
3316            this.resizeSearch();
3317
3318            // update selection
3319            this.selection.find(".select2-search-choice").each(function() {
3320                val.push(self.opts.id($(this).data("select2-data")));
3321            });
3322            this.setVal(val);
3323            this.triggerChange();
3324        },
3325
3326        // multi
3327        data: function(values, triggerChange) {
3328            var self=this, ids, old;
3329            if (arguments.length === 0) {
3330                 return this.selection
3331                     .children(".select2-search-choice")
3332                     .map(function() { return $(this).data("select2-data"); })
3333                     .get();
3334            } else {
3335                old = this.data();
3336                if (!values) { values = []; }
3337                ids = $.map(values, function(e) { return self.opts.id(e); });
3338                this.setVal(ids);
3339                this.updateSelection(values);
3340                this.clearSearch();
3341                if (triggerChange) {
3342                    this.triggerChange(this.buildChangeDetails(old, this.data()));
3343                }
3344            }
3345        }
3346    });
3347
3348    $.fn.select2 = function () {
3349
3350        var args = Array.prototype.slice.call(arguments, 0),
3351            opts,
3352            select2,
3353            method, value, multiple,
3354            allowedMethods = ["val", "destroy", "opened", "open", "close", "focu
3354s", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3355            valueMethods = ["opened", "isFocused", "container", "dropdown"],
3356            propertyMethods = ["val", "data"],
3357            methodsMap = { search: "externalSearch" };
3358
3359        this.each(function () {
3360            if (args.length === 0 || typeof(args[0]) === "object") {
3361                opts = args.length === 0 ? {} : $.extend({}, args[0]);
3362                opts.element = $(this);
3363
3364                if (opts.element.get(0).tagName.toLowerCase() === "select") {
3365                    multiple = opts.element.prop("multiple");
3366                } else {
3367                    multiple = opts.multiple || false;
3368                    if ("tags" in opts) {opts.multiple = multiple = true;}
3369                }
3370
3371                select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
3372                select2.init(opts);
3373            } else if (typeof(args[0]) === "string") {
3374
3375                if (indexOf(args[0], allowedMethods) < 0) {
3376                    throw "Unknown method: " + args[0];
3377                }
3378
3379                value = undefined;
3380                select2 = $(this).data("select2");
3381                if (select2 === undefined) return;
3382
3383                method=args[0];
3384
3385                if (method === "container") {
3386                    value = select2.container;
3387                } else if (method === "dropdown") {
3388                    value = select2.dropdown;
3389                } else {
3390                    if (methodsMap[method]) method = methodsMap[method];
3391
3392                    value = select2[method].apply(select2, args.slice(1));
3393                }
3394                if (indexOf(args[0], valueMethods) >= 0
3395                    || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
3396                    return false; // abort the iteration, ready to return first matched value
3397                }
3398            } else {
3399                throw "Invalid arguments to select2 plugin: " + args;
3400            }
3401        });
3402        return (value === undefined) ? this : value;
3403    };
3404
3405    // plugin defaults, accessible to users
3406    $.fn.select2.defaults = {
3407        width: "copy",
3408        loadMorePadding: 0,
3409        closeOnSelect: true,
3410        openOnEnter: true,
3411        containerCss: {},
3412        dropdownCss: {},
3413        containerCssClass: "",
3414        dropdownCssClass: "",
3415        formatResult: function(result, container, query, escapeMarkup) {
3416            var markup=[];
3417            markMatch(result.text, query.term, markup, escapeMarkup);
3418            return markup.join("");
3419        },
3420        formatSelection: function (data, container, escapeMarkup) {
3421            return data ? escapeMarkup(data.text) : undefined;
3422        },
3423        sortResults: function (results, container, query) {
3424            return results;
3425        },
3426        formatResultCssClass: function(data) {return data.css;},
3427        formatSelectionCssClass: function(data, container) {return undefined;},
3428        minimumResultsForSearch: 0,
3429        minimumInputLength: 0,
3430        maximumInputLength: null,
3431        maximumSelectionSize: 0,
3432        id: function (e) { return e == undefined ? null : e.id; },
3433        matcher: function(term, text) {
3434            return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3435        },
3436        separator: ",",
3437        tokenSeparators: [],
3438        tokenizer: defaultTokenizer,
3439        escapeMarkup: defaultEscapeMarkup,
3440        blurOnChange: false,
3441        selectOnBlur: false,
3442        adaptContainerCssClass: function(c) { return c; },
3443        adaptDropdownCssClass: function(c) { return null; },
3444        nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
3445        searchInputPlaceholder: '',
3446        createSearchChoicePosition: 'top',
3447        shouldFocusInput: function (instance) {
3448            // Attempt to detect touch devices
3449            var supportsTouchEvents = (('ontouchstart' in window) ||
3450                                       (navigator.msMaxTouchPoints > 0));
3451
3452            // Only devices which support touch events should be special cased
3453            if (!supportsTouchEvents) {
3454                return true;
3455            }
3456
3457            // Never focus the input if search is disabled
3458            if (instance.opts.minimumResultsForSearch < 0) {
3459                return false;
3460            }
3461
3462            return true;
3463        }
3464    };
3465
3466    $.fn.select2.locales = [];
3467
3468    $.fn.select2.locales['en'] = {
3469         formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; },
3470         formatNoMatches: function () { return "No matches found"; },
3471         formatAjaxError: function (jqXHR, textStatus, errorThrown) { return "Loading failed"; },
3472         formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); },
3473         formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
3474         formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3475         formatLoadMore: function (pageNumber) { return "Loading more results…"; },
3476         formatSearching: function () { return "Searching…"; },
3477    };
3478
3479    $.extend($.fn.select2.defaults, $.fn.select2.locales['en']);
3480
3481    $.fn.select2.ajaxDefaults = {
3482        transport: $.ajax,
3483        params: {
3484            type: "GET",
3485            cache: false,
3486            dataType: "json"
3487        }
3488    };
3489
3490    // exports
3491    window.Select2 = {
3492        query: {
3493            ajax: ajax,
3494            local: local,
3495            tags: tags
3496        }, util: {
3497            debounce: debounce,
3498            markMatch: markMatch,
3499            escapeMarkup: defaultEscapeMarkup,
3500            stripDiacritics: stripDiacritics
3501        }, "class": {
3502            "abstract": AbstractSelect2,
3503            "single": SingleSelect2,
3504            "multi": MultiSelect2
3505        }
3506    };
3507
3508}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.