PageSourceSearch

https://duma72.ru/bitrix/templates/new_main/js/select2.js?1661519025135583

js duma72.ru collected 2026-10-01 08:56:44 UTC 135,583 bytes, 3,251 lines download raw bytes

1/*
2Copyright 2012 Igor Vaynberg
3
4Version: 3.4.4 Timestamp: Thu Oct 24 13:23:11 PDT 2013
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 Licesnse 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"};
102
103    $document = $(document);
104
105    nextUid=(function() { var counter=1; return function() { return counter++; }; }());
106
107
108    function stripDiacritics(str) {
109        var ret, i, l, c;
110
111        if (!str || str.length < 1) return str;
112
113        ret = "";
114        for (i = 0, l = str.length; i < l; i++) {
115            c = str.charAt(i);
116            ret += DIACRITICS[c] || c;
117        }
118        return ret;
119    }
120
121    function indexOf(value, array) {
122        var i = 0, l = array.length;
123        for (; i < l; i = i + 1) {
124            if (equal(value, array[i])) return i;
125        }
126        return -1;
127    }
128
129    function measureScrollbar () {
130        var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
131        $template.appendTo('body');
132
133        var dim = {
134            width: $template.width() - $template[0].clientWidth,
135            height: $template.height() - $template[0].clientHeight
136        };
137        $template.remove();
138
139        return dim;
140    }
141
142    /**
143     * Compares equality of a and b
144     * @param a
145     * @param b
146     */
147    function equal(a, b) {
148        if (a === b) return true;
149        if (a === undefined || b === undefined) return false;
150        if (a === null || b === null) return false;
151        // Check whether 'a' or 'b' is a string (primitive or object).
152        // The concatenation of an empty string (+'') converts its argument to a string's primitive.
153        if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
154        if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
155        return false;
156    }
157
158    /**
159     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
160     * strings
161     * @param string
162     * @param separator
163     */
164    function splitVal(string, separator) {
165        var val, i, l;
166        if (string === null || string.length < 1) return [];
167        val = string.split(separator);
168        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
169        return val;
170    }
171
172    function getSideBorderPadding(element) {
173        return element.outerWidth(false) - element.width();
174    }
175
176    function installKeyUpChangeEvent(element) {
177        var key="keyup-change-value";
178        element.on("keydown", function () {
179            if ($.data(element, key) === undefined) {
180                $.data(element, key, element.val());
181            }
182        });
183        element.on("keyup", function () {
184            var val= $.data(element, key);
185            if (val !== undefined && element.val() !== val) {
186                $.removeData(element, key);
187                element.trigger("keyup-change");
188            }
189        });
190    }
191
192    $document.on("mousemove", function (e) {
193        lastMousePosition.x = e.pageX;
194        lastMousePosition.y = e.pageY;
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    /**
234     * A simple implementation of a thunk
235     * @param formula function used to lazily initialize the thunk
236     * @return {Function}
237     */
238    function thunk(formula) {
239        var evaluated = false,
240            value;
241        return function() {
242            if (evaluated === false) { value = formula(); evaluated = true; }
243            return value;
244        };
245    };
246
247    function installDebouncedScroll(threshold, element) {
248        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
249        element.on("scroll", function (e) {
250            if (indexOf(e.target, element.get()) >= 0) notify(e);
251        });
252    }
253
254    function focus($el) {
255        if ($el[0] === document.activeElement) return;
256
257        /* set the focus in a 0 timeout - that way the focus is set after the processing
258            of the current event has finished - which seems like the only reliable way
259            to set focus */
260        window.setTimeout(function() {
261            var el=$el[0], pos=$el.val().length, range;
262
263            $el.focus();
264
265            /* make sure el received focus so we do not error out when trying to manipulate the caret.
266                sometimes modals or others listeners may steal it after its set */
267            if ($el.is(":visible") && el === document.activeElement) {
268
269                /* after the focus is set move the caret to the end, necessary when we val()
270                    just before setting focus */
271                if(el.setSelectionRange)
272                {
273                    el.setSelectionRange(pos, pos);
274                }
275                else if (el.createTextRange) {
276                    range = el.createTextRange();
277                    range.collapse(false);
278                    range.select();
279                }
280            }
281        }, 0);
282    }
283
284    function getCursorInfo(el) {
285        el = $(el)[0];
286        var offset = 0;
287        var length = 0;
288        if ('selectionStart' in el) {
289            offset = el.selectionStart;
290            length = el.selectionEnd - offset;
291        } else if ('selection' in document) {
292            el.focus();
293            var sel = document.selection.createRange();
294            length = document.selection.createRange().text.length;
295            sel.moveStart('character', -el.value.length);
296            offset = sel.text.length - length;
297        }
298        return { offset: offset, length: length };
299    }
300
301    function killEvent(event) {
302        event.preventDefault();
303        event.stopPropagation();
304    }
305    function killEventImmediately(event) {
306        event.preventDefault();
307        event.stopImmediatePropagation();
308    }
309
310    function measureTextWidth(e) {
311        if (!sizer){
312            var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
313            sizer = $(document.createElement("div")).css({
314                position: "absolute",
315                left: "-10000px",
316                top: "-10000px",
317                display: "none",
318                fontSize: style.fontSize,
319                fontFamily: style.fontFamily,
320                fontStyle: style.fontStyle,
321                fontWeight: style.fontWeight,
322                letterSpacing: style.letterSpacing,
323                textTransform: style.textTransform,
324                whiteSpace: "nowrap"
325            });
326            sizer.attr("class","select2-sizer");
327            $("body").append(sizer);
328        }
329        sizer.text(e.val());
330        return sizer.width();
331    }
332
333    function syncCssClasses(dest, src, adapter) {
334        var classes, replacements = [], adapted;
335
336        classes = dest.attr("class");
337        if (classes) {
338            classes = '' + classes; // for IE which returns object
339            $(classes.split(" ")).each2(function() {
340                if (this.indexOf("select2-") === 0) {
341                    replacements.push(this);
342                }
343            });
344        }
345        classes = src.attr("class");
346        if (classes) {
347            classes = '' + classes; // for IE which returns object
348            $(classes.split(" ")).each2(function() {
349                if (this.indexOf("select2-") !== 0) {
350                    adapted = adapter(this);
351                    if (adapted) {
352                        replacements.push(adapted);
353                    }
354                }
355            });
356        }
357        dest.attr("class", replacements.join(" "));
358    }
359
360
361    function markMatch(text, term, markup, escapeMarkup) {
362        var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
363            tl=term.length;
364
365        if (match<0) {
366            markup.push(escapeMarkup(text));
367            return;
368        }
369
370        markup.push(escapeMarkup(text.substring(0, match)));
371        markup.push("<span class='select2-match'>");
372        markup.push(escapeMarkup(text.substring(match, match + tl)));
373        markup.push("</span>");
374        markup.push(escapeMarkup(text.substring(match + tl, text.length)));
375    }
376
377    function defaultEscapeMarkup(markup) {
378        var replace_map = {
379            '\\': '&#92;',
380            '&': '&amp;',
381            '<': '&lt;',
382            '>': '&gt;',
383            '"': '&quot;',
384            "'": '&#39;',
385            "/": '&#47;'
386        };
387
388        return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
389            return replace_map[match];
390        });
391    }
392
393    /**
394     * Produces an ajax-based query function
395     *
396     * @param options object containing configuration paramters
397     * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
398     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
399     * @param options.url url for the data
400     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
401     * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
402     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
403     * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
404     *      The expected format is an object c
404ontaining the following keys:
405     *      results array of objects that will be used as choices
406     *      more (optional) boolean indicating whether there are more results available
407     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
408     */
409    function ajax(options) {
410        var timeout, // current scheduled but not yet executed request
411            handler = null,
412            quietMillis = options.quietMillis || 100,
413            ajaxUrl = options.url,
414            self = this;
415
416        return function (query) {
417            window.clearTimeout(timeout);
418            timeout = window.setTimeout(function () {
419                var data = options.data, // ajax data function
420                    url = ajaxUrl, // ajax url string or function
421                    transport = options.transport || $.fn.select2.ajaxDefaults.transport,
422                    // deprecated - to be removed in 4.0  - use params instead
423                    deprecated = {
424                        type: options.type || 'GET', // set type of request (GET or POST)
425                        cache: options.cache || false,
426                        jsonpCallback: options.jsonpCallback||undefined,
427                        dataType: options.dataType||"json"
428                    },
429                    params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
430
431                data = data ? data.call(self, query.term, query.page, query.context) : null;
432                url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
433
434                if (handler) { handler.abort(); }
435
436                if (options.params) {
437                    if ($.isFunction(options.params)) {
438                        $.extend(params, options.params.call(self));
439                    } else {
440                        $.extend(params, options.params);
441                    }
442                }
443
444                $.extend(params, {
445                    url: url,
446                    dataType: options.dataType,
447                    data: data,
448                    success: function (data) {
449                        // TODO - replace query.page with query so users have access to term, page, etc.
450                        var results = options.results(data, query.page);
451                        query.callback(results);
452                    }
453                });
454                handler = transport.call(self, params);
455            }, quietMillis);
456        };
457    }
458
459    /**
460     * Produces a query function that works with a local array
461     *
462     * @param options object containing configuration parameters. The options parameter can either be an array or an
463     * object.
464     *
465     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
466     *
467     * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
468     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
469     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
470     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
471     * the text.
472     */
473    function local(options) {
474        var data = options, // data elements
475            dataText,
476            tmp,
477            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
478
479         if ($.isArray(data)) {
480            tmp = data;
481            data = { results: tmp };
482        }
483
484         if ($.isFunction(data) === false) {
485            tmp = data;
486            data = function() { return tmp; };
487        }
488
489        var dataItem = data();
490        if (dataItem.text) {
491            text = dataItem.text;
492            // if text is not a function we assume it to be a key name
493            if (!$.isFunction(text)) {
494                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
495                text = function (item) { return item[dataText]; };
496            }
497        }
498
499        return function (query) {
500            var t = query.term, filtered = { results: [] }, process;
501            if (t === "") {
502                query.callback(data());
503                return;
504            }
505
506            process = function(datum, collection) {
507                var group, attr;
508                datum = datum[0];
509                if (datum.children) {
510                    group = {};
511                    for (attr in datum) {
512                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
513                    }
514                    group.children=[];
515                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
516                    if (group.children.length || query.matcher(t, text(group), datum)) {
517                        collection.push(group);
518                    }
519                } else {
520                    if (query.matcher(t, text(datum), datum)) {
521                        collection.push(datum);
522                    }
523                }
524            };
525
526            $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
527            query.callback(filtered);
528        };
529    }
530
531    // TODO javadoc
532    function tags(data) {
533        var isFunc = $.isFunction(data);
534        return function (query) {
535            var t = query.term, filtered = {results: []};
536            $(isFunc ? data() : data).each(function () {
537                var isObject = this.text !== undefined,
538                    text = isObject ? this.text : this;
539                if (t === "" || query.matcher(t, text)) {
540                    filtered.results.push(isObject ? this : {id: this, text: this});
541                }
542            });
543            query.callback(filtered);
544        };
545    }
546
547    /**
548     * Checks if the formatter function should be used.
549     *
550     * Throws an error if it is not a function. Returns true if it should be used,
551     * false if no formatting should be performed.
552     *
553     * @param formatter
554     */
555    function checkFormatter(formatter, formatterName) {
556        if ($.isFunction(formatter)) return true;
557        if (!formatter) return false;
558        throw new Error(formatterName +" must be a function or a falsy value");
559    }
560
561    function evaluate(val) {
562        return $.isFunction(val) ? val() : val;
563    }
564
565    function countResults(results) {
566        var count = 0;
567        $.each(results, function(i, item) {
568            if (item.children) {
569                count += countResults(item.children);
570            } else {
571                count++;
572            }
573        });
574        return count;
575    }
576
577    /**
578     * Default tokenizer. This function uses breaks the input on substring match of any string from the
579     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
580     * two options have to be defined in order for the tokenizer to work.
581     *
582     * @param input text user has typed so far or pasted into the search field
583     * @param selection currently selected choices
584     * @param selectCallback function(choice) callback tho add the choice to selection
585     * @param opts select2's opts
586     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
587     */
588    function defaultTokenizer(input, selection, selectCallback, opts) {
589        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
590            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
591            token, // token
592            index, // position at which the separator was found
593            i, l, // looping variables
594            separator; // the matched separator
595
596        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
597
598        while (true) {
599            index = -1;
600
601            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
602                separator = opts.tokenSeparators[i];
603                index = input.indexOf(separator);
604                if (index >= 0) break;
605            }
606
607            if (index < 0) break; // did not find any token separator in the input string, bail
608
609            token = input.substring(0, index);
610            input = input.substring(index + separator.length);
611
612            if (token.length > 0) {
613                token = opts.createSearchChoice.call(this, token, selection);
614                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
615                    dupe = false;
616                    for (i = 0, l = selection.length; i < l; i++) {
617                        if (equal(opts.id(token), opts.id(selection[i]))) {
618                            dupe = true; break;
619                        }
620                    }
621
622                    if (!dupe) selectCallback(token);
623                }
624            }
625        }
626
627        if (original!==input) return input;
628    }
629
630    /**
631     * Creates a new class
632     *
633     * @param superClass
634     * @param methods
635     */
636    function clazz(SuperClass, methods) {
637        var constructor = function () {};
638        constructor.prototype = new SuperClass;
639        constructor.prototype.constructor = constructor;
640        constructor.prototype.parent = SuperClass.prototype;
641        constructor.prototype = $.extend(constructor.prototype, methods);
642        return constructor;
643    }
644
645    AbstractSelect2 = clazz(Object, {
646
647        // abstract
648        bind: function (func) {
649            var self = this;
650            return function () {
651                func.apply(self, arguments);
652            };
653        },
654
655        // abstract
656        init: function (opts) {
657            var results, search, resultsSelector = ".select2-results";
658
659            // prepare options
660            this.opts = opts = this.prepareOpts(opts);
661
662            this.id=opts.id;
663
664            // destroy if called on an existing component
665            if (opts.element.data("select2") !== undefined &&
666                opts.element.data("select2") !== null) {
667                opts.element.data("select2").destroy();
668            }
669
670            this.container = this.createContainer();
671
672            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
673            this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
674            this.container.attr("id", this.containerId);
675
676            // cache the body so future lookups are cheap
677            this.body = thunk(function() { return opts.element.closest("body"); });
678
679            syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
680
681            this.container.attr("style", opts.element.attr("style"));
682            this.container.css(evaluate(opts.containerCss));
683            this.container.addClass(evaluate(opts.containerCssClass));
684
685            this.elementTabIndex = this.opts.element.attr("tabindex");
686
687            // swap container for the element
688            this.opts.element
689                .data("select2", this)
690                .attr("tabindex", "-1")
691                .before(this.container)
692                .on("click.select2", killEvent); // do not leak click events
693
694            this.container.data("select2", this);
695
696            this.dropdown = this.container.find(".select2-drop");
697
698            syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
699
700            this.dropdown.addClass(evaluate(opts.dropdownCssClass));
701            this.dropdown.data("select2", this);
702            this.dropdown.on("click", killEvent);
703
704            this.results = results = this.container.find(resultsSelector);
705            this.search = search = this.container.find("input.select2-input");
706
707            this.queryCount = 0;
708            this.resultsPage = 0;
709            this.context = null;
710
711            // initialize the container
712            this.initContainer();
713
714            this.container.on("click", killEvent);
715
716            installFilteredMouseMove(this.results);
717            this.dropdown.on("mousemove-filtered touchstart touchmove touchend", resultsSelector, this.bind(this.highlightUnderEvent));
718
719            installDebouncedScroll(80, this.results);
720            this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
721
722            // do not propagate change event from the search field out of the component
723            $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
724            $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
725
726            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
727            if ($.fn.mousewheel) {
728                results.mousewheel(function (e, delta, deltaX, deltaY) {
729                    var top = results.scrollTop();
730                    if (deltaY > 0 && top - deltaY <= 0) {
731                        results.scrollTop(0);
732                        killEvent(e);
733                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
734                        results.scrollTop(results.get(0).scrollHeight - results.height());
735                        killEvent(e);
736                    }
737                });
738            }
739
740            installKeyUpChangeEvent(search);
741            search.on("keyup-change input paste", this.bind(this.updateResults));
742            search.on("focus", function () { search.addClass("select2-focused"); });
743            search.on("blur", function () { search.removeClass("select2-focused");});
744
745            this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
746                if ($(e.target).closest(".select2-result-selectable").length > 0) {
747                    this.highlightUnderEvent(e);
748                    this.selectHighlighted(e);
749                }
750            }));
751
752            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
753            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
754            // dom it will trigger the popup close, which is not what we want
755            this.dropdown.on("click mouseup mousedown", function (e) { e.stopPropagation(); });
756
757            if ($.isFunction(this.opts.initSelection)) {
758                // initialize selection based on the current value of the source element
759                this.initSelection();
760
761                // if the user has provided a function that can set selection based on the value of the source element
762                // we monitor the change event on the element and trigger it, allowing for two way synchronization
763                this.monitorSource();
764            }
765
766            if (opts.maximumInputLength !== null) {
767                this.search.attr("maxlength", opts.maximumInputLength);
768            }
769
770            var disabled = opts.element.prop("disabled");
771            if (disabled === undefined) disabled = false;
772            this.enable(!disabled);
773
774            var readonly = opts.element.prop("readonly");
775            if (readonly === undefined) readonly = false;
776            this.readonly(readonly);
777
778            // Calculate size of scrollbar
779            scrollBarDimensions = scrollBarDimensions || measureScrollbar();
780
781            this.autofocus = opts.element.prop("autofocus");
782            opts.element.prop("autofocus", false);
783            if (this.autofocus) this.focus();
784
785            this.nextSearchTerm = undefined;
786        },
787
788        // abstract
789        destroy: function () {
790            var element=this.opts.element, select2 = element.data("select2");
791
792            this.close();
793
794            if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
795
796            if (select2 !== undefined) {
797                select2.container.remove();
798                select2.dropdown.remove();
799                element
800                    .removeClass("select2-offscreen")
801                    .removeData("select2")
802                    .off(".select2")
803                    .prop("autofocus", this.autofocus || false);
804                if (this.elementTabIndex) {
805                    element.attr({tabindex: this.elementTabIndex});
806                } else {
807                    element.removeAttr("tabindex");
808                }
809                element.show();
810            }
811        },
812
813        // abstract
814        optionToData: function(element) {
815            if (element.is("option")) {
816                return {
817                    id:element.prop("value"),
818                    text:element.text(),
819                    element: element.get(),
820                    css: element.attr("class"),
821                    disabled: element.prop("disabled"),
822                    locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
823                };
824            } else if (element.is("optgroup")) {
825                return {
826                    text:element.attr("label"),
827                    children:[],
828                    element: element.get(),
829                    css: element.attr("class")
830                };
831            }
832        },
833
834        // abstract
835        prepareOpts: function (opts) {
836            var element, select, idKey, ajaxUrl, self = this;
837
838            element = opts.element;
839
840            if (element.get(0).tagName.toLowerCase() === "select") {
841                this.select = select = opts.element;
842            }
843
844            if (select) {
845                // these options are not allowed when attached to a select because they are picked up off the element itself
846                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
847                    if (this in opts) {
848                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
849                    }
850                });
851            }
852
853            opts = $.extend({}, {
854                populateResults: function(container, results, query) {
855                    var populate, id=this.opts.id;
856
857                    populate=function(results, container, depth) {
858
859                        var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
860
861                        results = opts.sortResults(results, container, query);
862
863                        for (i = 0, l = results.length; i < l; i = i + 1) {
864
865                            result=results[i];
866
867                            disabled = (result.disabled === true);
868                            selectable = (!disabled) && (id(result) !== undefined);
869
870                            compound=result.children && result.children.length > 0;
871
872                            node=$("<li></li>");
873                            node.addClass("select2-results-dept-"+depth);
874                            node.addClass("select2-result");
875                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
876                            if (disabled) { node.addClass("select2-disabled"); }
877                            if (compound) { node.addClass("select2-result-with-children"); }
878                            node.addClass(self.opts.formatResultCssClass(result));
879
880                            label=$(document.createElement("div"));
881                            label.addClass("select2-result-label");
882
883                            formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
884                            if (formatted!==undefined) {
885                                label.html(formatted);
886                            }
887
888                            node.append(label);
889
890                            if (compound) {
891
892                                innerContainer=$("<ul></ul>");
893                                innerContainer.addClass("select2-result-sub");
894                                populate(result.children, innerContainer, depth+1);
895                                node.append(innerContainer);
896                            }
897
898                            node.data("select2-data", result);
899                            container.append(node);
900                        }
901                    };
902
903                    populate(results, container, 0);
904                }
905            }, $.fn.select2.defaults, opts);
906
907            if (typeof(opts.id) !== "function") {
908                idKey = opts.id;
909                opts.id = function (e) { return e[idKey]; };
910            }
911
912            if ($.isArray(opts.element.data("select2Tags"))) {
913                if ("tags" in opts) {
914                    throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
915                }
916                opts.tags=opts.element.data("select2Tags");
917            }
918
919            if (select) {
920                opts.query = this.bind(function (query) {
921                    var data = { results: [], more: false },
922                        term = query.term,
923                        children, placeholderOption, process;
924
925                    process=function(element, collection) {
926                        var group;
927                        if (element.is("option")) {
928                            if (query.matcher(term, element.text(), element)) {
929                                collection.push(self.optionToData(element));
930                            }
931                        } else if (element.is("optgroup")) {
932                            group=self.optionToData(element);
933                            element.children().each2(function(i, elm) { process(elm, group.children); });
934                            if (group.children.length>0) {
935                                collection.push(group);
936                            }
937                        }
938                    };
939
940                    children=element.children();
941
942                    // ignore the placeholder option if there is one
943                    if (this.getPlaceholder() !== undefined && children.length > 0) {
944                        placeholderOption = this.getPlaceholderOption();
945                        if (placeholderOption) {
946                            children=children.not(placeholderOption);
947                        }
948                    }
949
950                    children.each2(function(i, elm) { process(elm, data.results); });
951
952                    query.callback(data);
953                });
954                // this is needed because inside val() we construct choices from options and there id is hardcoded
955                opts.id=function(e) { return e.id; };
956                opts.formatResultCssClass = function(data) { return data.css; };
957            } else {
958                if (!("query" in opts)) {
959
960                    if ("ajax" in opts) {
961                        ajaxUrl = opts.element.data("ajax-url");
962                        if (ajaxUrl && ajaxUrl.length > 0) {
963                            opts.ajax.url = ajaxUrl;
964                        }
965                        opts.query = ajax.call(opts.element, opts.ajax);
966                    } else if ("data" in opts) {
967                        opts.query = local(opts.data);
968                    } else if ("tags" in opts) {
969                        opts.query = tags(opts.tags);
970                        if (opts.createSearchChoice === undefined) {
971                            opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
972                        }
973                        if (opts.initSelection === undefined) {
974                            opts.initSelection = function (element, callback) {
975                                var data = [];
976                                $(splitVal(element.val(), opts.separator)).each(function () {
977                                    var obj = { id: this, text: this },
978                                        tags = opts.tags;
979                                    if ($.isFunction(tags)) tags=tags();
980                                    $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
981                                    data.push(obj);
982                                });
983
984                                callback(data);
985                            };
986                        }
987                    }
988                }
989            }
990            if (typeof(opts.query) !== "function") {
991                throw "query function not defined for Select2 " + opts.element.attr("id");
992            }
993
994            return opts;
995        },
996
997        /**
998         * Monitor the original element for changes and update select2 accordingly
999         */
1000        // abstract
1001        monitorSource: function () {
1002            var el = this.opts.element, sync, observer;
1003
1004            el.on("change.select2", this.bind(function (e) {
1005                if (this.opts.element.data("select2-change-triggered") !== true) {
1006                    this.initSelection();
1007                }
1008            }));
1009
1010            sync = this.bind(function () {
1011
1012                // sync enabled state
1013                var disabled = el.prop("disabled");
1014                if (disabled === undefined) disabled = false;
1015                this.enable(!disabled);
1016
1017                var readonly = el.prop("readonly");
1018                if (readonly === undefined) readonly = false;
1019                this.readonly(readonly);
1020
1021                syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
1022                this.container.addClass(evaluate(this.opts.containerCssClass));
1023
1024                syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
1025                this.dropdown.addClass(evaluate(this.opts.dropdownCssClass));
1026
1027            });
1028
1029            // IE8-10
1030            el.on("propertychange.select2", sync);
1031
1032            // hold onto a reference of the callback to work around a chromium bug
1033            if (this.mutationCallback === undefined) {
1034                this.mutationCallback = function (mutations) {
1035                    mutations.forEach(sync);
1036                }
1037            }
1038
1039            // safari, chrome, firefox, IE11
1040            observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
1041            if (observer !== undefined) {
1042                if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
1043                this.propertyObserver = new observer(this.mutationCallback);
1044                this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
1045            }
1046        },
1047
1048        // abstract
1049        triggerSelect: function(data) {
1050            var evt = $.Event("select2-selecting", { val: this.id(data), object: data });
1051            this.opts.element.trigger(evt);
1052            return !evt.isDefaultPrevented();
1053        },
1054
1055        /**
1056         * Triggers the change event on the source element
1057         */
1058        // abstract
1059        triggerChange: function (details) {
1060
1061            details = details || {};
1062            details= $.extend({}, details, { type: "change", val: this.val() });
1063            // prevents recursive triggering
1064            this.opts.element.data("select2-change-triggered", true);
1065            this.opts.element.trigger(details);
1066            this.opts.element.data("select2-change-triggered", false);
1067
1068            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
1069            // so here we trigger the click event manually
1070            this.opts.element.click();
1071
1072            // ValidationEngine ignorea the change event and listens instead to blur
1073            // so here we trigger the blur event manually if so desired
1074            if (this.opts.blurOnChange)
1075                this.opts.element.blur();
1076        },
1077
1078        //abstract
1079        isInterfaceEnabled: function()
1080        {
1081            return this.enabledInterface === true;
1082        },
1083
1084        // abstract
1085        enableInterface: function() {
1086            var enabled = this._enabled && !this._readonly,
1087                disabled = !enabled;
1088
1089            if (enabled === this.enabledInterface) return false;
1090
1091            this.container.toggleClass("select2-container-disabled", disabled);
1092            this.close();
1093            this.enabledInterface = enabled;
1094
1095            return true;
1096        },
1097
1098        // abstract
1099        enable: function(enabled) {
1100            if (enabled === undefined) enabled = true;
1101            if (this._enabled === enabled) return;
1102            this._enabled = enabled;
1103
1104            this.opts.element.prop("disabled", !enabled);
1105            this.enableInterface();
1106        },
1107
1108        // abstract
1109        disable: function() {
1110            this.enable(false);
1111        },
1112
1113        // abstract
1114        readonly: function(enabled) {
1115            if (enabled === undefined) enabled = false;
1116            if (this._readonly === enabled) return false;
1117            this._readonly = enabled;
1118
1119            this.opts.element.prop("readonly", enabled);
1120            this.enableInterface();
1121            return true;
1122        },
1123
1124        // abstract
1125        opened: function () {
1126            return this.container.hasClass("select2-dropdown-open");
1127        },
1128
1129        // abstract
1130        positionDropdown: function() {
1131            var $dropdown = this.dropdown,
1132                offset = this.container.offset(),
1133                height = this.container.outerHeight(false),
1134                width = this.container.outerWidth(false),
1135                dropHeight = $dropdown.outerHeight(false),
1136                $window = $(window),
1137                windowWidth = $window.width(),
1138                windowHeight = $window.height(),
1139                viewPortRight = $window.scrollLeft() + windowWidth,
1140                viewportBottom = $window.scrollTop() + windowHeight,
1141                dropTop = offset.top + height,
1142                dropLeft = offset.left,
1143                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
1144                enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
1145                dropWidth = $dropdown.outerWidth(false),
1146                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
1147                aboveNow = $dropdown.hasClass("select2-drop-above"),
1148                bodyOffset,
1149                above,
1150                changeDirection,
1151                css,
1152                resultsListNode;
1153
1154            // always prefer the current above/below alignment, unless there is not enough room
1155            if (aboveNow) {
1156                above = true;
1157                if (!enoughRoomAbove && enoughRoomBelow) {
1158                    changeDirection = true;
1159                    above = false;
1160                }
1161            } else {
1162                above = false;
1163                if (!enoughRoomBelow && enoughRoomAbove) {
1164                    changeDirection = true;
1165                    above = true;
1166                }
1167            }
1168
1169            //if we are changing direction we need to get positions when dropdown is hidden;
1170            if (changeDirection) {
1171                $dropdown.hide();
1172                offset = this.container.offset();
1173                height = this.container.outerHeight(false);
1174                width = this.container.outerWidth(false);
1175                dropHeight = $dropdown.outerHeight(false);
1176                viewPortRight = $window.scrollLeft() + windowWidth;
1177                viewportBottom = $window.scrollTop() + windowHeight;
1178                dropTop = offset.top + height;
1179                dropLeft = offset.left;
1180                dropWidth = $dropdown.outerWidth(false);
1181                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1182                $dropdown.show();
1183            }
1184
1185            if (this.opts.dropdownAutoWidth) {
1186                resultsListNode = $('.select2-results', $dropdown)[0];
1187                $dropdown.addClass('select2-drop-auto-width');
1188                $dropdown.css('width', '');
1189                // Add scrollbar width to dropdown if vertical scrollbar is present
1190                dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
1191                dropWidth > width ? width = dropWidth : dropWidth = width;
1192                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1193            }
1194            else {
1195                this.container.removeClass('select2-drop-auto-width');
1196            }
1197
1198            //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1199            //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
1200
1201            // fix positioning when body has an offset and is not position: static
1202            if (this.body().css('position') !== 'static') {
1203                bodyOffset = this.body().offset();
1204                dropTop -= bodyOffset.top;
1205                dropLeft -= bodyOffset.left;
1206            }
1207
1208            if (!enoughRoomOnRight) {
1209               dropLeft = offset.left + width - dropWidth;
1210            }
1211
1212            css =  {
1213                left: dropLeft,
1214                width: width
1215            };
1216
1217            if (above) {
1218                css.bottom = windowHeight - offset.top;
1219                css.top = 'auto';
1220                this.container.addClass("select2-drop-above");
1221                $dropdown.addClass("select2-drop-above");
1222            }
1223            else {
1224                css.top = dropTop;
1225                css.bottom = 'auto';
1226                this.container.removeClass("select2-drop-above");
1227                $dropdown.removeClass("select2-drop-above");
1228            }
1229            css = $.extend(css, evaluate(this.opts.dropdownCss));
1230
1231            $dropdown.css(css);
1232        },
1233
1234        // abstract
1235        shouldOpen: function() {
1236            var event;
1237
1238            if (this.opened()) return false;
1239
1240            if (this._enabled === false || this._readonly === true) return false;
1241
1242            event = $.Event("select2-opening");
1243            this.opts.element.trigger(event);
1244            return !event.isDefaultPrevented();
1245        },
1246
1247        // abstract
1248        clearDropdownAlignmentPreference: function() {
1249            // clear the classes used to figure out the preference of where the dropdown should be opened
1250            this.container.removeClass("select2-drop-above");
1251            this.dropdown.removeClass("select2-drop-above");
1252        },
1253
1254        /**
1255         * Opens the dropdown
1256         *
1257         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1258         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1259         */
1260        // abstract
1261        open: function () {
1262
1263            if (!this.shouldOpen()) return false;
1264
1265            this.opening();
1266
1267            return true;
1268        },
1269
1270        /**
1271         * Performs the opening of the dropdown
1272         */
1273        // abstract
1274        opening: function() {
1275            var cid = this.containerId,
1276                scroll = "scroll." + cid,
1277                resize = "resize."+cid,
1278                orient = "orientationchange."+cid,
1279                mask;
1280
1281            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1282
1283            this.clearDropdownAlignmentPreference();
1284
1285            if(this.dropdown[0] !== this.body().children().last()[0]) {
1286                this.dropdown.detach().appendTo(this.body());
1287            }
1288
1289            // create the dropdown mask if doesnt already exist
1290            mask = $("#select2-drop-mask");
1291            if (mask.length == 0) {
1292                mask = $(document.createElement("div"));
1293                mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
1294                mask.hide();
1295                mask.appendTo(this.body());
1296                mask.on("mousedown touchstart click", function (e) {
1297                    var dropdown = $("#select2-drop"), self;
1298                    if (dropdown.length > 0) {
1299                        self=dropdown.data("select2");
1300                        if (self.opts.selectOnBlur) {
1301                            self.selectHighlighted({noFocus: true});
1302                        }
1303                        self.close({focus:true});
1304                        e.preventDefault();
1305                        e.stopPropagation();
1306                    }
1307                });
1308            }
1309
1310            // ensure the mask is always right before the dropdown
1311            if (this.dropdown.prev()[0] !== mask[0]) {
1312                this.dropdown.before(mask);
1313            }
1314
1315            // move the global id to the correct dropdown
1316            $("#select2-drop").removeAttr("id");
1317            this.dropdown.attr("id", "select2-drop");
1318
1319            // show the elements
1320            mask.show();
1321
1322            this.positionDropdown();
1323            this.dropdown.show();
1324            this.positionDropdown();
1325
1326            this.dropdown.addClass("select2-drop-active");
1327
1328            // attach listeners to events that can change the position of the container and thus require
1329            // the position of the dropdown to be updated as well so it does not come unglued from the container
1330            var that = this;
1331            this.container.parents().add(window).each(function () {
1332                $(this).on(resize+" "+scroll+" "+orient, function (e) {
1333                    that.positionDropdown();
1334                });
1335            });
1336
1337
1338        },
1339
1340        // abstract
1341        close: function () {
1342            if (!this.opened()) return;
1343
1344            var cid = this.containerId,
1345                scroll = "scroll." + cid,
1346                resize = "resize."+cid,
1347                orient = "orientationchange."+cid;
1348
1349            // unbind event listeners
1350            this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
1351
1352            this.clearDropdownAlignmentPreference();
1353
1354            $("#select2-drop-mask").hide();
1355            this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1356            this.dropdown.hide();
1357            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
1358            this.results.empty();
1359
1360
1361            this.clearSearch();
1362            this.search.removeClass("select2-active");
1363            this.opts.element.trigger($.Event("select2-close"));
1364        },
1365
1366        /**
1367         * Opens control, sets input value, and updates results.
1368         */
1369        // abstract
1370        externalSearch: function (term) {
1371            this.open();
1372            this.search.val(term);
1373            this.updateResults(false);
1374        },
1375
1376        // abstract
1377        clearSearch: function () {
1378
1379        },
1380
1381        //abstract
1382        getMaximumSelectionSize: function() {
1383            return evaluate(this.opts.maximumSelectionSize);
1384        },
1385
1386        // abstract
1387        ensureHighlightVisible: function () {
1388            var results = this.results, children, index, child, hb, rb, y, more;
1389
1390            index = this.highlight();
1391
1392            if (index < 0) return;
1393
1394            if (index == 0) {
1395
1396                // if the first element is highlighted scroll all the way to the top,
1397                // that way any unselectable headers above it will also be scrolled
1398                // into view
1399
1400                results.scrollTop(0);
1401                return;
1402            }
1403
1404            children = this.findHighlightableChoices().find('.select2-result-label');
1405
1406            child = $(children[index]);
1407
1408            hb = child.offset().top + child.outerHeight(true);
1409
1410            // if this is the last child lets also make sure select2-more-results is visible
1411            if (index === children.length - 1) {
1412                more = results.find("li.select2-more-results");
1413                if (more.length > 0) {
1414                    hb = more.offset().top + more.outerHeight(true);
1415                }
1416            }
1417
1418            rb = results.offset().top + results.outerHeight(true);
1419            if (hb > rb) {
1420                results.scrollTop(results.scrollTop() + (hb - rb));
1421            }
1422            y = child.offset().top - results.offset().top;
1423
1424            // make sure the top of the element is visible
1425            if (y < 0 && child.css('display') != 'none' ) {
1426                results.scrollTop(results.scrollTop() + y); // y is negative
1427            }
1428        },
1429
1430        // abstract
1431        findHighlightableChoices: function() {
1432            return this.results.find(".select2-result-selectable:not(.select2-disabled, .select2-selected)");
1433        },
1434
1435        // abstract
1436        moveHighlight: function (delta) {
1437            var choices = this.findHighlightableChoices(),
1438                index = this.highlight();
1439
1440            while (index > -1 && index < choices.length) {
1441                index += delta;
1442                var choice = $(choices[index]);
1443                if (choice.hasClass("select2-result-selectable") && !c
1443hoice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1444                    this.highlight(index);
1445                    break;
1446                }
1447            }
1448        },
1449
1450        // abstract
1451        highlight: function (index) {
1452            var choices = this.findHighlightableChoices(),
1453                choice,
1454                data;
1455
1456            if (arguments.length === 0) {
1457                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1458            }
1459
1460            if (index >= choices.length) index = choices.length - 1;
1461            if (index < 0) index = 0;
1462
1463            this.removeHighlight();
1464
1465            choice = $(choices[index]);
1466            choice.addClass("select2-highlighted");
1467
1468            this.ensureHighlightVisible();
1469
1470            data = choice.data("select2-data");
1471            if (data) {
1472                this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1473            }
1474        },
1475
1476        removeHighlight: function() {
1477            this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1478        },
1479
1480        // abstract
1481        countSelectableResults: function() {
1482            return this.findHighlightableChoices().length;
1483        },
1484
1485        // abstract
1486        highlightUnderEvent: function (event) {
1487            var el = $(event.target).closest(".select2-result-selectable");
1488            if (el.length > 0 && !el.is(".select2-highlighted")) {
1489                var choices = this.findHighlightableChoices();
1490                this.highlight(choices.index(el));
1491            } else if (el.length == 0) {
1492                // if we are over an unselectable item remove all highlights
1493                this.removeHighlight();
1494            }
1495        },
1496
1497        // abstract
1498        loadMoreIfNeeded: function () {
1499            var results = this.results,
1500                more = results.find("li.select2-more-results"),
1501                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1502                page = this.resultsPage + 1,
1503                self=this,
1504                term=this.search.val(),
1505                context=this.context;
1506
1507            if (more.length === 0) return;
1508            below = more.offset().top - results.offset().top - results.height();
1509
1510            if (below <= this.opts.loadMorePadding) {
1511                more.addClass("select2-active");
1512                this.opts.query({
1513                        element: this.opts.element,
1514                        term: term,
1515                        page: page,
1516                        context: context,
1517                        matcher: this.opts.matcher,
1518                        callback: this.bind(function (data) {
1519
1520                    // ignore a response if the select2 has been closed before it was received
1521                    if (!self.opened()) return;
1522
1523
1524                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1525                    self.postprocessResults(data, false, false);
1526
1527                    if (data.more===true) {
1528                        more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
1529                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1530                    } else {
1531                        more.remove();
1532                    }
1533                    self.positionDropdown();
1534                    self.resultsPage = page;
1535                    self.context = data.context;
1536                    this.opts.element.trigger({ type: "select2-loaded", items: data });
1537                })});
1538            }
1539        },
1540
1541        /**
1542         * Default tokenizer function which does nothing
1543         */
1544        tokenize: function() {
1545
1546        },
1547
1548        /**
1549         * @param initial whether or not this is the call to this method right after the dropdown has been opened
1550         */
1551        // abstract
1552        updateResults: function (initial) {
1553            var search = this.search,
1554                results = this.results,
1555                opts = this.opts,
1556                data,
1557                self = this,
1558                input,
1559                term = search.val(),
1560                lastTerm = $.data(this.container, "select2-last-term"),
1561                // sequence number used to drop out-of-order responses
1562                queryNumber;
1563
1564            // prevent duplicate queries against the same term
1565            if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1566
1567            $.data(this.container, "select2-last-term", term);
1568
1569            // if the search is currently hidden we do not alter the results
1570            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1571                return;
1572            }
1573
1574            function postRender() {
1575                search.removeClass("select2-active");
1576                self.positionDropdown();
1577            }
1578
1579            function render(html) {
1580                results.html(html);
1581                postRender();
1582            }
1583
1584            queryNumber = ++this.queryCount;
1585
1586            var maxSelSize = this.getMaximumSelectionSize();
1587            if (maxSelSize >=1) {
1588                data = this.data();
1589                if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1590                    render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>");
1591                    return;
1592                }
1593            }
1594
1595            if (search.val().length < opts.minimumInputLength) {
1596                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1597                    render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
1598                } else {
1599                    render("");
1600                }
1601                if (initial && this.showSearch) this.showSearch(true);
1602                return;
1603            }
1604
1605            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1606                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1607                    render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>");
1608                } else {
1609                    render("");
1610                }
1611                return;
1612            }
1613
1614            if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1615                render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
1616            }
1617
1618            search.addClass("select2-active");
1619
1620            this.removeHighlight();
1621
1622            // give the tokenizer a chance to pre-process the input
1623            input = this.tokenize();
1624            if (input != undefined && input != null) {
1625                search.val(input);
1626            }
1627
1628            this.resultsPage = 1;
1629
1630            opts.query({
1631                element: opts.element,
1632                    term: search.val(),
1633                    page: this.resultsPage,
1634                    context: null,
1635                    matcher: opts.matcher,
1636                    callback: this.bind(function (data) {
1637                var def; // default choice
1638
1639                // ignore old responses
1640                if (queryNumber != this.queryCount) {
1641                  return;
1642                }
1643
1644                // ignore a response if the select2 has been closed before it was received
1645                if (!this.opened()) {
1646                    this.search.removeClass("select2-active");
1647                    return;
1648                }
1649
1650                // save context, if any
1651                this.context = (data.context===undefined) ? null : data.context;
1652                // create a default choice and prepend it to the list
1653                if (this.opts.createSearchChoice && search.val() !== "") {
1654                    def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1655                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1656                        if ($(data.results).filter(
1657                            function () {
1658                                return equal(self.id(this), self.id(def));
1659                            }).length === 0) {
1660                            data.results.unshift(def);
1661                        }
1662                    }
1663                }
1664
1665                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1666                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
1667                    return;
1668                }
1669
1670                results.empty();
1671                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1672
1673                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1674                    results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
1675                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1676                }
1677
1678                this.postprocessResults(data, initial);
1679
1680                postRender();
1681
1682                this.opts.element.trigger({ type: "select2-loaded", items: data });
1683            })});
1684        },
1685
1686        // abstract
1687        cancel: function () {
1688            this.close();
1689        },
1690
1691        // abstract
1692        blur: function () {
1693            // if selectOnBlur == true, select the currently highlighted option
1694            if (this.opts.selectOnBlur)
1695                this.selectHighlighted({noFocus: true});
1696
1697            this.close();
1698            this.container.removeClass("select2-container-active");
1699            // synonymous to .is(':focus'), which is available in jquery >= 1.6
1700            if (this.search[0] === document.activeElement) { this.search.blur(); }
1701            this.clearSearch();
1702            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1703        },
1704
1705        // abstract
1706        focusSearch: function () {
1707            focus(this.search);
1708        },
1709
1710        // abstract
1711        selectHighlighted: function (options) {
1712            var index=this.highlight(),
1713                highlighted=this.results.find(".select2-highlighted"),
1714                data = highlighted.closest('.select2-result').data("select2-data");
1715
1716            if (data) {
1717                this.highlight(index);
1718                this.onSelect(data, options);
1719            } else if (options && options.noFocus) {
1720                this.close();
1721            }
1722        },
1723
1724        // abstract
1725        getPlaceholder: function () {
1726            var placeholderOption;
1727            return this.opts.element.attr("placeholder") ||
1728                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1729                this.opts.element.data("placeholder") ||
1730                this.opts.placeholder ||
1731                ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
1732        },
1733
1734        // abstract
1735        getPlaceholderOption: function() {
1736            if (this.select) {
1737                var firstOption = this.select.children('option').first();
1738                if (this.opts.placeholderOption !== undefined ) {
1739                    //Determine the placeholder option based on the specified placeholderOption setting
1740                    return (this.opts.placeholderOption === "first" && firstOption) ||
1741                           (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
1742                } else if (firstOption.text() === "" && firstOption.val() === "") {
1743                    //No explicit placeholder option specified, use the first if it's blank
1744                    return firstOption;
1745                }
1746            }
1747        },
1748
1749        /**
1750         * Get the desired width for the container element.  This is
1751         * derived first from option `width` passed to select2, then
1752         * the inline 'style' on the original element, and finally
1753         * falls back to the jQuery calculated element width.
1754         */
1755        // abstract
1756        initContainerWidth: function () {
1757            function resolveContainerWidth() {
1758                var style, attrs, matches, i, l, attr;
1759
1760                if (this.opts.width === "off") {
1761                    return null;
1762                } else if (this.opts.width === "element"){
1763                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1764                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1765                    // check if there is inline style on the element that contains width
1766                    style = this.opts.element.attr('style');
1767                    if (style !== undefined) {
1768                        attrs = style.split(';');
1769                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
1770                            attr = attrs[i].replace(/\s/g, '');
1771                            matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
1772                            if (matches !== null && matches.length >= 1)
1773                                return matches[1];
1774                        }
1775                    }
1776
1777                    if (this.opts.width === "resolve") {
1778                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1779                        // when attached to input type=hidden or elements hidden via css
1780                        style = this.opts.element.css('width');
1781                        if (style.indexOf("%") > 0) return style;
1782
1783                        // finally, fallback on the calculated width of the element
1784                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1785                    }
1786
1787                    return null;
1788                } else if ($.isFunction(this.opts.width)) {
1789                    return this.opts.width();
1790                } else {
1791                    return this.opts.width;
1792               }
1793            };
1794
1795            var width = resolveContainerWidth.call(this);
1796            if (width !== null) {
1797                this.container.css("width", width);
1798            }
1799        }
1800    });
1801
1802    SingleSelect2 = clazz(AbstractSelect2, {
1803
1804        // single
1805
1806        createContainer: function () {
1807            var container = $(document.createElement("div")).attr({
1808                "class": "select2-container"
1809            }).html([
1810                "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>",
1811                "   <span class='select2-chosen'>&nbsp;</span><abbr class='select2-search-choice-close'></abbr>",
1812                "   <span class='select2-arrow'><b></b></span>",
1813                "</a>",
1814                "<input class='select2-focusser select2-offscreen' type='text'/>",
1815                "<div class='select2-drop select2-display-none'>",
1816                "   <div class='select2-search'>",
1817                "       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>",
1818                "   </div>",
1819                "   <ul class='select2-results'>",
1820                "   </ul>",
1821                "</div>"].join(""));
1822            return container;
1823        },
1824
1825        // single
1826        enableInterface: function() {
1827            if (this.parent.enableInterface.apply(this, arguments)) {
1828                this.focusser.prop("disabled", !this.isInterfaceEnabled());
1829            }
1830        },
1831
1832        // single
1833        opening: function () {
1834            var el, range, len;
1835
1836            if (this.opts.minimumResultsForSearch >= 0) {
1837                this.showSearch(true);
1838            }
1839
1840            this.parent.opening.apply(this, arguments);
1841
1842            if (this.showSearchInput !== false) {
1843                // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
1844                // all other browsers handle this just fine
1845
1846                this.search.val(this.focusser.val());
1847            }
1848            this.search.focus();
1849            // move the cursor to the end after focussing, otherwise it will be at the beginning and
1850            // new text will appear *before* focusser.val()
1851            el = this.search.get(0);
1852            if (el.createTextRange) {
1853                range = el.createTextRange();
1854                range.collapse(false);
1855                range.select();
1856            } else if (el.setSelectionRange) {
1857                len = this.search.val().length;
1858                el.setSelectionRange(len, len);
1859            }
1860
1861            // initializes search's value with nextSearchTerm (if defined by user)
1862            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
1863            if(this.search.val() === "") {
1864                if(this.nextSearchTerm != undefined){
1865                    this.search.val(this.nextSearchTerm);
1866                    this.search.select();
1867                }
1868            }
1869
1870            this.focusser.prop("disabled", true).val("");
1871            this.updateResults(true);
1872            this.opts.element.trigger($.Event("select2-open"));
1873        },
1874
1875        // single
1876        close: function (params) {
1877            if (!this.opened()) return;
1878            this.parent.close.apply(this, arguments);
1879
1880            params = params || {focus: true};
1881            this.focusser.removeAttr("disabled");
1882
1883            if (params.focus) {
1884                this.focusser.focus();
1885            }
1886        },
1887
1888        // single
1889        focus: function () {
1890            if (this.opened()) {
1891                this.close();
1892            } else {
1893                this.focusser.removeAttr("disabled");
1894                this.focusser.focus();
1895            }
1896        },
1897
1898        // single
1899        isFocused: function () {
1900            return this.container.hasClass("select2-container-active");
1901        },
1902
1903        // single
1904        cancel: function () {
1905            this.parent.cancel.apply(this, arguments);
1906            this.focusser.removeAttr("disabled");
1907            this.focusser.focus();
1908        },
1909
1910        // single
1911        destroy: function() {
1912            $("label[for='" + this.focusser.attr('id') + "']")
1913                .attr('for', this.opts.element.attr("id"));
1914            this.parent.destroy.apply(this, arguments);
1915        },
1916
1917        // single
1918        initContainer: function () {
1919
1920            var selection,
1921                container = this.container,
1922                dropdown = this.dropdown;
1923
1924            if (this.opts.minimumResultsForSearch < 0) {
1925                this.showSearch(false);
1926            } else {
1927                this.showSearch(true);
1928            }
1929
1930            this.selection = selection = container.find(".select2-choice");
1931
1932            this.focusser = container.find(".select2-focusser");
1933
1934            // rewrite labels from original element to focusser
1935            this.focusser.attr("id", "s2id_autogen"+nextUid());
1936
1937            $("label[for='" + this.opts.element.attr("id") + "']")
1938                .attr('for', this.focusser.attr('id'));
1939
1940            this.focusser.attr("tabindex", this.elementTabIndex);
1941
1942            this.search.on("keydown", this.bind(function (e) {
1943                if (!this.isInterfaceEnabled()) return;
1944
1945                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
1946                    // prevent the page from scrolling
1947                    killEvent(e);
1948                    return;
1949                }
1950
1951                switch (e.which) {
1952                    case KEY.UP:
1953                    case KEY.DOWN:
1954                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
1955                        killEvent(e);
1956                        return;
1957                    case KEY.ENTER:
1958                        this.selectHighlighted();
1959                        killEvent(e);
1960                        return;
1961                    case KEY.TAB:
1962                        this.selectHighlighted({noFocus: true});
1963                        return;
1964                    case KEY.ESC:
1965                        this.cancel(e);
1966                        killEvent(e);
1967                        return;
1968                }
1969            }));
1970
1971            this.search.on("blur", this.bind(function(e) {
1972                // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
1973                // without this the search field loses focus which is annoying
1974                if (document.activeElement === this.body().get(0)) {
1975                    window.setTimeout(this.bind(function() {
1976                        this.search.focus();
1977                    }), 0);
1978                }
1979            }));
1980
1981            this.focusser.on("keydown", this.bind(function (e) {
1982                if (!this.isInterfaceEnabled()) return;
1983
1984                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
1985                    return;
1986                }
1987
1988                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
1989                    killEvent(e);
1990                    return;
1991                }
1992
1993                if (e.which == KEY.DOWN || e.which == KEY.UP
1994                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
1995
1996                    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
1997
1998                    this.open();
1999                    killEvent(e);
2000                    return;
2001                }
2002
2003                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
2004                    if (this.opts.allowClear) {
2005                        this.clear();
2006                    }
2007                    killEvent(e);
2008                    return;
2009                }
2010            }));
2011
2012
2013            installKeyUpChangeEvent(this.focusser);
2014            this.focusser.on("keyup-change input", this.bind(function(e) {
2015                if (this.opts.minimumResultsForSearch >= 0) {
2016                    e.stopPropagation();
2017                    if (this.opened()) return;
2018                    this.open();
2019                }
2020            }));
2021
2022            selection.on("mousedown", "abbr", this.bind(function (e) {
2023                if (!this.isInterfaceEnabled()) return;
2024                this.clear();
2025                killEventImmediately(e);
2026                this.close();
2027                this.selection.focus();
2028            }));
2029
2030            selection.on("mousedown", this.bind(function (e) {
2031
2032                if (!this.container.hasClass("select2-container-active")) {
2033                    this.opts.element.trigger($.Event("select2-focus"));
2034                }
2035
2036                if (this.opened()) {
2037                    this.close();
2038                } else if (this.isInterfaceEnabled()) {
2039                    this.open();
2040                }
2041
2042                killEvent(e);
2043            }));
2044
2045            dropdown.on("mousedown", this.bind(function() { this.search.focus(); }));
2046
2047            selection.on("focus", this.bind(function(e) {
2048                killEvent(e);
2049            }));
2050
2051            this.focusser.on("focus", this.bind(function(){
2052                if (!this.container.hasClass("select2-container-active")) {
2053                    this.opts.element.trigger($.Event("select2-focus"));
2054                }
2055                this.container.addClass("select2-container-active");
2056            })).on("blur", this.bind(function() {
2057                if (!this.opened()) {
2058                    this.container.removeClass("select2-container-active");
2059                    this.opts.element.trigger($.Event("select2-blur"));
2060                }
2061            }));
2062            this.search.on("focus", this.bind(function(){
2063                if (!this.container.hasClass("select2-container-active")) {
2064                    this.opts.element.trigger($.Event("select2-focus"));
2065                }
2066                this.container.addClass("select2-container-active");
2067            }));
2068
2069            this.initContainerWidth();
2070            this.opts.element.addClass("select2-offscreen");
2071            this.setPlaceholder();
2072
2073        },
2074
2075        // single
2076        clear: function(triggerChange) {
2077            var data=this.selection.data("select2-data");
2078            if (data) { // guard against queued quick consecutive clicks
2079                var evt = $.Event("select2-clearing");
2080                this.opts.element.trigger(evt);
2081                if (evt.isDefaultPrevented()) {
2082                    return;
2083                }
2084                var placeholderOption = this.getPlaceholderOption();
2085                this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2086                this.selection.find(".select2-chosen").empty();
2087                this.selection.removeData("select2-data");
2088                this.setPlaceholder();
2089
2090                if (triggerChange !== false){
2091                    this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2092                    this.triggerChange({removed:data});
2093                }
2094            }
2095        },
2096
2097        /**
2098         * Sets selection based on source element's value
2099         */
2100        // single
2101        initSelection: function () {
2102            var selected;
2103            if (this.isPlaceholderOptionSelected()) {
2104                this.updateSelection(null);
2105                this.close();
2106                this.setPlaceholder();
2107            } else {
2108                var self = this;
2109                this.opts.initSelection.call(null, this.opts.element, function(selected){
2110                    if (selected !== undefined && selected !== null) {
2111                        self.updateSelection(selected);
2112                        self.close();
2113                        self.setPlaceholder();
2114                    }
2115                });
2116            }
2117        },
2118
2119        isPlaceholderOptionSelected: function() {
2120            var placeholderOption;
2121            if (!this.getPlaceholder()) return false; // no placeholder specified so no option should be considered
2122            return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
2123                || (this.opts.element.val() === "")
2124                || (this.opts.element.val() === undefined)
2125                || (this.opts.element.val() === null);
2126        },
2127
2128        // single
2129        prepareOpts: function () {
2130            var opts = this.parent.prepareOpts.apply(this, arguments),
2131                self=this;
2132
2133            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2134                // install the selection initializer
2135                opts.initSelection = function (element, callback) {
2136                    var selected = element.find("option").filter(function() { return this.selected });
2137                    // a single select box always has a value, no need to null check 'selected'
2138                    callback(self.optionToData(selected));
2139                };
2140            } else if ("data" in opts) {
2141                // install default initSelection when applied to hidden input and data is local
2142                opts.initSelection = opts.initSelection || function (element, callback) {
2143                    var id = element.val();
2144                    //search in data by id, storing the actual matching item
2145                    var match = null;
2146                    opts.query({
2147                        matcher: function(term, text, el){
2148                            var is_match = equal(id, opts.id(el));
2149                            if (is_match) {
2150                                match = el;
2151                            }
2152                            return is_match;
2153                        },
2154                        callback: !$.isFunction(callback) ? $.noop : function() {
2155                            callback(match);
2156                        }
2157                    });
2158                };
2159            }
2160
2161            return opts;
2162        },
2163
2164        // single
2165        getPlaceholder: function() {
2166            // if a placeholder is specified on a single select without a valid placeholder option ignore it
2167            if (this.select) {
2168                if (this.getPlaceholderOption() === undefined) {
2169                    return undefined;
2170                }
2171            }
2172
2173            return this.parent.getPlaceholder.apply(this, arguments);
2174        },
2175
2176        // single
2177        setPlaceholder: function () {
2178            var placeholder = this.getPlaceholder();
2179
2180            if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2181
2182                // check for a placeholder option if attached to a select
2183                if (this.select && this.getPlaceholderOption() === undefined) return;
2184
2185                this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2186
2187                this.selection.addClass("select2-default");
2188
2189                this.container.removeClass("select2-allowclear");
2190            }
2191        },
2192
2193        // single
2194        postprocessResults: function (data, initial, noHighlightUpdate) {
2195            var selected = 0, self = this, showSearchInput = true;
2196
2197            // find the selected element in the result list
2198
2199            this.findHighlightableChoices().each2(function (i, elm) {
2200                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2201                    selected = i;
2202                    return false;
2203                }
2204            });
2205
2206            // and highlight it
2207            if (noHighlightUpdate !== false) {
2208                if (initial === true && selected >= 0) {
2209                    this.highlight(selected);
2210                } else {
2211                    this.highlight(0);
2212                }
2213            }
2214
2215            // hide the search box if this is the first we got the results and there are enough of them for search
2216
2217            if (initial === true) {
2218                var min = this.opts.minimumResultsForSearch;
2219                if (min >= 0) {
2220                    this.showSearch(countResults(data.results) >= min);
2221                }
2222            }
2223        },
2224
2225        // single
2226        showSearch: function(showSearchInput) {
2227            if (this.showSearchInput === showSearchInput) return;
2228
2229            this.showSearchInput = showSearchInput;
2230
2231            this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2232            this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2233            //add "select2-with-searchbox" to the container if search box is shown
2234            $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2235        },
2236
2237        // single
2238        onSelect: function (data, options) {
2239
2240            if (!this.triggerSelect(data)) { return; }
2241
2242            var old = this.opts.element.val(),
2243                oldData = this.data();
2244
2245            this.opts.element.val(this.id(data));
2246            this.updateSelection(data);
2247
2248            this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2249
2250            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2251            this.close();
2252
2253            if (!options || !options.noFocus)
2254                this.focusser.focus();
2255
2256            if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); }
2257        },
2258
2259        // single
2260        updateSelection: function (data) {
2261
2262            var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2263
2264            this.selection.data("select2-data", data);
2265
2266            container.empty();
2267            if (data !== null) {
2268                formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2269            }
2270            if (formatted !== undefined) {
2271                container.append(formatted);
2272            }
2273            cssClass=this.opts.formatSelectionCssClass(data, container);
2274            if (cssClass !== undefined) {
2275                container.addClass(cssClass);
2276            }
2277
2278            this.selection.removeClass("select2-default");
2279
2280            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2281                this.container.addClass("select2-allowclear");
2282            }
2283        },
2284
2285        // single
2286        val: function () {
2287            var val,
2288                triggerChange = false,
2289                data = null,
2290                self = this,
2291                oldData = this.data();
2292
2293            if (arguments.length === 0) {
2294                return this.opts.element.val();
2295            }
2296
2297            val = arguments[0];
2298
2299            if (arguments.length > 1) {
2300                triggerChange = arguments[1];
2301            }
2302
2303            if (this.select) {
2304                this.select
2305                    .val(val)
2306                    .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2307                        data = self.optionToData(elm);
2308                        return false;
2309                    });
2310                this.updateSelection(data);
2311                this.setPlaceholder();
2312                if (triggerChange) {
2313                    this.triggerChange({added: data, removed:oldData});
2314                }
2315            } else {
2316                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2317                if (!val && val !== 0) {
2318                    this.clear(triggerChange);
2319                    return;
2320                }
2321                if (this.opts.initSelection === undefined) {
2322                    throw new Error("cannot call val() if initSelection() is not defined");
2323                }
2324                this.opts.element.val(val);
2325                this.opts.initSelection(this.opts.element, function(data){
2326                    self.opts.element.val(!data ? "" : self.id(data));
2327                    self.updateSelection(data);
2328                    self.setPlaceholder();
2329                    if (triggerChange) {
2330                        self.triggerChange({added: data, removed:oldData});
2331                    }
2332                });
2333            }
2334        },
2335
2336        // single
2337        clearSearch: function () {
2338            this.search.val("");
2339            this.focusser.val("");
2340        },
2341
2342        // single
2343        data: function(value) {
2344            var data,
2345                triggerChange = false;
2346
2347            if (arguments.length === 0) {
2348                data = this.selection.data("select2-data");
2349                if (data == undefined) data = null;
2350                return data;
2351            } else {
2352                if (arguments.length > 1) {
2353                    triggerChange = arguments[1];
2354                }
2355                if (!value) {
2356                    this.clear(triggerChange);
2357                } else {
2358                    data = this.data();
2359                    this.opts.element.val(!value ? "" : this.id(value));
2360                    this.updateSelection(value);
2361                    if (triggerChange) {
2362                        this.triggerChange({added: value, removed:data});
2363                    }
2364                }
2365            }
2366        }
2367    });
2368
2369    MultiSelect2 = clazz(AbstractSelect2, {
2370
2371        // multi
2372        createContainer: function () {
2373            var container = $(document.createElement("div")).attr({
2374                "class": "select2-container select2-container-multi"
2375            }).html([
2376                "<ul class='select2-choices'>",
2377                "  <li class='select2-search-field'>",
2378                "    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2379                "  </li>",
2380                "</ul>",
2381                "<div class='select2-drop select2-drop-multi select2-display-none'>",
2382                "   <ul class='select2-results'>",
2383                "   </ul>",
2384                "</div>"].join(""));
2385            return container;
2386        },
2387
2388        // multi
2389        prepareOpts: function () {
2390            var opts = this.parent.prepareOpts.apply(this, arguments),
2391                self=this;
2392
2393            // TODO validate placeholder is a string if specified
2394
2395            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2396                // install sthe selection initializer
2397                opts.initSelection = function (element, callback) {
2398
2399                    var data = [];
2400
2401                    element.find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2402                        data.push(self.optionToData(elm));
2403                    });
2404                    callback(data);
2405                };
2406            } else if ("data" in opts) {
2407                // install default initSelection when applied to hidden input and data is local
2408                opts.initSelection = opts.initSelection || function (element, callback) {
2409                    var ids = splitVal(element.val(), opts.separator);
2410                    //search in data by array of ids, storing matching items in a list
2411                    var matches = [];
2412                    opts.query({
2413                        matcher: function(term, text, el){
2414                            var is_match = $.grep(ids, function(id) {
2415                                return equal(id, opts.id(el));
2416                            }).length;
2417                            if (is_match) {
2418                                matches.push(el);
2419                            }
2420                            return is_match;
2421                        },
2422                        callback: !$.isFunction(callback) ? $.noop : function() {
2423                            // reorder matches based on the order they appear in the ids array because right now
2424                            // they are in the order in which they appear in data array
2425                            var ordered = [];
2426                            for (var i = 0; i < ids.length; i++) {
2427                                var id = ids[i];
2428                                for (var j = 0; j < matches.length; j++) {
2429                                    var match = matches[j];
2430                                    if (equal(id, opts.id(match))) {
2431                                        ordered.push(match);
2432                                        matches.splice(j, 1);
2433                                        break;
2434                                    }
2435                                }
2436                            }
2437                            callback(ordered);
2438                        }
2439                    });
2440                };
2441            }
2442
2443            return opts;
2444        },
2445
2446        selectChoice: function (choice) {
2447
2448            var selected = this.container.find(".select2-search-choice-focus");
2449            if (selected.length && choice && choice[0] == selected[0]) {
2450
2451            } else {
2452                if (selected.length) {
2453                    this.opts.element.trigger("choice-deselected", selected);
2454                }
2455                selected.removeClass("select2-search-choice-focus");
2456                if (choice && choice.length) {
2457                    this.close();
2458                    choice.addClass("select2-search-choice-focus");
2459                    this.opts.element.trigger("choice-selected", choice);
2460                }
2461            }
2462        },
2463
2464        // multi
2465        destroy: function() {
2466            $("label[for='" + this.search.attr('id') + "']")
2467                .attr('for', this.opts.element.attr("id"));
2468            this.parent.destroy.apply(this, arguments);
2469        },
2470
2471        // multi
2472        initContainer: function () {
2473
2474            var selector = ".select2-choices", selection;
2475
2476            this.searchContainer = this.container.find(".select2-search-field");
2477            this.selection = selection = this.container.find(selector);
2478
2479            var _this = this;
2480            this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
2481                //killEvent(e);
2482                _this.search[0].focus();
2483                _this.selectChoice($(this));
2484            });
2485
2486            // rewrite labels from original element to focusser
2487            this.search.attr("id", "s2id_autogen"+nextUid());
2488            $("label[for='" + this.opts.element.attr("id") + "']")
2489                .attr('for', this.search.attr('id'));
2490
2491            this.search.on("input paste", this.bind(function() {
2492                if (!this.isInterfaceEnabled()) return;
2493                if (!this.opened()) {
2494                    this.open();
2495                }
2496            }));
2497
2498            this.search.attr("tabindex", this.elementTabIndex);
2499
2500            this.keydowns = 0;
2501            this.search.on("keydown", this.bind(function (e) {
2502                if (!this.isInterfaceEnabled()) return;
2503
2504                ++this.keydowns;
2505                var selected = selection.find(".select2-search-choice-focus");
2506                var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2507                var next = selected.next(".select2-search-choice:not(.select2-locked)");
2508                var pos = getCursorInfo(this.search);
2509
2510                if (selected.length &&
2511                    (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2512                    var selectedChoice = selected;
2513                    if (e.which == KEY.LEFT && prev.length) {
2514                        selectedChoice = prev;
2515                    }
2516                    else if (e.which == KEY.RIGHT) {
2517                        selectedChoice = next.length ? next : null;
2518                    }
2519                    else if (e.which === KEY.BACKSPACE) {
2520                        this.unselect(selected.first());
2521                        this.search.width(10);
2522                        selectedChoice = prev.length ? prev : next;
2523                    } else if (e.which == KEY.DELETE) {
2524                        this.unselect(selected.first());
2525                        this.search.width(10);
2526                        selectedChoice = next.length ? next : null;
2527                    } else if (e.which == KEY.ENTER) {
2528                        selectedChoice = null;
2529                    }
2530
2531                    this.selectChoice(selectedChoice);
2532                    killEvent(e);
2533                    if (!selectedChoice || !selectedChoice.length) {
2534                        this.open();
2535                    }
2536                    return;
2537                } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2538                    || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2539
2540                    this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2541                    killEvent(e);
2542                    return;
2543                } else {
2544                    this.selectChoice(null);
2545                }
2546
2547                if (this.opened()) {
2548                    switch (e.which) {
2549                    case KEY.UP:
2550                    case KEY.DOWN:
2551                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2552                        killEvent(e);
2553                        return;
2554                    case KEY.ENTER:
2555                        this.selectHighlighted();
2556                        killEvent(e);
2557                        return;
2558                    case KEY.TAB:
2559                        this.selectHighlighted({noFocus:true});
2560                        this.close();
2561                        return;
2562                    case KEY.ESC:
2563                        this.cancel(e);
2564                        killEvent(e);
2565                        return;
2566                    }
2567                }
2568
2569                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2570                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2571                    return;
2572                }
2573
2574                if (e.which === KEY.ENTER) {
2575                    if (this.opts.openOnEnter === false) {
2576                        return;
2577                    } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2578                        return;
2579                    }
2580                }
2581
2582                this.open();
2583
2584                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2585                    // prevent the page from scrolling
2586                    killEvent(e);
2587                }
2588
2589                if (e.which === KEY.ENTER) {
2590                    // prevent form from being submitted
2591                    killEvent(e);
2592                }
2593
2594            }));
2595
2596            this.search.on("keyup", this.bind(function (e) {
2597                this.keydowns = 0;
2598                this.resizeSearch();
2599            })
2600            );
2601
2602            this.search.on("blur", this.bind(function(e) {
2603                this.container.removeClass("select2-container-active");
2604                this.search.removeClass("select2-focused");
2605                this.selectChoice(null);
2606                if (!this.opened()) this.clearSearch();
2607                e.stopImmediatePropagation();
2608                this.opts.element.trigger($.Event("select2-blur"));
2609            }));
2610
2611            this.container.on("click", selector, this.bind(function (e) {
2612                if (!this.isInterfaceEnabled()) return;
2613                if ($(e.target).closest(".select2-search-choice").length > 0) {
2614                    // clicked inside a select2 search choice, do not open
2615                    return;
2616                }
2617                this.selectChoice(null);
2618                this.clearPlaceholder();
2619                if (!this.container.hasClass("select2-container-active")) {
2620                    this.opts.element.trigger($.Event("select2-focus"));
2621                }
2622                this.open();
2623                this.focusSearch();
2624                e.preventDefault();
2625            }));
2626
2627            this.container.on("focus", selector, this.bind(function () {
2628                if (!this.isInterfaceEnabled()) return;
2629                if (!this.container.hasClass("select2-container-active")) {
2630                    this.opts.element.trigger($.Event("select2-focus"));
2631                }
2632                this.container.addClass("select2-container-active");
2633                this.dropdown.addClass("select2-drop-active");
2634                this.clearPlaceholder();
2635            }));
2636
2637            this.initContainerWidth();
2638            this.opts.element.addClass("select2-offscreen");
2639
2640            // set the placeholder if necessary
2641            this.clearSearch();
2642        },
2643
2644        // multi
2645        enableInterface: function() {
2646            if (this.parent.enableInterface.apply(this, arguments)) {
2647                this.search.prop("disabled", !this.isInterfaceEnabled());
2648            }
2649        },
2650
2651        // multi
2652        initSelection: function () {
2653            var data;
2654            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2655                this.updateSelection([]);
2656                this.close();
2657                // set the placeholder if necessary
2658                this.clearSearch();
2659            }
2660            if (this.select || this.opts.element.val() !== "") {
2661                var self = this;
2662                this.opts.initSelection.call(null, this.opts.element, function(data){
2663                    if (data !== undefined && data !== null) {
2664                        self.updateSelection(data);
2665                        self.close();
2666                        // set the placeholder if necessary
2667                        self.clearSearch();
2668                    }
2669                });
2670            }
2671        },
2672
2673        // multi
2674        clearSearch: function () {
2675            var placeholder = this.getPlaceholder(),
2676                maxWidth = this.getMaxSearchWidth();
2677
2678            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2679                this.search.val(placeholder).addClass("select2-default");
2680                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2681                // 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
2682                this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
2683            } else {
2684                this.search.val("").width(10);
2685            }
2686        },
2687
2688        // multi
2689        clearPlaceholder: function () {
2690            if (this.search.hasClass("select2-default")) {
2691                this.search.val("").removeClass("select2-default");
2692            }
2693        },
2694
2695        // multi
2696        opening: function () {
2697            this.clearPlaceholder(); // should be done before super so placeholder is not used to search
2698            this.resizeSearch();
2699
2700            this.parent.opening.apply(this, arguments);
2701
2702            this.focusSearch();
2703
2704            this.updateResults(true);
2705            this.search.focus();
2706            this.opts.element.trigger($.Event("select2-open"));
2707        },
2708
2709        // multi
2710        close: function () {
2711            if (!this.opened()) return;
2712            this.parent.close.apply(this, arguments);
2713        },
2714
2715        // multi
2716        focus: function () {
2717            this.close();
2718            this.search.focus();
2719        },
2720
2721        // multi
2722        isFocused: function () {
2723            return this.search.hasClass("select2-focused");
2724        },
2725
2726        // multi
2727        updateSelection: function (data) {
2728            var ids = [], filtered = [], self = this;
2729
2730            // filter out duplicates
2731            $(data).each(function () {
2732                if (indexOf(self.id(this), ids) < 0) {
2733                    ids.push(self.id(this));
2734                    filtered.push(this);
2735                }
2736            });
2737            data = filtered;
2738
2739            this.selection.find(".select2-search-choice").remove();
2740            $(data).each(function () {
2741                self.addSelectedChoice(this);
2742            });
2743            self.postprocessResults();
2744        },
2745
2746        // multi
2747        tokenize: function() {
2748            var input = this.search.val();
2749            input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
2750            if (input != null && input != undefined) {
2751                this.search.val(input);
2752                if (input.length > 0) {
2753                    this.open();
2754                }
2755            }
2756
2757        },
2758
2759        // multi
2760        onSelect: function (data, options) {
2761
2762            if (!this.triggerSelect(data)) { return; }
2763
2764            this.addSelectedChoice(data);
2765
2766            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2767
2768            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
2769
2770            if (this.opts.closeOnSelect) {
2771                this.close();
2772                this.search.width(10);
2773            } else {
2774                if (this.countSelectableResults()>0) {
2775                    this.search.width(10);
2776                    this.resizeSearch();
2777                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
2778                        // if we reached max selection size repaint the results so choices
2779                        // are replaced with the max selection reached message
2780                        this.updateResults(true);
2781                    }
2782                    this.positionDropdown();
2783                } else {
2784                    // if nothing left to select close
2785                    this.close();
2786                    this.search.width(10);
2787                }
2788            }
2789
2790            // since its not possible to select an element that has already been
2791            // added we do not need to check if this is a new element before firing change
2792            this.triggerChange({ added: data });
2793
2794            if (!options || !options.noFocus)
2795                this.focusSearch();
2796        },
2797
2798        // multi
2799        cancel: function () {
2800            this.close();
2801            this.focusSearch();
2802        },
2803
2804        addSelectedChoice: function (data) {
2805            var enableChoice = !data.locked,
2806                enabledItem = $(
2807                    "<li class='select2-search-choice'>" +
2808                    "    <div></div>" +
2809                    "    <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
2810                    "</li>"),
2811                disabledItem = $(
2812                    "<li class='select2-search-choice select2-locked'>" +
2813                    "<div></div>" +
2814                    "</li>");
2815            var choice = enableChoice ? enabledItem : disabledItem,
2816                id = this.id(data),
2817                val = this.getVal(),
2818                formatted,
2819                cssClass;
2820
2821            formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
2822            if (formatted != undefined) {
2823                choice.find("div").replaceWith("<div>"+formatted+"</div>");
2824            }
2825            cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
2826            if (cssClass != undefined) {
2827                choice.addClass(cssClass);
2828            }
2829
2830            if(enableChoice){
2831              choice.find(".select2-search-choice-close")
2832                  .on("mousedown", killEvent)
2833                  .on("click dblclick", this.bind(function (e) {
2834                  if (!this.isInterfaceEnabled()) return;
2835
2836                  $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
2837                      this.unselect($(e.target));
2838                      this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
2839                      this.close();
2840                      thi
2840s.focusSearch();
2841                  })).dequeue();
2842                  killEvent(e);
2843              })).on("focus", this.bind(function () {
2844                  if (!this.isInterfaceEnabled()) return;
2845                  this.container.addClass("select2-container-active");
2846                  this.dropdown.addClass("select2-drop-active");
2847              }));
2848            }
2849
2850            choice.data("select2-data", data);
2851            choice.insertBefore(this.searchContainer);
2852
2853            val.push(id);
2854            this.setVal(val);
2855        },
2856
2857        // multi
2858        unselect: function (selected) {
2859            var val = this.getVal(),
2860                data,
2861                index;
2862
2863            selected = selected.closest(".select2-search-choice");
2864
2865            if (selected.length === 0) {
2866                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
2867            }
2868
2869            data = selected.data("select2-data");
2870
2871            if (!data) {
2872                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
2873                // and invoked on an element already removed
2874                return;
2875            }
2876
2877            while((index = indexOf(this.id(data), val)) >= 0) {
2878                val.splice(index, 1);
2879                this.setVal(val);
2880                if (this.select) this.postprocessResults();
2881            }
2882
2883            var evt = $.Event("select2-removing");
2884            evt.val = this.id(data);
2885            evt.choice = data;
2886            this.opts.element.trigger(evt);
2887
2888            if (evt.isDefaultPrevented()) {
2889                return;
2890            }
2891
2892            this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2893            this.triggerChange({ removed: data });
2894        },
2895
2896        // multi
2897        postprocessResults: function (data, initial, noHighlightUpdate) {
2898            var val = this.getVal(),
2899                choices = this.results.find(".select2-result"),
2900                compound = this.results.find(".select2-result-with-children"),
2901                self = this;
2902
2903            choices.each2(function (i, choice) {
2904                var id = self.id(choice.data("select2-data"));
2905                if (indexOf(id, val) >= 0) {
2906                    choice.addClass("select2-selected");
2907                    // mark all children of the selected parent as selected
2908                    choice.find(".select2-result-selectable").addClass("select2-selected");
2909                }
2910            });
2911
2912            compound.each2(function(i, choice) {
2913                // hide an optgroup if it doesnt have any selectable children
2914                if (!choice.is('.select2-result-selectable')
2915                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
2916                    choice.addClass("select2-selected");
2917                }
2918            });
2919
2920            if (this.highlight() == -1 && noHighlightUpdate !== false){
2921                self.highlight(0);
2922            }
2923
2924            //If all results are chosen render formatNoMAtches
2925            if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
2926                if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
2927                    if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
2928                        this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>");
2929                    }
2930                }
2931            }
2932
2933        },
2934
2935        // multi
2936        getMaxSearchWidth: function() {
2937            return this.selection.width() - getSideBorderPadding(this.search);
2938        },
2939
2940        // multi
2941        resizeSearch: function () {
2942            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
2943                sideBorderPadding = getSideBorderPadding(this.search);
2944
2945            minimumWidth = measureTextWidth(this.search) + 10;
2946
2947            left = this.search.offset().left;
2948
2949            maxWidth = this.selection.width();
2950            containerLeft = this.selection.offset().left;
2951
2952            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
2953
2954            if (searchWidth < minimumWidth) {
2955                searchWidth = maxWidth - sideBorderPadding;
2956            }
2957
2958            if (searchWidth < 40) {
2959                searchWidth = maxWidth - sideBorderPadding;
2960            }
2961
2962            if (searchWidth <= 0) {
2963              searchWidth = minimumWidth;
2964            }
2965
2966            this.search.width(Math.floor(searchWidth));
2967        },
2968
2969        // multi
2970        getVal: function () {
2971            var val;
2972            if (this.select) {
2973                val = this.select.val();
2974                return val === null ? [] : val;
2975            } else {
2976                val = this.opts.element.val();
2977                return splitVal(val, this.opts.separator);
2978            }
2979        },
2980
2981        // multi
2982        setVal: function (val) {
2983            var unique;
2984            if (this.select) {
2985                this.select.val(val);
2986            } else {
2987                unique = [];
2988                // filter out duplicates
2989                $(val).each(function () {
2990                    if (indexOf(this, unique) < 0) unique.push(this);
2991                });
2992                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
2993            }
2994        },
2995
2996        // multi
2997        buildChangeDetails: function (old, current) {
2998            var current = current.slice(0),
2999                old = old.slice(0);
3000
3001            // remove intersection from each array
3002            for (var i = 0; i < current.length; i++) {
3003                for (var j = 0; j < old.length; j++) {
3004                    if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
3005                        current.splice(i, 1);
3006                        i--;
3007                        old.splice(j, 1);
3008                        j--;
3009                    }
3010                }
3011            }
3012
3013            return {added: current, removed: old};
3014        },
3015
3016
3017        // multi
3018        val: function (val, triggerChange) {
3019            var oldData, self=this;
3020
3021            if (arguments.length === 0) {
3022                return this.getVal();
3023            }
3024
3025            oldData=this.data();
3026            if (!oldData.length) oldData=[];
3027
3028            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3029            if (!val && val !== 0) {
3030                this.opts.element.val("");
3031                this.updateSelection([]);
3032                this.clearSearch();
3033                if (triggerChange) {
3034                    this.triggerChange({added: this.data(), removed: oldData});
3035                }
3036                return;
3037            }
3038
3039            // val is a list of ids
3040            this.setVal(val);
3041
3042            if (this.select) {
3043                this.opts.initSelection(this.select, this.bind(this.updateSelection));
3044                if (triggerChange) {
3045                    this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3046                }
3047            } else {
3048                if (this.opts.initSelection === undefined) {
3049                    throw new Error("val() cannot be called if initSelection() is not defined");
3050                }
3051
3052                this.opts.initSelection(this.opts.element, function(data){
3053                    var ids=$.map(data, self.id);
3054                    self.setVal(ids);
3055                    self.updateSelection(data);
3056                    self.clearSearch();
3057                    if (triggerChange) {
3058                        self.triggerChange(self.buildChangeDetails(oldData, self.data()));
3059                    }
3060                });
3061            }
3062            this.clearSearch();
3063        },
3064
3065        // multi
3066        onSortStart: function() {
3067            if (this.select) {
3068                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3069            }
3070
3071            // collapse search field into 0 width so its container can be collapsed as well
3072            this.search.width(0);
3073            // hide the container
3074            this.searchContainer.hide();
3075        },
3076
3077        // multi
3078        onSortEnd:function() {
3079
3080            var val=[], self=this;
3081
3082            // show search and move it to the end of the list
3083            this.searchContainer.show();
3084            // make sure the search container is the last item in the list
3085            this.searchContainer.appendTo(this.searchContainer.parent());
3086            // since we collapsed the width in dragStarted, we resize it here
3087            this.resizeSearch();
3088
3089            // update selection
3090            this.selection.find(".select2-search-choice").each(function() {
3091                val.push(self.opts.id($(this).data("select2-data")));
3092            });
3093            this.setVal(val);
3094            this.triggerChange();
3095        },
3096
3097        // multi
3098        data: function(values, triggerChange) {
3099            var self=this, ids, old;
3100            if (arguments.length === 0) {
3101                 return this.selection
3102                     .find(".select2-search-choice")
3103                     .map(function() { return $(this).data("select2-data"); })
3104                     .get();
3105            } else {
3106                old = this.data();
3107                if (!values) { values = []; }
3108                ids = $.map(values, function(e) { return self.opts.id(e); });
3109                this.setVal(ids);
3110                this.updateSelection(values);
3111                this.clearSearch();
3112                if (triggerChange) {
3113                    this.triggerChange(this.buildChangeDetails(old, this.data()));
3114                }
3115            }
3116        }
3117    });
3118
3119    $.fn.select2 = function () {
3120
3121        var args = Array.prototype.slice.call(arguments, 0),
3122            opts,
3123            select2,
3124            method, value, multiple,
3125            allowedMethods = ["val", "destroy", "opened", "open", "close", "focu
3125s", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3126            valueMethods = ["opened", "isFocused", "container", "dropdown"],
3127            propertyMethods = ["val", "data"],
3128            methodsMap = { search: "externalSearch" };
3129
3130        this.each(function () {
3131            if (args.length === 0 || typeof(args[0]) === "object") {
3132                opts = args.length === 0 ? {} : $.extend({}, args[0]);
3133                opts.element = $(this);
3134
3135                if (opts.element.get(0).tagName.toLowerCase() === "select") {
3136                    multiple = opts.element.prop("multiple");
3137                } else {
3138                    multiple = opts.multiple || false;
3139                    if ("tags" in opts) {opts.multiple = multiple = true;}
3140                }
3141
3142                select2 = multiple ? new MultiSelect2() : new SingleSelect2();
3143                select2.init(opts);
3144            } else if (typeof(args[0]) === "string") {
3145
3146                if (indexOf(args[0], allowedMethods) < 0) {
3147                    throw "Unknown method: " + args[0];
3148                }
3149
3150                value = undefined;
3151                select2 = $(this).data("select2");
3152                if (select2 === undefined) return;
3153
3154                method=args[0];
3155
3156                if (method === "container") {
3157                    value = select2.container;
3158                } else if (method === "dropdown") {
3159                    value = select2.dropdown;
3160                } else {
3161                    if (methodsMap[method]) method = methodsMap[method];
3162
3163                    value = select2[method].apply(select2, args.slice(1));
3164                }
3165                if (indexOf(args[0], valueMethods) >= 0
3166                    || (indexOf(args[0], propertyMethods) && args.length == 1)) {
3167                    return false; // abort the iteration, ready to return first matched value
3168                }
3169            } else {
3170                throw "Invalid arguments to select2 plugin: " + args;
3171            }
3172        });
3173        return (value === undefined) ? this : value;
3174    };
3175
3176    // plugin defaults, accessible to users
3177    $.fn.select2.defaults = {
3178        width: "copy",
3179        loadMorePadding: 0,
3180        closeOnSelect: true,
3181        openOnEnter: true,
3182        containerCss: {},
3183        dropdownCss: {},
3184        containerCssClass: "",
3185        dropdownCssClass: "",
3186        formatResult: function(result, container, query, escapeMarkup) {
3187            var markup=[];
3188            markMatch(result.text, query.term, markup, escapeMarkup);
3189            return markup.join("");
3190        },
3191        formatSelection: function (data, container, escapeMarkup) {
3192            return data ? escapeMarkup(data.text) : undefined;
3193        },
3194        sortResults: function (results, container, query) {
3195            return results;
3196        },
3197        formatResultCssClass: function(data) {return undefined;},
3198        formatSelectionCssClass: function(data, container) {return undefined;},
3199        formatNoMatches: function () { return "Ничего не найдено"; },
3200        formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
3201        formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
3202        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3203        formatLoadMore: function (pageNumber) { return "Loading more results..."; },
3204        formatSearching: function () { return "Searching..."; },
3205        minimumResultsForSearch: 0,
3206        minimumInputLength: 0,
3207        maximumInputLength: null,
3208        maximumSelectionSize: 0,
3209        id: function (e) { return e.id; },
3210        matcher: function(term, text) {
3211            return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3212        },
3213        separator: ",",
3214        tokenSeparators: [],
3215        tokenizer: defaultTokenizer,
3216        escapeMarkup: defaultEscapeMarkup,
3217        blurOnChange: false,
3218        selectOnBlur: false,
3219        adaptContainerCssClass: function(c) { return c; },
3220        adaptDropdownCssClass: function(c) { return null; },
3221        nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; }
3222    };
3223
3224    $.fn.select2.ajaxDefaults = {
3225        transport: $.ajax,
3226        params: {
3227            type: "GET",
3228            cache: false,
3229            dataType: "json"
3230        }
3231    };
3232
3233    // exports
3234    window.Select2 = {
3235        query: {
3236            ajax: ajax,
3237            local: local,
3238            tags: tags
3239        }, util: {
3240            debounce: debounce,
3241            markMatch: markMatch,
3242            escapeMarkup: defaultEscapeMarkup,
3243            stripDiacritics: stripDiacritics
3244        }, "class": {
3245            "abstract": AbstractSelect2,
3246            "single": SingleSelect2,
3247            "multi": MultiSelect2
3248        }
3249    };
3250
3251}(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.