PageSourceSearch

http://www.portoenorte.pt/temas/tpnp/js/jquery/plugins/select2-3.4.3/select2.js

js portoenorte.pt collected 2026-10-02 10:38:10 UTC 134,098 bytes, 3,217 lines download raw bytes

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

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