Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

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