PageSourceSearch

https://actionnetwork.org/includes/js/select2/select2.js?v=1

js actionnetwork.org collected 2026-09-24 07:04:29 UTC 134,381 bytes, 3,223 lines download raw bytes

1/*
2Copyright 2012 Igor Vaynberg
3
4Version: @@ver@@ Timestamp: @@timestamp@@
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    //www.apache.org/licenses/LICENSE-2.0
14    //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            // console.log('fire');
1406            // console.log(this);
1407            // console.log(this.results);
1408            return this.results.find(".select2-result-selectable:not(.select2-disabled,.select2-selected)");
1409        },
1410
1411        // abstract
1412        moveHighlight: function (delta) {
1413            var choices = this.findHighlightableChoices(),
1414                index = this.highlight();
1415
1416            while (index > -1 && index < choices.length) {
1417                index += delta;
1418                var choice = $(choices[index]);
1419                if (choice.hasClass("select2-result-selectable") && !c
1419hoice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1420                    this.highlight(index);
1421                    break;
1422                }
1423            }
1424        },
1425
1426        // abstract
1427        highlight: function (index) {
1428            var choices = this.findHighlightableChoices(),
1429                choice,
1430                data;
1431
1432            if (arguments.length === 0) {
1433                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1434            }
1435
1436            if (index >= choices.length) index = choices.length - 1;
1437            if (index < 0) index = 0;
1438
1439            this.removeHighlight();
1440
1441            choice = $(choices[index]);
1442            choice.addClass("select2-highlighted");
1443
1444            this.ensureHighlightVisible();
1445
1446            data = choice.data("select2-data");
1447            if (data) {
1448                this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1449            }
1450        },
1451
1452        removeHighlight: function() {
1453            this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1454        },
1455
1456        // abstract
1457        countSelectableResults: function() {
1458            return this.findHighlightableChoices().length;
1459        },
1460
1461        // abstract
1462        highlightUnderEvent: function (event) {
1463            var el = $(event.target).closest(".select2-result-selectable");
1464            if (el.length > 0 && !el.is(".select2-highlighted")) {
1465                var choices = this.findHighlightableChoices();
1466                this.highlight(choices.index(el));
1467            } else if (el.length == 0) {
1468                // if we are over an unselectable item remove all highlights
1469                this.removeHighlight();
1470            }
1471        },
1472
1473        // abstract
1474        loadMoreIfNeeded: function () {
1475            var results = this.results,
1476                more = results.find("li.select2-more-results"),
1477                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1478                offset = -1, // index of first element without data
1479                page = this.resultsPage + 1,
1480                self=this,
1481                term=this.search.val(),
1482                context=this.context;
1483
1484            if (more.length === 0) return;
1485            below = more.offset().top - results.offset().top - results.height();
1486
1487            if (below <= this.opts.loadMorePadding) {
1488                more.addClass("select2-active");
1489                this.opts.query({
1490                        element: this.opts.element,
1491                        term: term,
1492                        page: page,
1493                        context: context,
1494                        matcher: this.opts.matcher,
1495                        callback: this.bind(function (data) {
1496
1497                    // ignore a response if the select2 has been closed before it was received
1498                    if (!self.opened()) return;
1499
1500
1501                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1502                    self.postprocessResults(data, false, false);
1503
1504                    if (data.more===true) {
1505                        more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
1506                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1507                    } else {
1508                        more.remove();
1509                    }
1510                    self.positionDropdown();
1511                    self.resultsPage = page;
1512                    self.context = data.context;
1513                    this.opts.element.trigger({ type: "select2-loaded", items: data });
1514                })});
1515            }
1516        },
1517
1518        /**
1519         * Default tokenizer function which does nothing
1520         */
1521        tokenize: function() {
1522
1523        },
1524
1525        /**
1526         * @param initial whether or not this is the call to this method right after the dropdown has been opened
1527         */
1528        // abstract
1529        updateResults: function (initial) {
1530            var search = this.search,
1531                results = this.results,
1532                opts = this.opts,
1533                data,
1534                self = this,
1535                input,
1536                term = search.val(),
1537                lastTerm = $.data(this.container, "select2-last-term"),
1538                // sequence number used to drop out-of-order responses
1539                queryNumber;
1540
1541            // prevent duplicate queries against the same term
1542            if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1543
1544            $.data(this.container, "select2-last-term", term);
1545
1546            // if the search is currently hidden we do not alter the results
1547            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1548                return;
1549            }
1550
1551            function postRender() {
1552                search.removeClass("select2-active");
1553                self.positionDropdown();
1554            }
1555
1556            function render(html) {
1557                results.html(html);
1558                postRender();
1559            }
1560
1561            queryNumber = ++this.queryCount;
1562
1563            var maxSelSize = this.getMaximumSelectionSize();
1564            if (maxSelSize >=1) {
1565                data = this.data();
1566                if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1567                    render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>");
1568                    return;
1569                }
1570            }
1571
1572            if (search.val().length < opts.minimumInputLength) {
1573                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1574                    render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
1575                } else {
1576                    render("");
1577                }
1578                if (initial && this.showSearch) this.showSearch(true);
1579                return;
1580            }
1581
1582            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1583                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1584                    render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>");
1585                } else {
1586                    render("");
1587                }
1588                return;
1589            }
1590
1591            if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1592                render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
1593            }
1594
1595            search.addClass("select2-active");
1596
1597            this.removeHighlight();
1598
1599            // give the tokenizer a chance to pre-process the input
1600            input = this.tokenize();
1601            if (input != undefined && input != null) {
1602                search.val(input);
1603            }
1604
1605            this.resultsPage = 1;
1606
1607            opts.query({
1608                element: opts.element,
1609                    term: search.val(),
1610                    page: this.resultsPage,
1611                    context: null,
1612                    matcher: opts.matcher,
1613                    callback: this.bind(function (data) {
1614                var def; // default choice
1615
1616                // ignore old responses
1617                if (queryNumber != this.queryCount) {
1618                  return;
1619                }
1620
1621                // ignore a response if the select2 has been closed before it was received
1622                if (!this.opened()) {
1623                    this.search.removeClass("select2-active");
1624                    return;
1625                }
1626
1627                // save context, if any
1628                this.context = (data.context===undefined) ? null : data.context;
1629                // create a default choice and prepend it to the list
1630                if (this.opts.createSearchChoice && search.val() !== "") {
1631                    def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1632                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1633                        if ($(data.results).filter(
1634                            function () {
1635                                return equal(self.id(this), self.id(def));
1636                            }).length === 0) {
1637                            data.results.unshift(def);
1638                        }
1639                    }
1640                }
1641
1642                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1643                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
1644                    return;
1645                }
1646
1647                results.empty();
1648                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1649
1650                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1651                    results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
1652                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1653                }
1654
1655                this.postprocessResults(data, initial);
1656
1657                postRender();
1658
1659                this.opts.element.trigger({ type: "select2-loaded", items: data });
1660            })});
1661        },
1662
1663        // abstract
1664        cancel: function () {
1665            this.close();
1666        },
1667
1668        // abstract
1669        blur: function () {
1670            // if selectOnBlur == true, select the currently highlighted option
1671            if (this.opts.selectOnBlur)
1672                this.selectHighlighted({noFocus: true});
1673
1674            this.close();
1675            this.container.removeClass("select2-container-active");
1676            // synonymous to .is(':focus'), which is available in jquery >= 1.6
1677            if (this.search[0] === document.activeElement) { this.search.blur(); }
1678            this.clearSearch();
1679            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1680        },
1681
1682        // abstract
1683        focusSearch: function () {
1684            focus(this.search);
1685        },
1686
1687        // abstract
1688        selectHighlighted: function (options) {
1689            var index=this.highlight(),
1690                highlighted=this.results.find(".select2-highlighted:not(.select2-selected)"),
1691                data = highlighted.closest('.select2-result').data("select2-data");
1692                //console.log(highlighted);
1693                //highlighted.removeClass('select2-highlighted');
1694                //console.log(highlighted);
1695
1696            if (data) {
1697                this.highlight(index);
1698                this.onSelect(data, options);
1699            } else if (options && options.noFocus) {
1700                this.close();
1701            }
1702        },
1703
1704        // abstract
1705        getPlaceholder: function () {
1706            var placeholderOption;
1707            return this.opts.element.attr("placeholder") ||
1708                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1709                this.opts.element.data("placeholder") ||
1710                this.opts.placeholder ||
1711                ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
1712        },
1713
1714        // abstract
1715        getPlaceholderOption: function() {
1716            if (this.select) {
1717                var firstOption = this.select.children().first();
1718                if (this.opts.placeholderOption !== undefined ) {
1719                    //Determine the placeholder option based on the specified placeholderOption setting
1720                    return (this.opts.placeholderOption === "first" && firstOption) ||
1721                           (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
1722                } else if (firstOption.text() === "" && firstOption.val() === "") {
1723                    //No explicit placeholder option specified, use the first if it's blank
1724                    return firstOption;
1725                }
1726            }
1727        },
1728
1729        /**
1730         * Get the desired width for the container element.  This is
1731         * derived first from option `width` passed to select2, then
1732         * the inline 'style' on the original element, and finally
1733         * falls back to the jQuery calculated element width.
1734         */
1735        // abstract
1736        initContainerWidth: function () {
1737            function resolveContainerWidth() {
1738                var style, attrs, matches, i, l;
1739
1740                if (this.opts.width === "off") {
1741                    return null;
1742                } else if (this.opts.width === "element"){
1743                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1744                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1745                    // check if there is inline style on the element that contains width
1746                    style = this.opts.element.attr('style');
1747                    if (style !== undefined) {
1748                        attrs = style.split(';');
1749                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
1750                            matches = attrs[i].replace(/\s/g, '')
1751                                .match(/[^-]width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
1752                            if (matches !== null && matches.length >= 1)
1753                                return matches[1];
1754                        }
1755                    }
1756
1757                    if (this.opts.width === "resolve") {
1758                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1759                        // when attached to input type=hidden or elements hidden via css
1760                        style = this.opts.element.css('width');
1761                        if (style.indexOf("%") > 0) return style;
1762
1763                        // finally, fallback on the calculated width of the element
1764                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1765                    }
1766
1767                    return null;
1768                } else if ($.isFunction(this.opts.width)) {
1769                    return this.opts.width();
1770                } else {
1771                    return this.opts.width;
1772               }
1773            };
1774
1775            var width = resolveContainerWidth.call(this);
1776            if (width !== null) {
1777                this.container.css("width", width);
1778            }
1779        }
1780    });
1781
1782    SingleSelect2 = clazz(AbstractSelect2, {
1783
1784        // single
1785
1786        createContainer: function () {
1787            var container = $(document.createElement("div")).attr({
1788                "class": "select2-container"
1789            }).html([
1790                "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>",
1791                "   <span class='select2-chosen'>&nbsp;</span><abbr class='select2-search-choice-close'></abbr>",
1792                "   <span class='select2-arrow'><b></b></span>",
1793                "</a>",
1794                "<input class='select2-focusser select2-offscreen' type='text'/>",
1795                "<div class='select2-drop select2-display-none'>",
1796                "   <div class='select2-search'>",
1797                "       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>",
1798                "   </div>",
1799                "   <ul class='select2-results'>",
1800                "   </ul>",
1801                "</div>"].join(""));
1802            return container;
1803        },
1804
1805        // single
1806        enableInterface: function() {
1807            if (this.parent.enableInterface.apply(this, arguments)) {
1808                this.focusser.prop("disabled", !this.isInterfaceEnabled());
1809            }
1810        },
1811
1812        // single
1813        opening: function () {
1814            var el, range, len;
1815
1816            if (this.opts.minimumResultsForSearch >= 0) {
1817                this.showSearch(true);
1818            }
1819
1820            this.parent.opening.apply(this, arguments);
1821
1822            if (this.showSearchInput !== false) {
1823                // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
1824                // all other browsers handle this just fine
1825
1826                this.search.val(this.focusser.val());
1827            }
1828            this.search.focus();
1829            // move the cursor to the end after focussing, otherwise it will be at the beginning and
1830            // new text will appear *before* focusser.val()
1831            el = this.search.get(0);
1832            if (el.createTextRange) {
1833                range = el.createTextRange();
1834                range.collapse(false);
1835                range.select();
1836            } else if (el.setSelectionRange) {
1837                len = this.search.val().length;
1838                el.setSelectionRange(len, len);
1839            }
1840
1841            // initializes search's value with nextSearchTerm (if defined by user)
1842            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
1843            if(this.search.val() === "") {
1844                if(this.nextSearchTerm != undefined){
1845                    this.search.val(this.nextSearchTerm);
1846                    this.search.select();
1847                }
1848            }
1849
1850            this.focusser.prop("disabled", true).val("");
1851            this.updateResults(true);
1852            this.opts.element.trigger($.Event("select2-open"));
1853        },
1854
1855        // single
1856        close: function (params) {
1857            if (!this.opened()) return;
1858            this.parent.close.apply(this, arguments);
1859
1860            params = params || {focus: true};
1861            this.focusser.removeAttr("disabled");
1862
1863            if (params.focus) {
1864                this.focusser.focus();
1865            }
1866        },
1867
1868        // single
1869        focus: function () {
1870            if (this.opened()) {
1871                this.close();
1872            } else {
1873                this.focusser.removeAttr("disabled");
1874                this.focusser.focus();
1875            }
1876        },
1877
1878        // single
1879        isFocused: function () {
1880            return this.container.hasClass("select2-container-active");
1881        },
1882
1883        // single
1884        cancel: function () {
1885            this.parent.cancel.apply(this, arguments);
1886            this.focusser.removeAttr("disabled");
1887            this.focusser.focus();
1888        },
1889
1890        // single
1891        destroy: function() {
1892            $("label[for='" + this.focusser.attr('id') + "']")
1893                .attr('for', this.opts.element.attr("id"));
1894            this.parent.destroy.apply(this, arguments);
1895        },
1896
1897        // single
1898        initContainer: function () {
1899
1900            var selection,
1901                container = this.container,
1902                dropdown = this.dropdown;
1903
1904            if (this.opts.minimumResultsForSearch < 0) {
1905                this.showSearch(false);
1906            } else {
1907                this.showSearch(true);
1908            }
1909
1910            this.selection = selection = container.find(".select2-choice");
1911
1912            this.focusser = container.find(".select2-focusser");
1913
1914            // rewrite labels from original element to focusser
1915            this.focusser.attr("id", "s2id_autogen"+nextUid());
1916
1917            $("label[for='" + this.opts.element.attr("id") + "']")
1918                .attr('for', this.focusser.attr('id'));
1919
1920            this.focusser.attr("tabindex", this.elementTabIndex);
1921
1922            this.search.on("keydown", this.bind(function (e) {
1923                if (!this.isInterfaceEnabled()) return;
1924
1925                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
1926                    // prevent the page from scrolling
1927                    killEvent(e);
1928                    return;
1929                }
1930
1931                switch (e.which) {
1932                    case KEY.UP:
1933                    case KEY.DOWN:
1934                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
1935                        killEvent(e);
1936                        return;
1937                    case KEY.ENTER:
1938                        this.selectHighlighted();
1939                        killEvent(e);
1940                        return;
1941                    case KEY.TAB:
1942                        this.selectHighlighted({noFocus: true});
1943                        return;
1944                    case KEY.ESC:
1945                        this.cancel(e);
1946                        killEvent(e);
1947                        return;
1948                }
1949            }));
1950
1951            this.search.on("blur", this.bind(function(e) {
1952                // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
1953                // without this the search field loses focus which is annoying
1954                if (document.activeElement === this.body().get(0)) {
1955                    window.setTimeout(this.bind(function() {
1956                        this.search.focus();
1957                    }), 0);
1958                }
1959            }));
1960
1961            this.focusser.on("keydown", this.bind(function (e) {
1962                if (!this.isInterfaceEnabled()) return;
1963
1964                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
1965                    return;
1966                }
1967
1968                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
1969                    killEvent(e);
1970                    return;
1971                }
1972
1973                if (e.which == KEY.DOWN || e.which == KEY.UP
1974                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
1975
1976                    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
1977
1978                    this.open();
1979                    killEvent(e);
1980                    return;
1981                }
1982
1983                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
1984                    if (this.opts.allowClear) {
1985                        this.clear();
1986                    }
1987                    killEvent(e);
1988                    return;
1989                }
1990            }));
1991
1992
1993            installKeyUpChangeEvent(this.focusser);
1994            this.focusser.on("keyup-change input", this.bind(function(e) {
1995                if (this.opts.minimumResultsForSearch >= 0) {
1996                    e.stopPropagation();
1997                    if (this.opened()) return;
1998                    this.open();
1999                }
2000            }));
2001
2002            selection.on("mousedown", "abbr", this.bind(function (e) {
2003                if (!this.isInterfaceEnabled()) return;
2004                this.clear();
2005                killEventImmediately(e);
2006                this.close();
2007                this.selection.focus();
2008            }));
2009
2010            selection.on("mousedown", this.bind(function (e) {
2011
2012                if (!this.container.hasClass("select2-container-active")) {
2013                    this.opts.element.trigger($.Event("select2-focus"));
2014                }
2015
2016                if (this.opened()) {
2017                    this.close();
2018                } else if (this.isInterfaceEnabled()) {
2019                    this.open();
2020                }
2021
2022                killEvent(e);
2023            }));
2024
2025            dropdown.on("mousedown", this.bind(function() { this.search.focus(); }));
2026
2027            selection.on("focus", this.bind(function(e) {
2028                killEvent(e);
2029            }));
2030
2031            this.focusser.on("focus", this.bind(function(){
2032                if (!this.container.hasClass("select2-container-active")) {
2033                    this.opts.element.trigger($.Event("select2-focus"));
2034                }
2035                this.container.addClass("select2-container-active");
2036            })).on("blur", this.bind(function() {
2037                if (!this.opened()) {
2038                    this.container.removeClass("select2-container-active");
2039                    this.opts.element.trigger($.Event("select2-blur"));
2040                }
2041            }));
2042            this.search.on("focus", this.bind(function(){
2043                if (!this.container.hasClass("select2-container-active")) {
2044                    this.opts.element.trigger($.Event("select2-focus"));
2045                }
2046                this.container.addClass("select2-container-active");
2047            }));
2048
2049            this.initContainerWidth();
2050            this.opts.element.addClass("select2-offscreen");
2051            this.setPlaceholder();
2052
2053        },
2054
2055        // single
2056        clear: function(triggerChange) {
2057            var data=this.selection.data("select2-data");
2058            if (data) { // guard against queued quick consecutive clicks
2059                var evt = $.Event("select2-clearing");
2060                this.opts.element.trigger(evt);
2061                if (evt.isDefaultPrevented()) {
2062                    return;
2063                }
2064                var placeholderOption = this.getPlaceholderOption();
2065                this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2066                this.selection.find(".select2-chosen").empty();
2067                this.selection.removeData("select2-data");
2068                this.setPlaceholder();
2069
2070                if (triggerChange !== false){
2071                    this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2072                    this.triggerChange({removed:data});
2073                }
2074            }
2075        },
2076
2077        /**
2078         * Sets selection based on source element's value
2079         */
2080        // single
2081        initSelection: function () {
2082            var selected;
2083            if (this.isPlaceholderOptionSelected()) {
2084                this.updateSelection(null);
2085                this.close();
2086                this.setPlaceholder();
2087            } else {
2088                var self = this;
2089                this.opts.initSelection.call(null, this.opts.element, function(selected){
2090                    if (selected !== undefined && selected !== null) {
2091                        self.updateSelection(selected);
2092                        self.close();
2093                        self.setPlaceholder();
2094                    }
2095                });
2096            }
2097        },
2098
2099        isPlaceholderOptionSelected: function() {
2100            var placeholderOption;
2101            if (!this.getPlaceholder()) return false; // no placeholder specified so no option should be considered
2102            return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.is(':selected'))
2103                || (this.opts.element.val() === "")
2104                || (this.opts.element.val() === undefined)
2105                || (this.opts.element.val() === null);
2106        },
2107
2108        // single
2109        prepareOpts: function () {
2110            var opts = this.parent.prepareOpts.apply(this, arguments),
2111                self=this;
2112
2113            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2114                // install the selection initializer
2115                opts.initSelection = function (element, callback) {
2116                    var selected = element.find(":selected");
2117                    // a single select box always has a value, no need to null check 'selected'
2118                    callback(self.optionToData(selected));
2119                };
2120            } else if ("data" in opts) {
2121                // install default initSelection when applied to hidden input and data is local
2122                opts.initSelection = opts.initSelection || function (element, callback) {
2123                    var id = element.val();
2124                    //search in data by id, storing the actual matching item
2125                    var match = null;
2126                    opts.query({
2127                        matcher: function(term, text, el){
2128                            var is_match = equal(id, opts.id(el));
2129                            if (is_match) {
2130                                match = el;
2131                            }
2132                            return is_match;
2133                        },
2134                        callback: !$.isFunction(callback) ? $.noop : function() {
2135                            callback(match);
2136                        }
2137                    });
2138                };
2139            }
2140
2141            return opts;
2142        },
2143
2144        // single
2145        getPlaceholder: function() {
2146            // if a placeholder is specified on a single select without a valid placeholder option ignore it
2147            if (this.select) {
2148                if (this.getPlaceholderOption() === undefined) {
2149                    return undefined;
2150                }
2151            }
2152
2153            return this.parent.getPlaceholder.apply(this, arguments);
2154        },
2155
2156        // single
2157        setPlaceholder: function () {
2158            var placeholder = this.getPlaceholder();
2159
2160            if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2161
2162                // check for a placeholder option if attached to a select
2163                if (this.select && this.getPlaceholderOption() === undefined) return;
2164
2165                this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2166
2167                this.selection.addClass("select2-default");
2168
2169                this.container.removeClass("select2-allowclear");
2170            }
2171        },
2172
2173        // single
2174        postprocessResults: function (data, initial, noHighlightUpdate) {
2175            var selected = 0, self = this, showSearchInput = true;
2176
2177            // find the selected element in the result list
2178
2179            this.findHighlightableChoices().each2(function (i, elm) {
2180                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2181                    selected = i;
2182                    return false;
2183                }
2184            });
2185
2186            // and highlight it
2187            if (noHighlightUpdate !== false) {
2188                if (initial === true && selected >= 0) {
2189                    this.highlight(selected);
2190                } else {
2191                    this.highlight(0);
2192                }
2193            }
2194
2195            // hide the search box if this is the first we got the results and there are enough of them for search
2196
2197            if (initial === true) {
2198                var min = this.opts.minimumResultsForSearch;
2199                if (min >= 0) {
2200                    this.showSearch(countResults(data.results) >= min);
2201                }
2202            }
2203        },
2204
2205        // single
2206        showSearch: function(showSearchInput) {
2207            if (this.showSearchInput === showSearchInput) return;
2208
2209            this.showSearchInput = showSearchInput;
2210
2211            this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2212            this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2213            //add "select2-with-searchbox" to the container if search box is shown
2214            $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2215        },
2216
2217        // single
2218        onSelect: function (data, options) {
2219
2220            if (!this.triggerSelect(data)) { return; }
2221
2222            var old = this.opts.element.val(),
2223                oldData = this.data();
2224
2225            this.opts.element.val(this.id(data));
2226            this.updateSelection(data);
2227
2228            this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2229
2230            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2231            this.close();
2232
2233            if (!options || !options.noFocus)
2234                this.focusser.focus();
2235
2236            if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); }
2237        },
2238
2239        // single
2240        updateSelection: function (data) {
2241
2242            var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2243
2244            this.selection.data("select2-data", data);
2245
2246            container.empty();
2247            if (data !== null) {
2248                formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2249            }
2250            if (formatted !== undefined) {
2251                container.append(formatted);
2252            }
2253            cssClass=this.opts.formatSelectionCssClass(data, container);
2254            if (cssClass !== undefined) {
2255                container.addClass(cssClass);
2256            }
2257
2258            this.selection.removeClass("select2-default");
2259
2260            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2261                this.container.addClass("select2-allowclear");
2262            }
2263        },
2264
2265        // single
2266        val: function () {
2267            var val,
2268                triggerChange = false,
2269                data = null,
2270                self = this,
2271                oldData = this.data();
2272
2273            if (arguments.length === 0) {
2274                return this.opts.element.val();
2275            }
2276
2277            val = arguments[0];
2278
2279            if (arguments.length > 1) {
2280                triggerChange = arguments[1];
2281            }
2282
2283            if (this.select) {
2284                this.select
2285                    .val(val)
2286                    .find(":selected").each2(function (i, elm) {
2287                        data = self.optionToData(elm);
2288                        return false;
2289                    });
2290                this.updateSelection(data);
2291                this.setPlaceholder();
2292                if (triggerChange) {
2293                    this.triggerChange({added: data, removed:oldData});
2294                }
2295            } else {
2296                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2297                if (!val && val !== 0) {
2298                    this.clear(triggerChange);
2299                    return;
2300                }
2301                if (this.opts.initSelection === undefined) {
2302                    throw new Error("cannot call val() if initSelection() is not defined");
2303                }
2304                this.opts.element.val(val);
2305                this.opts.initSelection(this.opts.element, function(data){
2306                    self.opts.element.val(!data ? "" : self.id(data));
2307                    self.updateSelection(data);
2308                    self.setPlaceholder();
2309                    if (triggerChange) {
2310                        self.triggerChange({added: data, removed:oldData});
2311                    }
2312                });
2313            }
2314        },
2315
2316        // single
2317        clearSearch: function () {
2318            this.search.val("");
2319            this.focusser.val("");
2320        },
2321
2322        // single
2323        data: function(value) {
2324            var data,
2325                triggerChange = false;
2326
2327            if (arguments.length === 0) {
2328                data = this.selection.data("select2-data");
2329                if (data == undefined) data = null;
2330                return data;
2331            } else {
2332                if (arguments.length > 1) {
2333                    triggerChange = arguments[1];
2334                }
2335                if (!value) {
2336                    this.clear(triggerChange);
2337                } else {
2338                    data = this.data();
2339                    this.opts.element.val(!value ? "" : this.id(value));
2340                    this.updateSelection(value);
2341                    if (triggerChange) {
2342                        this.triggerChange({added: value, removed:data});
2343                    }
2344                }
2345            }
2346        }
2347    });
2348
2349    MultiSelect2 = clazz(AbstractSelect2, {
2350
2351        // multi
2352        createContainer: function () {
2353            var container = $(document.createElement("div")).attr({
2354                "class": "select2-container select2-container-multi"
2355            }).html([
2356                "<ul class='select2-choices'>",
2357                "  <li class='select2-search-field'>",
2358                "    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2359                "  </li>",
2360                "</ul>",
2361                "<div class='select2-drop select2-drop-multi select2-display-none'>",
2362                "   <ul class='select2-results'>",
2363                "   </ul>",
2364                "</div>"].join(""));
2365            return container;
2366        },
2367
2368        // multi
2369        prepareOpts: function () {
2370            var opts = this.parent.prepareOpts.apply(this, arguments),
2371                self=this;
2372
2373            // TODO validate placeholder is a string if specified
2374
2375            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2376                // install sthe selection initializer
2377                opts.initSelection = function (element, callback) {
2378
2379                    var data = [];
2380
2381                    element.find(":selected").each2(function (i, elm) {
2382                        data.push(self.optionToData(elm));
2383                    });
2384                    callback(data);
2385                };
2386            } else if ("data" in opts) {
2387                // install default initSelection when applied to hidden input and data is local
2388                opts.initSelection = opts.initSelection || function (element, callback) {
2389                    var ids = splitVal(element.val(), opts.separator);
2390                    //search in data by array of ids, storing matching items in a list
2391                    var matches = [];
2392                    opts.query({
2393                        matcher: function(term, text, el){
2394                            var is_match = $.grep(ids, function(id) {
2395                                return equal(id, opts.id(el));
2396                            }).length;
2397                            if (is_match) {
2398                                matches.push(el);
2399                            }
2400                            return is_match;
2401                        },
2402                        callback: !$.isFunction(callback) ? $.noop : function() {
2403                            // reorder matches based on the order they appear in the ids array because right now
2404                            // they are in the order in which they appear in data array
2405                            var ordered = [];
2406                            for (var i = 0; i < ids.length; i++) {
2407                                var id = ids[i];
2408                                for (var j = 0; j < matches.length; j++) {
2409                                    var match = matches[j];
2410                                    if (equal(id, opts.id(match))) {
2411                                        ordered.push(match);
2412                                        matches.splice(j, 1);
2413                                        break;
2414                                    }
2415                                }
2416                            }
2417                            callback(ordered);
2418                        }
2419                    });
2420                };
2421            }
2422
2423            return opts;
2424        },
2425
2426        selectChoice: function (choice) {
2427
2428            var selected = this.container.find(".select2-search-choice-focus");
2429            if (selected.length && choice && choice[0] == selected[0]) {
2430
2431            } else {
2432                if (selected.length) {
2433                    this.opts.element.trigger("choice-deselected", selected);
2434                }
2435                selected.removeClass("select2-search-choice-focus");
2436                if (choice && choice.length) {
2437                    this.close();
2438                    choice.addClass("select2-search-choice-focus");
2439                    this.opts.element.trigger("choice-selected", choice);
2440                }
2441            }
2442        },
2443
2444        // multi
2445        destroy: function() {
2446            $("label[for='" + this.search.attr('id') + "']")
2447                .attr('for', this.opts.element.attr("id"));
2448            this.parent.destroy.apply(this, arguments);
2449        },
2450
2451        // multi
2452        initContainer: function () {
2453
2454            var selector = ".select2-choices", selection;
2455
2456            this.searchContainer = this.container.find(".select2-search-field");
2457            this.selection = selection = this.container.find(selector);
2458
2459            var _this = this;
2460            this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
2461                //killEvent(e);
2462                _this.search[0].focus();
2463                _this.selectChoice($(this));
2464            });
2465
2466            // rewrite labels from original element to focusser
2467            this.search.attr("id", "s2id_autogen"+nextUid());
2468            $("label[for='" + this.opts.element.attr("id") + "']")
2469                .attr('for', this.search.attr('id'));
2470
2471            this.search.on("input paste", this.bind(function() {
2472                if (!this.isInterfaceEnabled()) return;
2473                if (!this.opened()) {
2474                    this.open();
2475                }
2476            }));
2477
2478            this.search.attr("tabindex", this.elementTabIndex);
2479
2480            this.keydowns = 0;
2481            this.search.on("keydown", this.bind(function (e) {
2482                if (!this.isInterfaceEnabled()) return;
2483
2484                ++this.keydowns;
2485                var selected = selection.find(".select2-search-choice-focus");
2486                var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2487                var next = selected.next(".select2-search-choice:not(.select2-locked)");
2488                var pos = getCursorInfo(this.search);
2489
2490                if (selected.length &&
2491                    (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2492                    var selectedChoice = selected;
2493                    if (e.which == KEY.LEFT && prev.length) {
2494                        selectedChoice = prev;
2495                    }
2496                    else if (e.which == KEY.RIGHT) {
2497                        selectedChoice = next.length ? next : null;
2498                    }
2499                    else if (e.which === KEY.BACKSPACE) {
2500                        this.unselect(selected.first());
2501                        this.search.width(10);
2502                        selectedChoice = prev.length ? prev : next;
2503                    } else if (e.which == KEY.DELETE) {
2504                        this.unselect(selected.first());
2505                        this.search.width(10);
2506                        selectedChoice = next.length ? next : null;
2507                    } else if (e.which == KEY.ENTER) {
2508                        selectedChoice = null;
2509                    }
2510
2511                    this.selectChoice(selectedChoice);
2512                    killEvent(e);
2513                    if (!selectedChoice || !selectedChoice.length) {
2514                        this.open();
2515                    }
2516                    return;
2517                } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2518                    || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2519
2520                    this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2521                    killEvent(e);
2522                    return;
2523                } else {
2524                    this.selectChoice(null);
2525                }
2526
2527                if (this.opened()) {
2528                    switch (e.which) {
2529                    case KEY.UP:
2530                    case KEY.DOWN:
2531                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2532                        killEvent(e);
2533                        return;
2534                    case KEY.ENTER:
2535                        this.selectHighlighted();
2536                        killEvent(e);
2537                        return;
2538                    case KEY.TAB:
2539                        this.selectHighlighted({noFocus:true});
2540                        this.close();
2541                        return;
2542                    case KEY.ESC:
2543                        this.cancel(e);
2544                        killEvent(e);
2545                        return;
2546                    }
2547                }
2548
2549                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2550                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2551                    return;
2552                }
2553
2554                if (e.which === KEY.ENTER) {
2555                    if (this.opts.openOnEnter === false) {
2556                        return;
2557                    } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2558                        return;
2559                    }
2560                }
2561
2562                this.open();
2563
2564                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2565                    // prevent the page from scrolling
2566                    killEvent(e);
2567                }
2568
2569                if (e.which === KEY.ENTER) {
2570                    // prevent form from being submitted
2571                    killEvent(e);
2572                }
2573
2574            }));
2575
2576            this.search.on("keyup", this.bind(function (e) {
2577                this.keydowns = 0;
2578                this.resizeSearch();
2579            })
2580            );
2581
2582            this.search.on("blur", this.bind(function(e) {
2583                this.container.removeClass("select2-container-active");
2584                this.search.removeClass("select2-focused");
2585                this.selectChoice(null);
2586                if (!this.opened()) this.clearSearch();
2587                e.stopImmediatePropagation();
2588                this.opts.element.trigger($.Event("select2-blur"));
2589            }));
2590
2591            this.container.on("click", selector, this.bind(function (e) {
2592                if (!this.isInterfaceEnabled()) return;
2593                if ($(e.target).closest(".select2-search-choice").length > 0) {
2594                    // clicked inside a select2 search choice, do not open
2595                    return;
2596                }
2597                this.selectChoice(null);
2598                this.clearPlaceholder();
2599                if (!this.container.hasClass("select2-container-active")) {
2600                    this.opts.element.trigger($.Event("select2-focus"));
2601                }
2602                this.open();
2603                this.focusSearch();
2604                e.preventDefault();
2605            }));
2606
2607            this.container.on("focus", selector, this.bind(function () {
2608                if (!this.isInterfaceEnabled()) return;
2609                if (!this.container.hasClass("select2-container-active")) {
2610                    this.opts.element.trigger($.Event("select2-focus"));
2611                }
2612                this.container.addClass("select2-container-active");
2613                this.dropdown.addClass("select2-drop-active");
2614                this.clearPlaceholder();
2615            }));
2616
2617            this.initContainerWidth();
2618            this.opts.element.addClass("select2-offscreen");
2619
2620            // set the placeholder if necessary
2621            this.clearSearch();
2622        },
2623
2624        // multi
2625        enableInterface: function() {
2626            if (this.parent.enableInterface.apply(this, arguments)) {
2627                this.search.prop("disabled", !this.isInterfaceEnabled());
2628            }
2629        },
2630
2631        // multi
2632        initSelection: function () {
2633            var data;
2634            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2635                this.updateSelection([]);
2636                this.close();
2637                // set the placeholder if necessary
2638                this.clearSearch();
2639            }
2640            if (this.select || this.opts.element.val() !== "") {
2641                var self = this;
2642                this.opts.initSelection.call(null, this.opts.element, function(data){
2643                    if (data !== undefined && data !== null) {
2644                        self.updateSelection(data);
2645                        self.close();
2646                        // set the placeholder if necessary
2647                        self.clearSearch();
2648                    }
2649                });
2650            }
2651        },
2652
2653        // multi
2654        clearSearch: function () {
2655            var placeholder = this.getPlaceholder(),
2656                maxWidth = this.getMaxSearchWidth();
2657
2658            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2659                this.search.val(placeholder).addClass("select2-default");
2660                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2661                // 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
2662                this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
2663            } else {
2664                this.search.val("").width(10);
2665            }
2666        },
2667
2668        // multi
2669        clearPlaceholder: function () {
2670            if (this.search.hasClass("select2-default")) {
2671                this.search.val("").removeClass("select2-default");
2672            }
2673        },
2674
2675        // multi
2676        opening: function () {
2677            this.clearPlaceholder(); // should be done before super so placeholder is not used to search
2678            this.resizeSearch();
2679
2680            this.parent.opening.apply(this, arguments);
2681
2682            this.focusSearch();
2683
2684            this.updateResults(true);
2685            this.search.focus();
2686            this.opts.element.trigger($.Event("select2-open"));
2687        },
2688
2689        // multi
2690        close: function () {
2691            if (!this.opened()) return;
2692            this.parent.close.apply(this, arguments);
2693        },
2694
2695        // multi
2696        focus: function () {
2697            this.close();
2698            this.search.focus();
2699        },
2700
2701        // multi
2702        isFocused: function () {
2703            return this.search.hasClass("select2-focused");
2704        },
2705
2706        // multi
2707        updateSelection: function (data) {
2708            var ids = [], filtered = [], self = this;
2709
2710            // filter out duplicates
2711            $(data).each(function () {
2712                if (indexOf(self.id(this), ids) < 0) {
2713                    ids.push(self.id(this));
2714                    filtered.push(this);
2715                }
2716            });
2717            data = filtered;
2718
2719            this.selection.find(".select2-search-choice").remove();
2720            $(data).each(function () {
2721                self.addSelectedChoice(this);
2722            });
2723            self.postprocessResults();
2724        },
2725
2726        // multi
2727        tokenize: function() {
2728            var input = this.search.val();
2729            input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
2730            if (input != null && input != undefined) {
2731                this.search.val(input);
2732                if (input.length > 0) {
2733                    this.open();
2734                }
2735            }
2736
2737        },
2738
2739        // multi
2740        onSelect: function (data, options) {
2741
2742            if (!this.triggerSelect(data)) { return; }
2743
2744            this.addSelectedChoice(data);
2745
2746            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2747
2748            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
2749
2750            if (this.opts.closeOnSelect) {
2751                this.close();
2752                this.search.width(10);
2753            } else {
2754                if (this.countSelectableResults()>0) {
2755                    this.search.width(10);
2756                    this.resizeSearch();
2757                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
2758                        // if we reached max selection size repaint the results so choices
2759                        // are replaced with the max selection reached message
2760                        this.updateResults(true);
2761                    }
2762                    this.positionDropdown();
2763                } else {
2764                    // if nothing left to select close
2765                    this.close();
2766                    this.search.width(10);
2767                }
2768            }
2769
2770            // since its not possible to select an element that has already been
2771            // added we do not need to check if this is a new element before firing change
2772            this.triggerChange({ added: data });
2773
2774            if (!options || !options.noFocus)
2775                this.focusSearch();
2776        },
2777
2778        // multi
2779        cancel: function () {
2780            this.close();
2781            this.focusSearch();
2782        },
2783
2784        addSelectedChoice: function (data) {
2785            var enableChoice = !data.locked,
2786                enabledItem = $(
2787                    "<li class='select2-search-choice'>" +
2788                    "    <div></div>" +
2789                    "    <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
2790                    "</li>"),
2791                disabledItem = $(
2792                    "<li class='select2-search-choice select2-locked'>" +
2793                    "<div></div>" +
2794                    "</li>");
2795            var choice = enableChoice ? enabledItem : disabledItem,
2796                id = this.id(data),
2797                val = this.getVal(),
2798                formatted,
2799                cssClass;
2800
2801            formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
2802            if (formatted != undefined) {
2803                choice.find("div").replaceWith("<div>"+formatted+"</div>");
2804            }
2805            cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
2806            if (cssClass != undefined) {
2807                choice.addClass(cssClass);
2808            }
2809
2810            if(enableChoice){
2811              choice.find(".select2-search-choice-close")
2812                  .on("mousedown", killEvent)
2813                  .on("click dblclick", this.bind(function (e) {
2814                  if (!this.isInterfaceEnabled()) return;
2815
2816                  $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
2817                      this.unselect($(e.target));
2818                      this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
2819                      this.close();
2820                      thi
2820s.focusSearch();
2821                  })).dequeue();
2822                  killEvent(e);
2823              })).on("focus", this.bind(function () {
2824                  if (!this.isInterfaceEnabled()) return;
2825                  this.container.addClass("select2-container-active");
2826                  this.dropdown.addClass("select2-drop-active");
2827              }));
2828            }
2829
2830            choice.data("select2-data", data);
2831            choice.insertBefore(this.searchContainer);
2832
2833            val.push(id);
2834            this.setVal(val);
2835        },
2836
2837        // multi
2838        unselect: function (selected) {
2839            var val = this.getVal(),
2840                data,
2841                index;
2842
2843            selected = selected.closest(".select2-search-choice");
2844
2845            if (selected.length === 0) {
2846                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
2847            }
2848
2849            data = selected.data("select2-data");
2850
2851            if (!data) {
2852                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
2853                // and invoked on an element already removed
2854                return;
2855            }
2856
2857            while((index = indexOf(this.id(data), val)) >= 0) {
2858                val.splice(index, 1);
2859                this.setVal(val);
2860                if (this.select) this.postprocessResults();
2861            }
2862            selected.remove();
2863
2864            this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
2865            this.triggerChange({ removed: data });
2866        },
2867
2868        // multi
2869        postprocessResults: function (data, initial, noHighlightUpdate) {
2870            var val = this.getVal(),
2871                choices = this.results.find(".select2-result"),
2872                compound = this.results.find(".select2-result-with-children"),
2873                self = this;
2874
2875            choices.each2(function (i, choice) {
2876                var id = self.id(choice.data("select2-data"));
2877                if (indexOf(id, val) >= 0) {
2878                    choice.addClass("select2-selected");
2879                    // mark all children of the selected parent as selected
2880                    choice.find(".select2-result-selectable").addClass("select2-selected");
2881                }
2882            });
2883
2884            compound.each2(function(i, choice) {
2885                // hide an optgroup if it doesnt have any selectable children
2886                if (!choice.is('.select2-result-selectable')
2887                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
2888                    choice.addClass("select2-selected");
2889                }
2890            });
2891
2892            if (this.highlight() == -1 && noHighlightUpdate !== false){
2893                self.highlight(0);
2894            }
2895
2896            //If all results are chosen render formatNoMAtches
2897            if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
2898                if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
2899                    if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
2900                        this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>");
2901                    }
2902                }
2903            }
2904
2905        },
2906
2907        // multi
2908        getMaxSearchWidth: function() {
2909            return this.selection.width() - getSideBorderPadding(this.search);
2910        },
2911
2912        // multi
2913        resizeSearch: function () {
2914            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
2915                sideBorderPadding = getSideBorderPadding(this.search);
2916
2917            minimumWidth = measureTextWidth(this.search) + 10;
2918
2919            left = this.search.offset().left;
2920
2921            maxWidth = this.selection.width();
2922            containerLeft = this.selection.offset().left;
2923
2924            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
2925
2926            if (searchWidth < minimumWidth) {
2927                searchWidth = maxWidth - sideBorderPadding;
2928            }
2929
2930            if (searchWidth < 40) {
2931                searchWidth = maxWidth - sideBorderPadding;
2932            }
2933
2934            if (searchWidth <= 0) {
2935              searchWidth = minimumWidth;
2936            }
2937
2938            this.search.width(Math.floor(searchWidth));
2939        },
2940
2941        // multi
2942        getVal: function () {
2943            var val;
2944            if (this.select) {
2945                val = this.select.val();
2946                return val === null ? [] : val;
2947            } else {
2948                val = this.opts.element.val();
2949                return splitVal(val, this.opts.separator);
2950            }
2951        },
2952
2953        // multi
2954        setVal: function (val) {
2955            var unique;
2956            if (this.select) {
2957                this.select.val(val);
2958            } else {
2959                unique = [];
2960                // filter out duplicates
2961                $(val).each(function () {
2962                    if (indexOf(this, unique) < 0) unique.push(this);
2963                });
2964                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
2965            }
2966        },
2967
2968        // multi
2969        buildChangeDetails: function (old, current) {
2970            var current = current.slice(0),
2971                old = old.slice(0);
2972
2973            // remove intersection from each array
2974            for (var i = 0; i < current.length; i++) {
2975                for (var j = 0; j < old.length; j++) {
2976                    if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
2977                        current.splice(i, 1);
2978                        i--;
2979                        old.splice(j, 1);
2980                        j--;
2981                    }
2982                }
2983            }
2984
2985            return {added: current, removed: old};
2986        },
2987
2988
2989        // multi
2990        val: function (val, triggerChange) {
2991            var oldData, self=this, changeDetails;
2992
2993            if (arguments.length === 0) {
2994                return this.getVal();
2995            }
2996
2997            oldData=this.data();
2998            if (!oldData.length) oldData=[];
2999
3000            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3001            if (!val && val !== 0) {
3002                this.opts.element.val("");
3003                this.updateSelection([]);
3004                this.clearSearch();
3005                if (triggerChange) {
3006                    this.triggerChange({added: this.data(), removed: oldData});
3007                }
3008                return;
3009            }
3010
3011            // val is a list of ids
3012            this.setVal(val);
3013
3014            if (this.select) {
3015                this.opts.initSelection(this.select, this.bind(this.updateSelection));
3016                if (triggerChange) {
3017                    this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3018                }
3019            } else {
3020                if (this.opts.initSelection === undefined) {
3021                    throw new Error("val() cannot be called if initSelection() is not defined");
3022                }
3023
3024                this.opts.initSelection(this.opts.element, function(data){
3025                    var ids=$.map(data, self.id);
3026                    self.setVal(ids);
3027                    self.updateSelection(data);
3028                    self.clearSearch();
3029                    if (triggerChange) {
3030                        self.triggerChange(self.buildChangeDetails(oldData, this.data()));
3031                    }
3032                });
3033            }
3034            this.clearSearch();
3035        },
3036
3037        // multi
3038        onSortStart: function() {
3039            if (this.select) {
3040                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3041            }
3042
3043            // collapse search field into 0 width so its container can be collapsed as well
3044            this.search.width(0);
3045            // hide the container
3046            this.searchContainer.hide();
3047        },
3048
3049        // multi
3050        onSortEnd:function() {
3051
3052            var val=[], self=this;
3053
3054            // show search and move it to the end of the list
3055            this.searchContainer.show();
3056            // make sure the search container is the last item in the list
3057            this.searchContainer.appendTo(this.searchContainer.parent());
3058            // since we collapsed the width in dragStarted, we resize it here
3059            this.resizeSearch();
3060
3061            // update selection
3062            this.selection.find(".select2-search-choice").each(function() {
3063                val.push(self.opts.id($(this).data("select2-data")));
3064            });
3065            this.setVal(val);
3066            this.triggerChange();
3067        },
3068
3069        // multi
3070        data: function(values, triggerChange) {
3071            var self=this, ids, old;
3072            if (arguments.length === 0) {
3073                 return this.selection
3074                     .find(".select2-search-choice")
3075                     .map(function() { return $(this).data("select2-data"); })
3076                     .get();
3077            } else {
3078                old = this.data();
3079                if (!values) { values = []; }
3080                ids = $.map(values, function(e) { return self.opts.id(e); });
3081                this.setVal(ids);
3082                this.updateSelection(values);
3083                this.clearSearch();
3084                if (triggerChange) {
3085                    this.triggerChange(this.buildChangeDetails(old, this.data()));
3086                }
3087            }
3088        }
3089    });
3090
3091    $.fn.select2 = function () {
3092
3093        var args = Array.prototype.slice.call(arguments, 0),
3094            opts,
3095            select2,
3096            method, value, multiple,
3097            allowedMethods = ["val", "destroy", "opened", "open", "close", "focu
3097s", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3098            valueMethods = ["opened", "isFocused", "container", "dropdown"],
3099            propertyMethods = ["val", "data"],
3100            methodsMap = { search: "externalSearch" };
3101
3102        this.each(function () {
3103            if (args.length === 0 || typeof(args[0]) === "object") {
3104                opts = args.length === 0 ? {} : $.extend({}, args[0]);
3105                opts.element = $(this);
3106
3107                if (opts.element.get(0).tagName.toLowerCase() === "select") {
3108                    multiple = opts.element.prop("multiple");
3109                } else {
3110                    multiple = opts.multiple || false;
3111                    if ("tags" in opts) {opts.multiple = multiple = true;}
3112                }
3113
3114                select2 = multiple ? new MultiSelect2() : new SingleSelect2();
3115                select2.init(opts);
3116            } else if (typeof(args[0]) === "string") {
3117
3118                if (indexOf(args[0], allowedMethods) < 0) {
3119                    throw "Unknown method: " + args[0];
3120                }
3121
3122                value = undefined;
3123                select2 = $(this).data("select2");
3124                if (select2 === undefined) return;
3125
3126                method=args[0];
3127
3128                if (method === "container") {
3129                    value = select2.container;
3130                } else if (method === "dropdown") {
3131                    value = select2.dropdown;
3132                } else {
3133                    if (methodsMap[method]) method = methodsMap[method];
3134
3135                    value = select2[method].apply(select2, args.slice(1));
3136                }
3137                if (indexOf(args[0], valueMethods) >= 0
3138                    || (indexOf(args[0], propertyMethods) && args.length == 1)) {
3139                    return false; // abort the iteration, ready to return first matched value
3140                }
3141            } else {
3142                throw "Invalid arguments to select2 plugin: " + args;
3143            }
3144        });
3145        return (value === undefined) ? this : value;
3146    };
3147
3148    // plugin defaults, accessible to users
3149    $.fn.select2.defaults = {
3150        width: "copy",
3151        loadMorePadding: 0,
3152        closeOnSelect: true,
3153        openOnEnter: true,
3154        containerCss: {},
3155        dropdownCss: {},
3156        containerCssClass: "",
3157        dropdownCssClass: "",
3158        formatResult: function(result, container, query, escapeMarkup) {
3159            var markup=[];
3160            markMatch(result.text, query.term, markup, escapeMarkup);
3161            return markup.join("");
3162        },
3163        formatSelection: function (data, container, escapeMarkup) {
3164            return data ? escapeMarkup(data.text) : undefined;
3165        },
3166        sortResults: function (results, container, query) {
3167            return results;
3168        },
3169        formatResultCssClass: function(data) {return undefined;},
3170        formatSelectionCssClass: function(data, container) {return undefined;},
3171        formatNoMatches: function () { return "No matches found"; },
3172        formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
3173        formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
3174        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3175        formatLoadMore: function (pageNumber) { return "Loading more results..."; },
3176        formatSearching: function () { return "Searching..."; },
3177        minimumResultsForSearch: 0,
3178        minimumInputLength: 0,
3179        maximumInputLength: null,
3180        maximumSelectionSize: 0,
3181        id: function (e) { return e.id; },
3182        matcher: function(term, text) {
3183            return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3184        },
3185        separator: ",",
3186        tokenSeparators: [],
3187        tokenizer: defaultTokenizer,
3188        escapeMarkup: defaultEscapeMarkup,
3189        blurOnChange: false,
3190        selectOnBlur: false,
3191        adaptContainerCssClass: function(c) { return c; },
3192        adaptDropdownCssClass: function(c) { return null; },
3193        nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; }
3194    };
3195
3196    $.fn.select2.ajaxDefaults = {
3197        transport: $.ajax,
3198        params: {
3199            type: "GET",
3200            cache: false,
3201            dataType: "json"
3202        }
3203    };
3204
3205    // exports
3206    window.Select2 = {
3207        query: {
3208            ajax: ajax,
3209            local: local,
3210            tags: tags
3211        }, util: {
3212            debounce: debounce,
3213            markMatch: markMatch,
3214            escapeMarkup: defaultEscapeMarkup,
3215            stripDiacritics: stripDiacritics
3216        }, "class": {
3217            "abstract": AbstractSelect2,
3218            "single": SingleSelect2,
3219            "multi": MultiSelect2
3220        }
3221    };
3222
3223}(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.