PageSourceSearch

https://portal.uicalaska.com/Scripts/select2.js

js uicalaska.com collected 2026-09-25 04:12:42 UTC 152,077 bytes, 3,541 lines download raw bytes

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