PageSourceSearch

https://bayalagbuteegch.mn/js/select2.js

js bayalagbuteegch.mn collected 2026-10-04 13:27:38 UTC 137,230 bytes, 3,217 lines download raw bytes

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