PageSourceSearch

https://smartride.org/js/libs/select2.js

js smartride.org collected 2026-09-28 12:38:45 UTC 135,740 bytes, 3,255 lines download raw bytes

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