PageSourceSearch

https://ma.moscowpartners.com/Serenity.Assets/Scripts/select2.js?v=n5Crdtq374K0g4jRmqm5DA

js moscowpartners.com collected 2026-10-07 15:16:35 UTC 147,437 bytes, 3,511 lines download raw bytes

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