Project

General

Profile

1
/*
2
Copyright 2012 Igor Vaynberg
3

    
4
Version: 3.5.4 Timestamp: Sun Aug 30 13:30:32 EDT 2015
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 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(document.body);
136

    
137
        var dim = {
138
            width: $template.width() - $template[0].clientWidth,
139
            height: $template.height() - $template[0].clientHeight
140
        };
141
        $template.remove();
142

    
143
        return dim;
144
    }
145

    
146
    /**
147
     * Compares equality of a and b
148
     * @param a
149
     * @param b
150
     */
151
    function equal(a, b) {
152
        if (a === b) return true;
153
        if (a === undefined || b === undefined) return false;
154
        if (a === null || b === null) return false;
155
        // Check whether 'a' or 'b' is a string (primitive or object).
156
        // The concatenation of an empty string (+'') converts its argument to a string's primitive.
157
        if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
158
        if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
159
        return false;
160
    }
161

    
162
    /**
163
     * Splits the string into an array of values, transforming each value. An empty array is returned for nulls or empty
164
     * strings
165
     * @param string
166
     * @param separator
167
     */
168
    function splitVal(string, separator, transform) {
169
        var val, i, l;
170
        if (string === null || string.length < 1) return [];
171
        val = string.split(separator);
172
        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = transform(val[i]);
173
        return val;
174
    }
175

    
176
    function getSideBorderPadding(element) {
177
        return element.outerWidth(false) - element.width();
178
    }
179

    
180
    function installKeyUpChangeEvent(element) {
181
        var key="keyup-change-value";
182
        element.on("keydown", function () {
183
            if ($.data(element, key) === undefined) {
184
                $.data(element, key, element.val());
185
            }
186
        });
187
        element.on("keyup", function () {
188
            var val= $.data(element, key);
189
            if (val !== undefined && element.val() !== val) {
190
                $.removeData(element, key);
191
                element.trigger("keyup-change");
192
            }
193
        });
194
    }
195

    
196

    
197
    /**
198
     * filters mouse events so an event is fired only if the mouse moved.
199
     *
200
     * filters out mouse events that occur when mouse is stationary but
201
     * the elements under the pointer are scrolled.
202
     */
203
    function installFilteredMouseMove(element) {
204
        element.on("mousemove", function (e) {
205
            var lastpos = lastMousePosition;
206
            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
207
                $(e.target).trigger("mousemove-filtered", e);
208
            }
209
        });
210
    }
211

    
212
    /**
213
     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
214
     * within the last quietMillis milliseconds.
215
     *
216
     * @param quietMillis number of milliseconds to wait before invoking fn
217
     * @param fn function to be debounced
218
     * @param ctx object to be used as this reference within fn
219
     * @return debounced version of fn
220
     */
221
    function debounce(quietMillis, fn, ctx) {
222
        ctx = ctx || undefined;
223
        var timeout;
224
        return function () {
225
            var args = arguments;
226
            window.clearTimeout(timeout);
227
            timeout = window.setTimeout(function() {
228
                fn.apply(ctx, args);
229
            }, quietMillis);
230
        };
231
    }
232

    
233
    function installDebouncedScroll(threshold, element) {
234
        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
235
        element.on("scroll", function (e) {
236
            if (indexOf(e.target, element.get()) >= 0) notify(e);
237
        });
238
    }
239

    
240
    function focus($el) {
241
        if ($el[0] === document.activeElement) return;
242

    
243
        /* set the focus in a 0 timeout - that way the focus is set after the processing
244
            of the current event has finished - which seems like the only reliable way
245
            to set focus */
246
        window.setTimeout(function() {
247
            var el=$el[0], pos=$el.val().length, range;
248

    
249
            $el.focus();
250

    
251
            /* make sure el received focus so we do not error out when trying to manipulate the caret.
252
                sometimes modals or others listeners may steal it after its set */
253
            var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
254
            if (isVisible && el === document.activeElement) {
255

    
256
                /* after the focus is set move the caret to the end, necessary when we val()
257
                    just before setting focus */
258
                if(el.setSelectionRange)
259
                {
260
                    el.setSelectionRange(pos, pos);
261
                }
262
                else if (el.createTextRange) {
263
                    range = el.createTextRange();
264
                    range.collapse(false);
265
                    range.select();
266
                }
267
            }
268
        }, 0);
269
    }
270

    
271
    function getCursorInfo(el) {
272
        el = $(el)[0];
273
        var offset = 0;
274
        var length = 0;
275
        if ('selectionStart' in el) {
276
            offset = el.selectionStart;
277
            length = el.selectionEnd - offset;
278
        } else if ('selection' in document) {
279
            el.focus();
280
            var sel = document.selection.createRange();
281
            length = document.selection.createRange().text.length;
282
            sel.moveStart('character', -el.value.length);
283
            offset = sel.text.length - length;
284
        }
285
        return { offset: offset, length: length };
286
    }
287

    
288
    function killEvent(event) {
289
        event.preventDefault();
290
        event.stopPropagation();
291
    }
292
    function killEventImmediately(event) {
293
        event.preventDefault();
294
        event.stopImmediatePropagation();
295
    }
296

    
297
    function measureTextWidth(e) {
298
        if (!sizer){
299
            var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
300
            sizer = $(document.createElement("div")).css({
301
                position: "absolute",
302
                left: "-10000px",
303
                top: "-10000px",
304
                display: "none",
305
                fontSize: style.fontSize,
306
                fontFamily: style.fontFamily,
307
                fontStyle: style.fontStyle,
308
                fontWeight: style.fontWeight,
309
                letterSpacing: style.letterSpacing,
310
                textTransform: style.textTransform,
311
                whiteSpace: "nowrap"
312
            });
313
            sizer.attr("class","select2-sizer");
314
            $(document.body).append(sizer);
315
        }
316
        sizer.text(e.val());
317
        return sizer.width();
318
    }
319

    
320
    function syncCssClasses(dest, src, adapter) {
321
        var classes, replacements = [], adapted;
322

    
323
        classes = $.trim(dest.attr("class"));
324

    
325
        if (classes) {
326
            classes = '' + classes; // for IE which returns object
327

    
328
            $(classes.split(/\s+/)).each2(function() {
329
                if (this.indexOf("select2-") === 0) {
330
                    replacements.push(this);
331
                }
332
            });
333
        }
334

    
335
        classes = $.trim(src.attr("class"));
336

    
337
        if (classes) {
338
            classes = '' + classes; // for IE which returns object
339

    
340
            $(classes.split(/\s+/)).each2(function() {
341
                if (this.indexOf("select2-") !== 0) {
342
                    adapted = adapter(this);
343

    
344
                    if (adapted) {
345
                        replacements.push(adapted);
346
                    }
347
                }
348
            });
349
        }
350

    
351
        dest.attr("class", replacements.join(" "));
352
    }
353

    
354

    
355
    function markMatch(text, term, markup, escapeMarkup) {
356
        var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
357
            tl=term.length;
358

    
359
        if (match<0) {
360
            markup.push(escapeMarkup(text));
361
            return;
362
        }
363

    
364
        markup.push(escapeMarkup(text.substring(0, match)));
365
        markup.push("<span class='select2-match'>");
366
        markup.push(escapeMarkup(text.substring(match, match + tl)));
367
        markup.push("</span>");
368
        markup.push(escapeMarkup(text.substring(match + tl, text.length)));
369
    }
370

    
371
    function defaultEscapeMarkup(markup) {
372
        var replace_map = {
373
            '\\': '&#92;',
374
            '&': '&amp;',
375
            '<': '&lt;',
376
            '>': '&gt;',
377
            '"': '&quot;',
378
            "'": '&#39;',
379
            "/": '&#47;'
380
        };
381

    
382
        return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
383
            return replace_map[match];
384
        });
385
    }
386

    
387
    /**
388
     * Produces an ajax-based query function
389
     *
390
     * @param options object containing configuration parameters
391
     * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
392
     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
393
     * @param options.url url for the data
394
     * @param options.data a function(searchTerm, pageNumber, context) that should return an object 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 = $('.select2-hidden-accessible');
703
            if (this.liveRegion.length == 0) {
704
                this.liveRegion = $("<span>", {
705
                        role: "status",
706
                        "aria-live": "polite"
707
                    })
708
                    .addClass("select2-hidden-accessible")
709
                    .appendTo(document.body);
710
            }
711

    
712
            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
713
            this.containerEventName= this.containerId
714
                .replace(/([.])/g, '_')
715
                .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
716
            this.container.attr("id", this.containerId);
717

    
718
            this.container.attr("title", opts.element.attr("title"));
719

    
720
            this.body = $(document.body);
721

    
722
            syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
723

    
724
            this.container.attr("style", opts.element.attr("style"));
725
            this.container.css(evaluate(opts.containerCss, this.opts.element));
726
            this.container.addClass(evaluate(opts.containerCssClass, this.opts.element));
727

    
728
            this.elementTabIndex = this.opts.element.attr("tabindex");
729

    
730
            // swap container for the element
731
            this.opts.element
732
                .data("select2", this)
733
                .attr("tabindex", "-1")
734
                .before(this.container)
735
                .on("click.select2", killEvent); // do not leak click events
736

    
737
            this.container.data("select2", this);
738

    
739
            this.dropdown = this.container.find(".select2-drop");
740

    
741
            syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
742

    
743
            this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element));
744
            this.dropdown.data("select2", this);
745
            this.dropdown.on("click", killEvent);
746

    
747
            this.results = results = this.container.find(resultsSelector);
748
            this.search = search = this.container.find("input.select2-input");
749

    
750
            this.queryCount = 0;
751
            this.resultsPage = 0;
752
            this.context = null;
753

    
754
            // initialize the container
755
            this.initContainer();
756

    
757
            this.container.on("click", killEvent);
758

    
759
            installFilteredMouseMove(this.results);
760

    
761
            this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
762
            this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
763
                this._touchEvent = true;
764
                this.highlightUnderEvent(event);
765
            }));
766
            this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
767
            this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
768

    
769
            // Waiting for a click event on touch devices to select option and hide dropdown
770
            // otherwise click will be triggered on an underlying element
771
            this.dropdown.on('click', this.bind(function (event) {
772
                if (this._touchEvent) {
773
                    this._touchEvent = false;
774
                    this.selectHighlighted();
775
                }
776
            }));
777

    
778
            installDebouncedScroll(80, this.results);
779
            this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
780

    
781
            // do not propagate change event from the search field out of the component
782
            $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
783
            $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
784

    
785
            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
786
            if ($.fn.mousewheel) {
787
                results.mousewheel(function (e, delta, deltaX, deltaY) {
788
                    var top = results.scrollTop();
789
                    if (deltaY > 0 && top - deltaY <= 0) {
790
                        results.scrollTop(0);
791
                        killEvent(e);
792
                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
793
                        results.scrollTop(results.get(0).scrollHeight - results.height());
794
                        killEvent(e);
795
                    }
796
                });
797
            }
798

    
799
            installKeyUpChangeEvent(search);
800
            search.on("keyup-change input paste", this.bind(this.updateResults));
801
            search.on("focus", function () { search.addClass("select2-focused"); });
802
            search.on("blur", function () { search.removeClass("select2-focused");});
803

    
804
            this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
805
                if ($(e.target).closest(".select2-result-selectable").length > 0) {
806
                    this.highlightUnderEvent(e);
807
                    this.selectHighlighted(e);
808
                }
809
            }));
810

    
811
            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
812
            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
813
            // dom it will trigger the popup close, which is not what we want
814
            // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
815
            this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); });
816

    
817
            this.lastSearchTerm = undefined;
818

    
819
            if ($.isFunction(this.opts.initSelection)) {
820
                // initialize selection based on the current value of the source element
821
                this.initSelection();
822

    
823
                // if the user has provided a function that can set selection based on the value of the source element
824
                // we monitor the change event on the element and trigger it, allowing for two way synchronization
825
                this.monitorSource();
826
            }
827

    
828
            if (opts.maximumInputLength !== null) {
829
                this.search.attr("maxlength", opts.maximumInputLength);
830
            }
831

    
832
            var disabled = opts.element.prop("disabled");
833
            if (disabled === undefined) disabled = false;
834
            this.enable(!disabled);
835

    
836
            var readonly = opts.element.prop("readonly");
837
            if (readonly === undefined) readonly = false;
838
            this.readonly(readonly);
839

    
840
            // Calculate size of scrollbar
841
            scrollBarDimensions = scrollBarDimensions || measureScrollbar();
842

    
843
            this.autofocus = opts.element.prop("autofocus");
844
            opts.element.prop("autofocus", false);
845
            if (this.autofocus) this.focus();
846

    
847
            this.search.attr("placeholder", opts.searchInputPlaceholder);
848
        },
849

    
850
        // abstract
851
        destroy: function () {
852
            var element=this.opts.element, select2 = element.data("select2"), self = this;
853

    
854
            this.close();
855

    
856
            if (element.length && element[0].detachEvent && self._sync) {
857
                element.each(function () {
858
                    if (self._sync) {
859
                        this.detachEvent("onpropertychange", self._sync);
860
                    }
861
                });
862
            }
863
            if (this.propertyObserver) {
864
                this.propertyObserver.disconnect();
865
                this.propertyObserver = null;
866
            }
867
            this._sync = null;
868

    
869
            if (select2 !== undefined) {
870
                select2.container.remove();
871
                select2.liveRegion.remove();
872
                select2.dropdown.remove();
873
                element.removeData("select2")
874
                    .off(".select2");
875
                if (!element.is("input[type='hidden']")) {
876
                    element
877
                        .show()
878
                        .prop("autofocus", this.autofocus || false);
879
                    if (this.elementTabIndex) {
880
                        element.attr({tabindex: this.elementTabIndex});
881
                    } else {
882
                        element.removeAttr("tabindex");
883
                    }
884
                    element.show();
885
                } else {
886
                    element.css("display", "");
887
                }
888
            }
889

    
890
            cleanupJQueryElements.call(this,
891
                "container",
892
                "liveRegion",
893
                "dropdown",
894
                "results",
895
                "search"
896
            );
897
        },
898

    
899
        // abstract
900
        optionToData: function(element) {
901
            if (element.is("option")) {
902
                return {
903
                    id:element.prop("value"),
904
                    text:element.text(),
905
                    element: element.get(),
906
                    css: element.attr("class"),
907
                    disabled: element.prop("disabled"),
908
                    locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
909
                };
910
            } else if (element.is("optgroup")) {
911
                return {
912
                    text:element.attr("label"),
913
                    children:[],
914
                    element: element.get(),
915
                    css: element.attr("class")
916
                };
917
            }
918
        },
919

    
920
        // abstract
921
        prepareOpts: function (opts) {
922
            var element, select, idKey, ajaxUrl, self = this;
923

    
924
            element = opts.element;
925

    
926
            if (element.get(0).tagName.toLowerCase() === "select") {
927
                this.select = select = opts.element;
928
            }
929

    
930
            if (select) {
931
                // these options are not allowed when attached to a select because they are picked up off the element itself
932
                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
933
                    if (this in opts) {
934
                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
935
                    }
936
                });
937
            }
938

    
939
            opts.debug = opts.debug || $.fn.select2.defaults.debug;
940

    
941
            // Warnings for options renamed/removed in Select2 4.0.0
942
            // Only when it's enabled through debug mode
943
            if (opts.debug && console && console.warn) {
944
                // id was removed
945
                if (opts.id != null) {
946
                    console.warn(
947
                        'Select2: The `id` option has been removed in Select2 4.0.0, ' +
948
                        'consider renaming your `id` property or mapping the property before your data makes it to Select2. ' +
949
                        'You can read more at https://select2.github.io/announcements-4.0.html#changed-id'
950
                    );
951
                }
952

    
953
                // text was removed
954
                if (opts.text != null) {
955
                    console.warn(
956
                        'Select2: The `text` option has been removed in Select2 4.0.0, ' +
957
                        'consider renaming your `text` property or mapping the property before your data makes it to Select2. ' +
958
                        'You can read more at https://select2.github.io/announcements-4.0.html#changed-id'
959
                    );
960
                }
961

    
962
                // sortResults was renamed to results
963
                if (opts.sortResults != null) {
964
                    console.warn(
965
                        'Select2: the `sortResults` option has been renamed to `sorter` in Select2 4.0.0. '
966
                    );
967
                }
968

    
969
                // selectOnBlur was renamed to selectOnClose
970
                if (opts.selectOnBlur != null) {
971
                    console.warn(
972
                        'Select2: The `selectOnBlur` option has been renamed to `selectOnClose` in Select2 4.0.0.'
973
                    );
974
                }
975

    
976
                // ajax.results was renamed to ajax.processResults
977
                if (opts.ajax != null && opts.ajax.results != null) {
978
                    console.warn(
979
                        'Select2: The `ajax.results` option has been renamed to `ajax.processResults` in Select2 4.0.0.'
980
                    );
981
                }
982

    
983
                // format* options were renamed to language.*
984
                if (opts.formatNoResults != null) {
985
                    console.warn(
986
                        'Select2: The `formatNoResults` option has been renamed to `language.noResults` in Select2 4.0.0.'
987
                    );
988
                }
989
                if (opts.formatSearching != null) {
990
                    console.warn(
991
                        'Select2: The `formatSearching` option has been renamed to `language.searching` in Select2 4.0.0.'
992
                    );
993
                }
994
                if (opts.formatInputTooShort != null) {
995
                    console.warn(
996
                        'Select2: The `formatInputTooShort` option has been renamed to `language.inputTooShort` in Select2 4.0.0.'
997
                    );
998
                }
999
                if (opts.formatInputTooLong != null) {
1000
                    console.warn(
1001
                        'Select2: The `formatInputTooLong` option has been renamed to `language.inputTooLong` in Select2 4.0.0.'
1002
                    );
1003
                }
1004
                if (opts.formatLoading != null) {
1005
                    console.warn(
1006
                        'Select2: The `formatLoading` option has been renamed to `language.loadingMore` in Select2 4.0.0.'
1007
                    );
1008
                }
1009
                if (opts.formatSelectionTooBig != null) {
1010
                    console.warn(
1011
                        'Select2: The `formatSelectionTooBig` option has been renamed to `language.maximumSelected` in Select2 4.0.0.'
1012
                    );
1013
                }
1014

    
1015
                if (opts.element.data('select2Tags')) {
1016
                    console.warn(
1017
                        'Select2: The `data-select2-tags` attribute has been renamed to `data-tags` in Select2 4.0.0.'
1018
                    );
1019
                }
1020
            }
1021

    
1022
            // Aliasing options renamed in Select2 4.0.0
1023

    
1024
            // data-select2-tags -> data-tags
1025
            if (opts.element.data('tags') != null) {
1026
                var elemTags = opts.element.data('tags');
1027

    
1028
                // data-tags should actually be a boolean
1029
                if (!$.isArray(elemTags)) {
1030
                    elemTags = [];
1031
                }
1032

    
1033
                opts.element.data('select2Tags', elemTags);
1034
            }
1035

    
1036
            // sortResults -> sorter
1037
            if (opts.sorter != null) {
1038
                opts.sortResults = opts.sorter;
1039
            }
1040

    
1041
            // selectOnBlur -> selectOnClose
1042
            if (opts.selectOnClose != null) {
1043
                opts.selectOnBlur = opts.selectOnClose;
1044
            }
1045

    
1046
            // ajax.results -> ajax.processResults
1047
            if (opts.ajax != null) {
1048
                if ($.isFunction(opts.ajax.processResults)) {
1049
                    opts.ajax.results = opts.ajax.processResults;
1050
                }
1051
            }
1052

    
1053
            // Formatters/language options
1054
            if (opts.language != null) {
1055
                var lang = opts.language;
1056

    
1057
                // formatNoMatches -> language.noMatches
1058
                if ($.isFunction(lang.noMatches)) {
1059
                    opts.formatNoMatches = lang.noMatches;
1060
                }
1061

    
1062
                // formatSearching -> language.searching
1063
                if ($.isFunction(lang.searching)) {
1064
                    opts.formatSearching = lang.searching;
1065
                }
1066

    
1067
                // formatInputTooShort -> language.inputTooShort
1068
                if ($.isFunction(lang.inputTooShort)) {
1069
                    opts.formatInputTooShort = lang.inputTooShort;
1070
                }
1071

    
1072
                // formatInputTooLong -> language.inputTooLong
1073
                if ($.isFunction(lang.inputTooLong)) {
1074
                    opts.formatInputTooLong = lang.inputTooLong;
1075
                }
1076

    
1077
                // formatLoading -> language.loadingMore
1078
                if ($.isFunction(lang.loadingMore)) {
1079
                    opts.formatLoading = lang.loadingMore;
1080
                }
1081

    
1082
                // formatSelectionTooBig -> language.maximumSelected
1083
                if ($.isFunction(lang.maximumSelected)) {
1084
                    opts.formatSelectionTooBig = lang.maximumSelected;
1085
                }
1086
            }
1087

    
1088
            opts = $.extend({}, {
1089
                populateResults: function(container, results, query) {
1090
                    var populate, id=this.opts.id, liveRegion=this.liveRegion;
1091

    
1092
                    populate=function(results, container, depth) {
1093

    
1094
                        var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
1095

    
1096
                        results = opts.sortResults(results, container, query);
1097

    
1098
                        // collect the created nodes for bulk append
1099
                        var nodes = [];
1100
                        for (i = 0, l = results.length; i < l; i = i + 1) {
1101

    
1102
                            result=results[i];
1103

    
1104
                            disabled = (result.disabled === true);
1105
                            selectable = (!disabled) && (id(result) !== undefined);
1106

    
1107
                            compound=result.children && result.children.length > 0;
1108

    
1109
                            node=$("<li></li>");
1110
                            node.addClass("select2-results-dept-"+depth);
1111
                            node.addClass("select2-result");
1112
                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
1113
                            if (disabled) { node.addClass("select2-disabled"); }
1114
                            if (compound) { node.addClass("select2-result-with-children"); }
1115
                            node.addClass(self.opts.formatResultCssClass(result));
1116
                            node.attr("role", "presentation");
1117

    
1118
                            label=$(document.createElement("div"));
1119
                            label.addClass("select2-result-label");
1120
                            label.attr("id", "select2-result-label-" + nextUid());
1121
                            label.attr("role", "option");
1122

    
1123
                            formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
1124
                            if (formatted!==undefined) {
1125
                                label.html(formatted);
1126
                                node.append(label);
1127
                            }
1128

    
1129

    
1130
                            if (compound) {
1131
                                innerContainer=$("<ul></ul>");
1132
                                innerContainer.addClass("select2-result-sub");
1133
                                populate(result.children, innerContainer, depth+1);
1134
                                node.append(innerContainer);
1135
                            }
1136

    
1137
                            node.data("select2-data", result);
1138
                            nodes.push(node[0]);
1139
                        }
1140

    
1141
                        // bulk append the created nodes
1142
                        container.append(nodes);
1143
                        liveRegion.text(opts.formatMatches(results.length));
1144
                    };
1145

    
1146
                    populate(results, container, 0);
1147
                }
1148
            }, $.fn.select2.defaults, opts);
1149

    
1150
            if (typeof(opts.id) !== "function") {
1151
                idKey = opts.id;
1152
                opts.id = function (e) { return e[idKey]; };
1153
            }
1154

    
1155
            if ($.isArray(opts.element.data("select2Tags"))) {
1156
                if ("tags" in opts) {
1157
                    throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
1158
                }
1159
                opts.tags=opts.element.data("select2Tags");
1160
            }
1161

    
1162
            if (select) {
1163
                opts.query = this.bind(function (query) {
1164
                    var data = { results: [], more: false },
1165
                        term = query.term,
1166
                        children, placeholderOption, process;
1167

    
1168
                    process=function(element, collection) {
1169
                        var group;
1170
                        if (element.is("option")) {
1171
                            if (query.matcher(term, element.text(), element)) {
1172
                                collection.push(self.optionToData(element));
1173
                            }
1174
                        } else if (element.is("optgroup")) {
1175
                            group=self.optionToData(element);
1176
                            element.children().each2(function(i, elm) { process(elm, group.children); });
1177
                            if (group.children.length>0) {
1178
                                collection.push(group);
1179
                            }
1180
                        }
1181
                    };
1182

    
1183
                    children=element.children();
1184

    
1185
                    // ignore the placeholder option if there is one
1186
                    if (this.getPlaceholder() !== undefined && children.length > 0) {
1187
                        placeholderOption = this.getPlaceholderOption();
1188
                        if (placeholderOption) {
1189
                            children=children.not(placeholderOption);
1190
                        }
1191
                    }
1192

    
1193
                    children.each2(function(i, elm) { process(elm, data.results); });
1194

    
1195
                    query.callback(data);
1196
                });
1197
                // this is needed because inside val() we construct choices from options and their id is hardcoded
1198
                opts.id=function(e) { return e.id; };
1199
            } else {
1200
                if (!("query" in opts)) {
1201
                    if ("ajax" in opts) {
1202
                        ajaxUrl = opts.element.data("ajax-url");
1203
                        if (ajaxUrl && ajaxUrl.length > 0) {
1204
                            opts.ajax.url = ajaxUrl;
1205
                        }
1206
                        opts.query = ajax.call(opts.element, opts.ajax);
1207
                    } else if ("data" in opts) {
1208
                        opts.query = local(opts.data);
1209
                    } else if ("tags" in opts) {
1210
                        opts.query = tags(opts.tags);
1211
                        if (opts.createSearchChoice === undefined) {
1212
                            opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
1213
                        }
1214
                        if (opts.initSelection === undefined) {
1215
                            opts.initSelection = function (element, callback) {
1216
                                var data = [];
1217
                                $(splitVal(element.val(), opts.separator, opts.transformVal)).each(function () {
1218
                                    var obj = { id: this, text: this },
1219
                                        tags = opts.tags;
1220
                                    if ($.isFunction(tags)) tags=tags();
1221
                                    $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
1222
                                    data.push(obj);
1223
                                });
1224

    
1225
                                callback(data);
1226
                            };
1227
                        }
1228
                    }
1229
                }
1230
            }
1231
            if (typeof(opts.query) !== "function") {
1232
                throw "query function not defined for Select2 " + opts.element.attr("id");
1233
            }
1234

    
1235
            if (opts.createSearchChoicePosition === 'top') {
1236
                opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
1237
            }
1238
            else if (opts.createSearchChoicePosition === 'bottom') {
1239
                opts.createSearchChoicePosition = function(list, item) { list.push(item); };
1240
            }
1241
            else if (typeof(opts.createSearchChoicePosition) !== "function")  {
1242
                throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
1243
            }
1244

    
1245
            return opts;
1246
        },
1247

    
1248
        /**
1249
         * Monitor the original element for changes and update select2 accordingly
1250
         */
1251
        // abstract
1252
        monitorSource: function () {
1253
            var el = this.opts.element, observer, self = this;
1254

    
1255
            el.on("change.select2", this.bind(function (e) {
1256
                if (this.opts.element.data("select2-change-triggered") !== true) {
1257
                    this.initSelection();
1258
                }
1259
            }));
1260

    
1261
            this._sync = this.bind(function () {
1262

    
1263
                // sync enabled state
1264
                var disabled = el.prop("disabled");
1265
                if (disabled === undefined) disabled = false;
1266
                this.enable(!disabled);
1267

    
1268
                var readonly = el.prop("readonly");
1269
                if (readonly === undefined) readonly = false;
1270
                this.readonly(readonly);
1271

    
1272
                if (this.container) {
1273
                    syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
1274
                    this.container.addClass(evaluate(this.opts.containerCssClass, this.opts.element));
1275
                }
1276

    
1277
                if (this.dropdown) {
1278
                    syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
1279
                    this.dropdown.addClass(evaluate(this.opts.dropdownCssClass, this.opts.element));
1280
                }
1281

    
1282
            });
1283

    
1284
            // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
1285
            if (el.length && el[0].attachEvent) {
1286
                el.each(function() {
1287
                    this.attachEvent("onpropertychange", self._sync);
1288
                });
1289
            }
1290

    
1291
            // safari, chrome, firefox, IE11
1292
            observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
1293
            if (observer !== undefined) {
1294
                if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
1295
                this.propertyObserver = new observer(function (mutations) {
1296
                    $.each(mutations, self._sync);
1297
                });
1298
                this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
1299
            }
1300
        },
1301

    
1302
        // abstract
1303
        triggerSelect: function(data) {
1304
            var evt = $.Event("select2-selecting", { val: this.id(data), object: data, choice: data });
1305
            this.opts.element.trigger(evt);
1306
            return !evt.isDefaultPrevented();
1307
        },
1308

    
1309
        /**
1310
         * Triggers the change event on the source element
1311
         */
1312
        // abstract
1313
        triggerChange: function (details) {
1314

    
1315
            details = details || {};
1316
            details= $.extend({}, details, { type: "change", val: this.val() });
1317
            // prevents recursive triggering
1318
            this.opts.element.data("select2-change-triggered", true);
1319
            this.opts.element.trigger(details);
1320
            this.opts.element.data("select2-change-triggered", false);
1321

    
1322
            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
1323
            // so here we trigger the click event manually
1324
            this.opts.element.click();
1325

    
1326
            // ValidationEngine ignores the change event and listens instead to blur
1327
            // so here we trigger the blur event manually if so desired
1328
            if (this.opts.blurOnChange)
1329
                this.opts.element.blur();
1330
        },
1331

    
1332
        //abstract
1333
        isInterfaceEnabled: function()
1334
        {
1335
            return this.enabledInterface === true;
1336
        },
1337

    
1338
        // abstract
1339
        enableInterface: function() {
1340
            var enabled = this._enabled && !this._readonly,
1341
                disabled = !enabled;
1342

    
1343
            if (enabled === this.enabledInterface) return false;
1344

    
1345
            this.container.toggleClass("select2-container-disabled", disabled);
1346
            this.close();
1347
            this.enabledInterface = enabled;
1348

    
1349
            return true;
1350
        },
1351

    
1352
        // abstract
1353
        enable: function(enabled) {
1354
            if (enabled === undefined) enabled = true;
1355
            if (this._enabled === enabled) return;
1356
            this._enabled = enabled;
1357

    
1358
            this.opts.element.prop("disabled", !enabled);
1359
            this.enableInterface();
1360
        },
1361

    
1362
        // abstract
1363
        disable: function() {
1364
            this.enable(false);
1365
        },
1366

    
1367
        // abstract
1368
        readonly: function(enabled) {
1369
            if (enabled === undefined) enabled = false;
1370
            if (this._readonly === enabled) return;
1371
            this._readonly = enabled;
1372

    
1373
            this.opts.element.prop("readonly", enabled);
1374
            this.enableInterface();
1375
        },
1376

    
1377
        // abstract
1378
        opened: function () {
1379
            return (this.container) ? this.container.hasClass("select2-dropdown-open") : false;
1380
        },
1381

    
1382
        // abstract
1383
        positionDropdown: function() {
1384
            var $dropdown = this.dropdown,
1385
                container = this.container,
1386
                offset = container.offset(),
1387
                height = container.outerHeight(false),
1388
                width = container.outerWidth(false),
1389
                dropHeight = $dropdown.outerHeight(false),
1390
                $window = $(window),
1391
                windowWidth = $window.width(),
1392
                windowHeight = $window.height(),
1393
                viewPortRight = $window.scrollLeft() + windowWidth,
1394
                viewportBottom = $window.scrollTop() + windowHeight,
1395
                dropTop = offset.top + height,
1396
                dropLeft = offset.left,
1397
                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
1398
                enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
1399
                dropWidth = $dropdown.outerWidth(false),
1400
                enoughRoomOnRight = function() {
1401
                    return dropLeft + dropWidth <= viewPortRight;
1402
                },
1403
                enoughRoomOnLeft = function() {
1404
                    return offset.left + viewPortRight + container.outerWidth(false)  > dropWidth;
1405
                },
1406
                aboveNow = $dropdown.hasClass("select2-drop-above"),
1407
                bodyOffset,
1408
                above,
1409
                changeDirection,
1410
                css,
1411
                resultsListNode;
1412

    
1413
            // always prefer the current above/below alignment, unless there is not enough room
1414
            if (aboveNow) {
1415
                above = true;
1416
                if (!enoughRoomAbove && enoughRoomBelow) {
1417
                    changeDirection = true;
1418
                    above = false;
1419
                }
1420
            } else {
1421
                above = false;
1422
                if (!enoughRoomBelow && enoughRoomAbove) {
1423
                    changeDirection = true;
1424
                    above = true;
1425
                }
1426
            }
1427

    
1428
            //if we are changing direction we need to get positions when dropdown is hidden;
1429
            if (changeDirection) {
1430
                $dropdown.hide();
1431
                offset = this.container.offset();
1432
                height = this.container.outerHeight(false);
1433
                width = this.container.outerWidth(false);
1434
                dropHeight = $dropdown.outerHeight(false);
1435
                viewPortRight = $window.scrollLeft() + windowWidth;
1436
                viewportBottom = $window.scrollTop() + windowHeight;
1437
                dropTop = offset.top + height;
1438
                dropLeft = offset.left;
1439
                dropWidth = $dropdown.outerWidth(false);
1440
                $dropdown.show();
1441

    
1442
                // fix so the cursor does not move to the left within the search-textbox in IE
1443
                this.focusSearch();
1444
            }
1445

    
1446
            if (this.opts.dropdownAutoWidth) {
1447
                resultsListNode = $('.select2-results', $dropdown)[0];
1448
                $dropdown.addClass('select2-drop-auto-width');
1449
                $dropdown.css('width', '');
1450
                // Add scrollbar width to dropdown if vertical scrollbar is present
1451
                dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
1452
                dropWidth > width ? width = dropWidth : dropWidth = width;
1453
                dropHeight = $dropdown.outerHeight(false);
1454
            }
1455
            else {
1456
                this.container.removeClass('select2-drop-auto-width');
1457
            }
1458

    
1459
            //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1460
            //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
1461

    
1462
            // fix positioning when body has an offset and is not position: static
1463
            if (this.body.css('position') !== 'static') {
1464
                bodyOffset = this.body.offset();
1465
                dropTop -= bodyOffset.top;
1466
                dropLeft -= bodyOffset.left;
1467
            }
1468

    
1469
            if (!enoughRoomOnRight() && enoughRoomOnLeft()) {
1470
                dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
1471
            }
1472

    
1473
            css =  {
1474
                left: dropLeft,
1475
                width: width
1476
            };
1477

    
1478
            if (above) {
1479
                this.container.addClass("select2-drop-above");
1480
                $dropdown.addClass("select2-drop-above");
1481
                dropHeight = $dropdown.outerHeight(false);
1482
                css.top = offset.top - dropHeight;
1483
                css.bottom = 'auto';
1484
            }
1485
            else {
1486
                css.top = dropTop;
1487
                css.bottom = 'auto';
1488
                this.container.removeClass("select2-drop-above");
1489
                $dropdown.removeClass("select2-drop-above");
1490
            }
1491
            css = $.extend(css, evaluate(this.opts.dropdownCss, this.opts.element));
1492

    
1493
            $dropdown.css(css);
1494
        },
1495

    
1496
        // abstract
1497
        shouldOpen: function() {
1498
            var event;
1499

    
1500
            if (this.opened()) return false;
1501

    
1502
            if (this._enabled === false || this._readonly === true) return false;
1503

    
1504
            event = $.Event("select2-opening");
1505
            this.opts.element.trigger(event);
1506
            return !event.isDefaultPrevented();
1507
        },
1508

    
1509
        // abstract
1510
        clearDropdownAlignmentPreference: function() {
1511
            // clear the classes used to figure out the preference of where the dropdown should be opened
1512
            this.container.removeClass("select2-drop-above");
1513
            this.dropdown.removeClass("select2-drop-above");
1514
        },
1515

    
1516
        /**
1517
         * Opens the dropdown
1518
         *
1519
         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1520
         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1521
         */
1522
        // abstract
1523
        open: function () {
1524

    
1525
            if (!this.shouldOpen()) return false;
1526

    
1527
            this.opening();
1528

    
1529
            // Only bind the document mousemove when the dropdown is visible
1530
            $document.on("mousemove.select2Event", function (e) {
1531
                lastMousePosition.x = e.pageX;
1532
                lastMousePosition.y = e.pageY;
1533
            });
1534

    
1535
            return true;
1536
        },
1537

    
1538
        /**
1539
         * Performs the opening of the dropdown
1540
         */
1541
        // abstract
1542
        opening: function() {
1543
            var cid = this.containerEventName,
1544
                scroll = "scroll." + cid,
1545
                resize = "resize."+cid,
1546
                orient = "orientationchange."+cid,
1547
                mask;
1548

    
1549
            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1550

    
1551
            this.clearDropdownAlignmentPreference();
1552

    
1553
            if(this.dropdown[0] !== this.body.children().last()[0]) {
1554
                this.dropdown.detach().appendTo(this.body);
1555
            }
1556

    
1557
            // create the dropdown mask if doesn't already exist
1558
            mask = $("#select2-drop-mask");
1559
            if (mask.length === 0) {
1560
                mask = $(document.createElement("div"));
1561
                mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
1562
                mask.hide();
1563
                mask.appendTo(this.body);
1564
                mask.on("mousedown touchstart click", function (e) {
1565
                    // Prevent IE from generating a click event on the body
1566
                    reinsertElement(mask);
1567

    
1568
                    var dropdown = $("#select2-drop"), self;
1569
                    if (dropdown.length > 0) {
1570
                        self=dropdown.data("select2");
1571
                        if (self.opts.selectOnBlur) {
1572
                            self.selectHighlighted({noFocus: true});
1573
                        }
1574
                        self.close();
1575
                        e.preventDefault();
1576
                        e.stopPropagation();
1577
                    }
1578
                });
1579
            }
1580

    
1581
            // ensure the mask is always right before the dropdown
1582
            if (this.dropdown.prev()[0] !== mask[0]) {
1583
                this.dropdown.before(mask);
1584
            }
1585

    
1586
            // move the global id to the correct dropdown
1587
            $("#select2-drop").removeAttr("id");
1588
            this.dropdown.attr("id", "select2-drop");
1589

    
1590
            // show the elements
1591
            mask.show();
1592

    
1593
            this.positionDropdown();
1594
            this.dropdown.show();
1595
            this.positionDropdown();
1596

    
1597
            this.dropdown.addClass("select2-drop-active");
1598

    
1599
            // attach listeners to events that can change the position of the container and thus require
1600
            // the position of the dropdown to be updated as well so it does not come unglued from the container
1601
            var that = this;
1602
            this.container.parents().add(window).each(function () {
1603
                $(this).on(resize+" "+scroll+" "+orient, function (e) {
1604
                    if (that.opened()) that.positionDropdown();
1605
                });
1606
            });
1607

    
1608

    
1609
        },
1610

    
1611
        // abstract
1612
        close: function () {
1613
            if (!this.opened()) return;
1614

    
1615
            var cid = this.containerEventName,
1616
                scroll = "scroll." + cid,
1617
                resize = "resize."+cid,
1618
                orient = "orientationchange."+cid;
1619

    
1620
            // unbind event listeners
1621
            this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
1622

    
1623
            this.clearDropdownAlignmentPreference();
1624

    
1625
            $("#select2-drop-mask").hide();
1626
            this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1627
            this.dropdown.hide();
1628
            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
1629
            this.results.empty();
1630

    
1631
            // Now that the dropdown is closed, unbind the global document mousemove event
1632
            $document.off("mousemove.select2Event");
1633

    
1634
            this.clearSearch();
1635
            this.search.removeClass("select2-active");
1636

    
1637
            // Remove the aria active descendant for highlighted element
1638
            this.search.removeAttr("aria-activedescendant");
1639
            this.opts.element.trigger($.Event("select2-close"));
1640
        },
1641

    
1642
        /**
1643
         * Opens control, sets input value, and updates results.
1644
         */
1645
        // abstract
1646
        externalSearch: function (term) {
1647
            this.open();
1648
            this.search.val(term);
1649
            this.updateResults(false);
1650
        },
1651

    
1652
        // abstract
1653
        clearSearch: function () {
1654

    
1655
        },
1656

    
1657
        /**
1658
         * @return {Boolean} Whether or not search value was changed.
1659
         * @private
1660
         */
1661
        prefillNextSearchTerm: function () {
1662
            // initializes search's value with nextSearchTerm (if defined by user)
1663
            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
1664
            if(this.search.val() !== "") {
1665
                return false;
1666
            }
1667

    
1668
            var nextSearchTerm = this.opts.nextSearchTerm(this.data(), this.lastSearchTerm);
1669
            if(nextSearchTerm !== undefined){
1670
                this.search.val(nextSearchTerm);
1671
                this.search.select();
1672
                return true;
1673
            }
1674

    
1675
            return false;
1676
        },
1677

    
1678
        //abstract
1679
        getMaximumSelectionSize: function() {
1680
            return evaluate(this.opts.maximumSelectionSize, this.opts.element);
1681
        },
1682

    
1683
        // abstract
1684
        ensureHighlightVisible: function () {
1685
            var results = this.results, children, index, child, hb, rb, y, more, topOffset;
1686

    
1687
            index = this.highlight();
1688

    
1689
            if (index < 0) return;
1690

    
1691
            if (index == 0) {
1692

    
1693
                // if the first element is highlighted scroll all the way to the top,
1694
                // that way any unselectable headers above it will also be scrolled
1695
                // into view
1696

    
1697
                results.scrollTop(0);
1698
                return;
1699
            }
1700

    
1701
            children = this.findHighlightableChoices().find('.select2-result-label');
1702

    
1703
            child = $(children[index]);
1704

    
1705
            topOffset = (child.offset() || {}).top || 0;
1706

    
1707
            hb = topOffset + child.outerHeight(true);
1708

    
1709
            // if this is the last child lets also make sure select2-more-results is visible
1710
            if (index === children.length - 1) {
1711
                more = results.find("li.select2-more-results");
1712
                if (more.length > 0) {
1713
                    hb = more.offset().top + more.outerHeight(true);
1714
                }
1715
            }
1716

    
1717
            rb = results.offset().top + results.outerHeight(false);
1718
            if (hb > rb) {
1719
                results.scrollTop(results.scrollTop() + (hb - rb));
1720
            }
1721
            y = topOffset - results.offset().top;
1722

    
1723
            // make sure the top of the element is visible
1724
            if (y < 0 && child.css('display') != 'none' ) {
1725
                results.scrollTop(results.scrollTop() + y); // y is negative
1726
            }
1727
        },
1728

    
1729
        // abstract
1730
        findHighlightableChoices: function() {
1731
            return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
1732
        },
1733

    
1734
        // abstract
1735
        moveHighlight: function (delta) {
1736
            var choices = this.findHighlightableChoices(),
1737
                index = this.highlight();
1738

    
1739
            while (index > -1 && index < choices.length) {
1740
                index += delta;
1741
                var choice = $(choices[index]);
1742
                if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1743
                    this.highlight(index);
1744
                    break;
1745
                }
1746
            }
1747
        },
1748

    
1749
        // abstract
1750
        highlight: function (index) {
1751
            var choices = this.findHighlightableChoices(),
1752
                choice,
1753
                data;
1754

    
1755
            if (arguments.length === 0) {
1756
                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1757
            }
1758

    
1759
            if (index >= choices.length) index = choices.length - 1;
1760
            if (index < 0) index = 0;
1761

    
1762
            this.removeHighlight();
1763

    
1764
            choice = $(choices[index]);
1765
            choice.addClass("select2-highlighted");
1766

    
1767
            // ensure assistive technology can determine the active choice
1768
            this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
1769

    
1770
            this.ensureHighlightVisible();
1771

    
1772
            this.liveRegion.text(choice.text());
1773

    
1774
            data = choice.data("select2-data");
1775
            if (data) {
1776
                this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1777
            }
1778
        },
1779

    
1780
        removeHighlight: function() {
1781
            this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1782
        },
1783

    
1784
        touchMoved: function() {
1785
            this._touchMoved = true;
1786
        },
1787

    
1788
        clearTouchMoved: function() {
1789
          this._touchMoved = false;
1790
        },
1791

    
1792
        // abstract
1793
        countSelectableResults: function() {
1794
            return this.findHighlightableChoices().length;
1795
        },
1796

    
1797
        // abstract
1798
        highlightUnderEvent: function (event) {
1799
            var el = $(event.target).closest(".select2-result-selectable");
1800
            if (el.length > 0 && !el.is(".select2-highlighted")) {
1801
                var choices = this.findHighlightableChoices();
1802
                this.highlight(choices.index(el));
1803
            } else if (el.length == 0) {
1804
                // if we are over an unselectable item remove all highlights
1805
                this.removeHighlight();
1806
            }
1807
        },
1808

    
1809
        // abstract
1810
        loadMoreIfNeeded: function () {
1811
            var results = this.results,
1812
                more = results.find("li.select2-more-results"),
1813
                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1814
                page = this.resultsPage + 1,
1815
                self=this,
1816
                term=this.search.val(),
1817
                context=this.context;
1818

    
1819
            if (more.length === 0) return;
1820
            below = more.offset().top - results.offset().top - results.height();
1821

    
1822
            if (below <= this.opts.loadMorePadding) {
1823
                more.addClass("select2-active");
1824
                this.opts.query({
1825
                        element: this.opts.element,
1826
                        term: term,
1827
                        page: page,
1828
                        context: context,
1829
                        matcher: this.opts.matcher,
1830
                        callback: this.bind(function (data) {
1831

    
1832
                    // ignore a response if the select2 has been closed before it was received
1833
                    if (!self.opened()) return;
1834

    
1835

    
1836
                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1837
                    self.postprocessResults(data, false, false);
1838

    
1839
                    if (data.more===true) {
1840
                        more.detach().appendTo(results).html(self.opts.escapeMarkup(evaluate(self.opts.formatLoadMore, self.opts.element, page+1)));
1841
                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1842
                    } else {
1843
                        more.remove();
1844
                    }
1845
                    self.positionDropdown();
1846
                    self.resultsPage = page;
1847
                    self.context = data.context;
1848
                    this.opts.element.trigger({ type: "select2-loaded", items: data });
1849
                })});
1850
            }
1851
        },
1852

    
1853
        /**
1854
         * Default tokenizer function which does nothing
1855
         */
1856
        tokenize: function() {
1857

    
1858
        },
1859

    
1860
        /**
1861
         * @param initial whether or not this is the call to this method right after the dropdown has been opened
1862
         */
1863
        // abstract
1864
        updateResults: function (initial) {
1865
            var search = this.search,
1866
                results = this.results,
1867
                opts = this.opts,
1868
                data,
1869
                self = this,
1870
                input,
1871
                term = search.val(),
1872
                lastTerm = $.data(this.container, "select2-last-term"),
1873
                // sequence number used to drop out-of-order responses
1874
                queryNumber;
1875

    
1876
            // prevent duplicate queries against the same term
1877
            if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1878

    
1879
            $.data(this.container, "select2-last-term", term);
1880

    
1881
            // if the search is currently hidden we do not alter the results
1882
            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1883
                return;
1884
            }
1885

    
1886
            function postRender() {
1887
                search.removeClass("select2-active");
1888
                self.positionDropdown();
1889
                if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
1890
                    self.liveRegion.text(results.text());
1891
                }
1892
                else {
1893
                    self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable:not(".select2-selected")').length));
1894
                }
1895
            }
1896

    
1897
            function render(html) {
1898
                results.html(html);
1899
                postRender();
1900
            }
1901

    
1902
            queryNumber = ++this.queryCount;
1903

    
1904
            var maxSelSize = this.getMaximumSelectionSize();
1905
            if (maxSelSize >=1) {
1906
                data = this.data();
1907
                if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1908
                    render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, opts.element, maxSelSize) + "</li>");
1909
                    return;
1910
                }
1911
            }
1912

    
1913
            if (search.val().length < opts.minimumInputLength) {
1914
                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1915
                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, opts.element, search.val(), opts.minimumInputLength) + "</li>");
1916
                } else {
1917
                    render("");
1918
                }
1919
                if (initial && this.showSearch) this.showSearch(true);
1920
                return;
1921
            }
1922

    
1923
            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1924
                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1925
                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, opts.element, search.val(), opts.maximumInputLength) + "</li>");
1926
                } else {
1927
                    render("");
1928
                }
1929
                return;
1930
            }
1931

    
1932
            if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1933
                render("<li class='select2-searching'>" + evaluate(opts.formatSearching, opts.element) + "</li>");
1934
            }
1935

    
1936
            search.addClass("select2-active");
1937

    
1938
            this.removeHighlight();
1939

    
1940
            // give the tokenizer a chance to pre-process the input
1941
            input = this.tokenize();
1942
            if (input != undefined && input != null) {
1943
                search.val(input);
1944
            }
1945

    
1946
            this.resultsPage = 1;
1947

    
1948
            opts.query({
1949
                element: opts.element,
1950
                    term: search.val(),
1951
                    page: this.resultsPage,
1952
                    context: null,
1953
                    matcher: opts.matcher,
1954
                    callback: this.bind(function (data) {
1955
                var def; // default choice
1956

    
1957
                // ignore old responses
1958
                if (queryNumber != this.queryCount) {
1959
                  return;
1960
                }
1961

    
1962
                // ignore a response if the select2 has been closed before it was received
1963
                if (!this.opened()) {
1964
                    this.search.removeClass("select2-active");
1965
                    return;
1966
                }
1967

    
1968
                // handle ajax error
1969
                if(data.hasError !== undefined && checkFormatter(opts.formatAjaxError, "formatAjaxError")) {
1970
                    render("<li class='select2-ajax-error'>" + evaluate(opts.formatAjaxError, opts.element, data.jqXHR, data.textStatus, data.errorThrown) + "</li>");
1971
                    return;
1972
                }
1973

    
1974
                // save context, if any
1975
                this.context = (data.context===undefined) ? null : data.context;
1976
                // create a default choice and prepend it to the list
1977
                if (this.opts.createSearchChoice && search.val() !== "") {
1978
                    def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1979
                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1980
                        if ($(data.results).filter(
1981
                            function () {
1982
                                return equal(self.id(this), self.id(def));
1983
                            }).length === 0) {
1984
                            this.opts.createSearchChoicePosition(data.results, def);
1985
                        }
1986
                    }
1987
                }
1988

    
1989
                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1990
                    render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, opts.element, search.val()) + "</li>");
1991
                    if(this.showSearch){
1992
                        this.showSearch(search.val());
1993
                    }
1994
                    return;
1995
                }
1996

    
1997
                results.empty();
1998
                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1999

    
2000
                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
2001
                    results.append("<li class='select2-more-results'>" + opts.escapeMarkup(evaluate(opts.formatLoadMore, opts.element, this.resultsPage)) + "</li>");
2002
                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
2003
                }
2004

    
2005
                this.postprocessResults(data, initial);
2006

    
2007
                postRender();
2008

    
2009
                this.opts.element.trigger({ type: "select2-loaded", items: data });
2010
            })});
2011
        },
2012

    
2013
        // abstract
2014
        cancel: function () {
2015
            this.close();
2016
        },
2017

    
2018
        // abstract
2019
        blur: function () {
2020
            // if selectOnBlur == true, select the currently highlighted option
2021
            if (this.opts.selectOnBlur)
2022
                this.selectHighlighted({noFocus: true});
2023

    
2024
            this.close();
2025
            this.container.removeClass("select2-container-active");
2026
            // synonymous to .is(':focus'), which is available in jquery >= 1.6
2027
            if (this.search[0] === document.activeElement) { this.search.blur(); }
2028
            this.clearSearch();
2029
            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
2030
        },
2031

    
2032
        // abstract
2033
        focusSearch: function () {
2034
            focus(this.search);
2035
        },
2036

    
2037
        // abstract
2038
        selectHighlighted: function (options) {
2039
            if (this._touchMoved) {
2040
              this.clearTouchMoved();
2041
              return;
2042
            }
2043
            var index=this.highlight(),
2044
                highlighted=this.results.find(".select2-highlighted"),
2045
                data = highlighted.closest('.select2-result').data("select2-data");
2046

    
2047
            if (data) {
2048
                this.highlight(index);
2049
                this.onSelect(data, options);
2050
            } else if (options && options.noFocus) {
2051
                this.close();
2052
            }
2053
        },
2054

    
2055
        // abstract
2056
        getPlaceholder: function () {
2057
            var placeholderOption;
2058
            return this.opts.element.attr("placeholder") ||
2059
                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
2060
                this.opts.element.data("placeholder") ||
2061
                this.opts.placeholder ||
2062
                ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
2063
        },
2064

    
2065
        // abstract
2066
        getPlaceholderOption: function() {
2067
            if (this.select) {
2068
                var firstOption = this.select.children('option').first();
2069
                if (this.opts.placeholderOption !== undefined ) {
2070
                    //Determine the placeholder option based on the specified placeholderOption setting
2071
                    return (this.opts.placeholderOption === "first" && firstOption) ||
2072
                           (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
2073
                } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
2074
                    //No explicit placeholder option specified, use the first if it's blank
2075
                    return firstOption;
2076
                }
2077
            }
2078
        },
2079

    
2080
        /**
2081
         * Get the desired width for the container element.  This is
2082
         * derived first from option `width` passed to select2, then
2083
         * the inline 'style' on the original element, and finally
2084
         * falls back to the jQuery calculated element width.
2085
         */
2086
        // abstract
2087
        initContainerWidth: function () {
2088
            function resolveContainerWidth() {
2089
                var style, attrs, matches, i, l, attr;
2090

    
2091
                if (this.opts.width === "off") {
2092
                    return null;
2093
                } else if (this.opts.width === "element"){
2094
                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
2095
                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
2096
                    // check if there is inline style on the element that contains width
2097
                    style = this.opts.element.attr('style');
2098
                    if (typeof(style) === "string") {
2099
                        attrs = style.split(';');
2100
                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
2101
                            attr = attrs[i].replace(/\s/g, '');
2102
                            matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
2103
                            if (matches !== null && matches.length >= 1)
2104
                                return matches[1];
2105
                        }
2106
                    }
2107

    
2108
                    if (this.opts.width === "resolve") {
2109
                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
2110
                        // when attached to input type=hidden or elements hidden via css
2111
                        style = this.opts.element.css('width');
2112
                        if (style.indexOf("%") > 0) return style;
2113

    
2114
                        // finally, fallback on the calculated width of the element
2115
                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
2116
                    }
2117

    
2118
                    return null;
2119
                } else if ($.isFunction(this.opts.width)) {
2120
                    return this.opts.width();
2121
                } else {
2122
                    return this.opts.width;
2123
               }
2124
            };
2125

    
2126
            var width = resolveContainerWidth.call(this);
2127
            if (width !== null) {
2128
                this.container.css("width", width);
2129
            }
2130
        }
2131
    });
2132

    
2133
    SingleSelect2 = clazz(AbstractSelect2, {
2134

    
2135
        // single
2136

    
2137
        createContainer: function () {
2138
            var container = $(document.createElement("div")).attr({
2139
                "class": "select2-container"
2140
            }).html([
2141
                "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
2142
                "   <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
2143
                "   <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
2144
                "</a>",
2145
                "<label for='' class='select2-offscreen'></label>",
2146
                "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
2147
                "<div class='select2-drop select2-display-none'>",
2148
                "   <div class='select2-search'>",
2149
                "       <label for='' class='select2-offscreen'></label>",
2150
                "       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
2151
                "       aria-autocomplete='list' />",
2152
                "   </div>",
2153
                "   <ul class='select2-results' role='listbox'>",
2154
                "   </ul>",
2155
                "</div>"].join(""));
2156
            return container;
2157
        },
2158

    
2159
        // single
2160
        enableInterface: function() {
2161
            if (this.parent.enableInterface.apply(this, arguments)) {
2162
                this.focusser.prop("disabled", !this.isInterfaceEnabled());
2163
            }
2164
        },
2165

    
2166
        // single
2167
        opening: function () {
2168
            var el, range, len;
2169

    
2170
            if (this.opts.minimumResultsForSearch >= 0) {
2171
                this.showSearch(true);
2172
            }
2173

    
2174
            this.parent.opening.apply(this, arguments);
2175

    
2176
            if (this.showSearchInput !== false) {
2177
                // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
2178
                // all other browsers handle this just fine
2179

    
2180
                this.search.val(this.focusser.val());
2181
            }
2182
            if (this.opts.shouldFocusInput(this)) {
2183
                this.search.focus();
2184
                // move the cursor to the end after focussing, otherwise it will be at the beginning and
2185
                // new text will appear *before* focusser.val()
2186
                el = this.search.get(0);
2187
                if (el.createTextRange) {
2188
                    range = el.createTextRange();
2189
                    range.collapse(false);
2190
                    range.select();
2191
                } else if (el.setSelectionRange) {
2192
                    len = this.search.val().length;
2193
                    el.setSelectionRange(len, len);
2194
                }
2195
            }
2196

    
2197
            this.prefillNextSearchTerm();
2198

    
2199
            this.focusser.prop("disabled", true).val("");
2200
            this.updateResults(true);
2201
            this.opts.element.trigger($.Event("select2-open"));
2202
        },
2203

    
2204
        // single
2205
        close: function () {
2206
            if (!this.opened()) return;
2207
            this.parent.close.apply(this, arguments);
2208

    
2209
            this.focusser.prop("disabled", false);
2210

    
2211
            if (this.opts.shouldFocusInput(this)) {
2212
                this.focusser.focus();
2213
            }
2214
        },
2215

    
2216
        // single
2217
        focus: function () {
2218
            if (this.opened()) {
2219
                this.close();
2220
            } else {
2221
                this.focusser.prop("disabled", false);
2222
                if (this.opts.shouldFocusInput(this)) {
2223
                    this.focusser.focus();
2224
                }
2225
            }
2226
        },
2227

    
2228
        // single
2229
        isFocused: function () {
2230
            return this.container.hasClass("select2-container-active");
2231
        },
2232

    
2233
        // single
2234
        cancel: function () {
2235
            this.parent.cancel.apply(this, arguments);
2236
            this.focusser.prop("disabled", false);
2237

    
2238
            if (this.opts.shouldFocusInput(this)) {
2239
                this.focusser.focus();
2240
            }
2241
        },
2242

    
2243
        // single
2244
        destroy: function() {
2245
            $("label[for='" + this.focusser.attr('id') + "']")
2246
                .attr('for', this.opts.element.attr("id"));
2247
            this.parent.destroy.apply(this, arguments);
2248

    
2249
            cleanupJQueryElements.call(this,
2250
                "selection",
2251
                "focusser"
2252
            );
2253
        },
2254

    
2255
        // single
2256
        initContainer: function () {
2257

    
2258
            var selection,
2259
                container = this.container,
2260
                dropdown = this.dropdown,
2261
                idSuffix = nextUid(),
2262
                elementLabel;
2263

    
2264
            if (this.opts.minimumResultsForSearch < 0) {
2265
                this.showSearch(false);
2266
            } else {
2267
                this.showSearch(true);
2268
            }
2269

    
2270
            this.selection = selection = container.find(".select2-choice");
2271

    
2272
            this.focusser = container.find(".select2-focusser");
2273

    
2274
            // add aria associations
2275
            selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
2276
            this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
2277
            this.results.attr("id", "select2-results-"+idSuffix);
2278
            this.search.attr("aria-owns", "select2-results-"+idSuffix);
2279

    
2280
            // rewrite labels from original element to focusser
2281
            this.focusser.attr("id", "s2id_autogen"+idSuffix);
2282

    
2283
            elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
2284
            this.opts.element.on('focus.select2', this.bind(function () { this.focus(); }));
2285

    
2286
            this.focusser.prev()
2287
                .text(elementLabel.text())
2288
                .attr('for', this.focusser.attr('id'));
2289

    
2290
            // Ensure the original element retains an accessible name
2291
            var originalTitle = this.opts.element.attr("title");
2292
            this.opts.element.attr("title", (originalTitle || elementLabel.text()));
2293

    
2294
            this.focusser.attr("tabindex", this.elementTabIndex);
2295

    
2296
            // write label for search field using the label from the focusser element
2297
            this.search.attr("id", this.focusser.attr('id') + '_search');
2298

    
2299
            this.search.prev()
2300
                .text($("label[for='" + this.focusser.attr('id') + "']").text())
2301
                .attr('for', this.search.attr('id'));
2302

    
2303
            this.search.on("keydown", this.bind(function (e) {
2304
                if (!this.isInterfaceEnabled()) return;
2305

    
2306
                // filter 229 keyCodes (input method editor is processing key input)
2307
                if (229 == e.keyCode) return;
2308

    
2309
                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2310
                    // prevent the page from scrolling
2311
                    killEvent(e);
2312
                    return;
2313
                }
2314

    
2315
                switch (e.which) {
2316
                    case KEY.UP:
2317
                    case KEY.DOWN:
2318
                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2319
                        killEvent(e);
2320
                        return;
2321
                    case KEY.ENTER:
2322
                        this.selectHighlighted();
2323
                        killEvent(e);
2324
                        return;
2325
                    case KEY.TAB:
2326
                        this.selectHighlighted({noFocus: true});
2327
                        return;
2328
                    case KEY.ESC:
2329
                        this.cancel(e);
2330
                        killEvent(e);
2331
                        return;
2332
                }
2333
            }));
2334

    
2335
            this.search.on("blur", this.bind(function(e) {
2336
                // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
2337
                // without this the search field loses focus which is annoying
2338
                if (document.activeElement === this.body.get(0)) {
2339
                    window.setTimeout(this.bind(function() {
2340
                        if (this.opened() && this.results && this.results.length > 1) {
2341
                            this.search.focus();
2342
                        }
2343
                    }), 0);
2344
                }
2345
            }));
2346

    
2347
            this.focusser.on("keydown", this.bind(function (e) {
2348
                if (!this.isInterfaceEnabled()) return;
2349

    
2350
                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
2351
                    return;
2352
                }
2353

    
2354
                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
2355
                    killEvent(e);
2356
                    return;
2357
                }
2358

    
2359
                if (e.which == KEY.DOWN || e.which == KEY.UP
2360
                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
2361

    
2362
                    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
2363

    
2364
                    this.open();
2365
                    killEvent(e);
2366
                    return;
2367
                }
2368

    
2369
                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
2370
                    if (this.opts.allowClear) {
2371
                        this.clear();
2372
                    }
2373
                    killEvent(e);
2374
                    return;
2375
                }
2376
            }));
2377

    
2378

    
2379
            installKeyUpChangeEvent(this.focusser);
2380
            this.focusser.on("keyup-change input", this.bind(function(e) {
2381
                if (this.opts.minimumResultsForSearch >= 0) {
2382
                    e.stopPropagation();
2383
                    if (this.opened()) return;
2384
                    this.open();
2385
                }
2386
            }));
2387

    
2388
            selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
2389
                if (!this.isInterfaceEnabled()) {
2390
                    return;
2391
                }
2392

    
2393
                this.clear();
2394
                killEventImmediately(e);
2395
                this.close();
2396

    
2397
                if (this.selection) {
2398
                    this.selection.focus();
2399
                }
2400
            }));
2401

    
2402
            selection.on("mousedown touchstart", this.bind(function (e) {
2403
                // Prevent IE from generating a click event on the body
2404
                reinsertElement(selection);
2405

    
2406
                if (!this.container.hasClass("select2-container-active")) {
2407
                    this.opts.element.trigger($.Event("select2-focus"));
2408
                }
2409

    
2410
                if (this.opened()) {
2411
                    this.close();
2412
                } else if (this.isInterfaceEnabled()) {
2413
                    this.open();
2414
                }
2415

    
2416
                killEvent(e);
2417
            }));
2418

    
2419
            dropdown.on("mousedown touchstart", this.bind(function() {
2420
                if (this.opts.shouldFocusInput(this)) {
2421
                    this.search.focus();
2422
                }
2423
            }));
2424

    
2425
            selection.on("focus", this.bind(function(e) {
2426
                killEvent(e);
2427
            }));
2428

    
2429
            this.focusser.on("focus", this.bind(function(){
2430
                if (!this.container.hasClass("select2-container-active")) {
2431
                    this.opts.element.trigger($.Event("select2-focus"));
2432
                }
2433
                this.container.addClass("select2-container-active");
2434
            })).on("blur", this.bind(function() {
2435
                if (!this.opened()) {
2436
                    this.container.removeClass("select2-container-active");
2437
                    this.opts.element.trigger($.Event("select2-blur"));
2438
                }
2439
            }));
2440
            this.search.on("focus", this.bind(function(){
2441
                if (!this.container.hasClass("select2-container-active")) {
2442
                    this.opts.element.trigger($.Event("select2-focus"));
2443
                }
2444
                this.container.addClass("select2-container-active");
2445
            }));
2446

    
2447
            this.initContainerWidth();
2448
            this.opts.element.hide();
2449
            this.setPlaceholder();
2450

    
2451
        },
2452

    
2453
        // single
2454
        clear: function(triggerChange) {
2455
            var data=this.selection.data("select2-data");
2456
            if (data) { // guard against queued quick consecutive clicks
2457
                var evt = $.Event("select2-clearing");
2458
                this.opts.element.trigger(evt);
2459
                if (evt.isDefaultPrevented()) {
2460
                    return;
2461
                }
2462
                var placeholderOption = this.getPlaceholderOption();
2463
                this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2464
                this.selection.find(".select2-chosen").empty();
2465
                this.selection.removeData("select2-data");
2466
                this.setPlaceholder();
2467

    
2468
                if (triggerChange !== false){
2469
                    this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2470
                    this.triggerChange({removed:data});
2471
                }
2472
            }
2473
        },
2474

    
2475
        /**
2476
         * Sets selection based on source element's value
2477
         */
2478
        // single
2479
        initSelection: function () {
2480
            var selected;
2481
            if (this.isPlaceholderOptionSelected()) {
2482
                this.updateSelection(null);
2483
                this.close();
2484
                this.setPlaceholder();
2485
            } else {
2486
                var self = this;
2487
                this.opts.initSelection.call(null, this.opts.element, function(selected){
2488
                    if (selected !== undefined && selected !== null) {
2489
                        self.updateSelection(selected);
2490
                        self.close();
2491
                        self.setPlaceholder();
2492
                        self.lastSearchTerm = self.search.val();
2493
                    }
2494
                });
2495
            }
2496
        },
2497

    
2498
        isPlaceholderOptionSelected: function() {
2499
            var placeholderOption;
2500
            if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
2501
            return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
2502
                || (this.opts.element.val() === "")
2503
                || (this.opts.element.val() === undefined)
2504
                || (this.opts.element.val() === null);
2505
        },
2506

    
2507
        // single
2508
        prepareOpts: function () {
2509
            var opts = this.parent.prepareOpts.apply(this, arguments),
2510
                self=this;
2511

    
2512
            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2513
                // install the selection initializer
2514
                opts.initSelection = function (element, callback) {
2515
                    var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
2516
                    // a single select box always has a value, no need to null check 'selected'
2517
                    callback(self.optionToData(selected));
2518
                };
2519
            } else if ("data" in opts) {
2520
                // install default initSelection when applied to hidden input and data is local
2521
                opts.initSelection = opts.initSelection || function (element, callback) {
2522
                    var id = element.val();
2523
                    //search in data by id, storing the actual matching item
2524
                    var match = null;
2525
                    opts.query({
2526
                        matcher: function(term, text, el){
2527
                            var is_match = equal(id, opts.id(el));
2528
                            if (is_match) {
2529
                                match = el;
2530
                            }
2531
                            return is_match;
2532
                        },
2533
                        callback: !$.isFunction(callback) ? $.noop : function() {
2534
                            callback(match);
2535
                        }
2536
                    });
2537
                };
2538
            }
2539

    
2540
            return opts;
2541
        },
2542

    
2543
        // single
2544
        getPlaceholder: function() {
2545
            // if a placeholder is specified on a single select without a valid placeholder option ignore it
2546
            if (this.select) {
2547
                if (this.getPlaceholderOption() === undefined) {
2548
                    return undefined;
2549
                }
2550
            }
2551

    
2552
            return this.parent.getPlaceholder.apply(this, arguments);
2553
        },
2554

    
2555
        // single
2556
        setPlaceholder: function () {
2557
            var placeholder = this.getPlaceholder();
2558

    
2559
            if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2560

    
2561
                // check for a placeholder option if attached to a select
2562
                if (this.select && this.getPlaceholderOption() === undefined) return;
2563

    
2564
                this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2565

    
2566
                this.selection.addClass("select2-default");
2567

    
2568
                this.container.removeClass("select2-allowclear");
2569
            }
2570
        },
2571

    
2572
        // single
2573
        postprocessResults: function (data, initial, noHighlightUpdate) {
2574
            var selected = 0, self = this, showSearchInput = true;
2575

    
2576
            // find the selected element in the result list
2577

    
2578
            this.findHighlightableChoices().each2(function (i, elm) {
2579
                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2580
                    selected = i;
2581
                    return false;
2582
                }
2583
            });
2584

    
2585
            // and highlight it
2586
            if (noHighlightUpdate !== false) {
2587
                if (initial === true && selected >= 0) {
2588
                    this.highlight(selected);
2589
                } else {
2590
                    this.highlight(0);
2591
                }
2592
            }
2593

    
2594
            // hide the search box if this is the first we got the results and there are enough of them for search
2595

    
2596
            if (initial === true) {
2597
                var min = this.opts.minimumResultsForSearch;
2598
                if (min >= 0) {
2599
                    this.showSearch(countResults(data.results) >= min);
2600
                }
2601
            }
2602
        },
2603

    
2604
        // single
2605
        showSearch: function(showSearchInput) {
2606
            if (this.showSearchInput === showSearchInput) return;
2607

    
2608
            this.showSearchInput = showSearchInput;
2609

    
2610
            this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2611
            this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2612
            //add "select2-with-searchbox" to the container if search box is shown
2613
            $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2614
        },
2615

    
2616
        // single
2617
        onSelect: function (data, options) {
2618

    
2619
            if (!this.triggerSelect(data)) { return; }
2620

    
2621
            var old = this.opts.element.val(),
2622
                oldData = this.data();
2623

    
2624
            this.opts.element.val(this.id(data));
2625
            this.updateSelection(data);
2626

    
2627
            this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2628

    
2629
            this.lastSearchTerm = this.search.val();
2630
            this.close();
2631

    
2632
            if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
2633
                this.focusser.focus();
2634
            }
2635

    
2636
            if (!equal(old, this.id(data))) {
2637
                this.triggerChange({ added: data, removed: oldData });
2638
            }
2639
        },
2640

    
2641
        // single
2642
        updateSelection: function (data) {
2643

    
2644
            var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2645

    
2646
            this.selection.data("select2-data", data);
2647

    
2648
            container.empty();
2649
            if (data !== null) {
2650
                formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2651
            }
2652
            if (formatted !== undefined) {
2653
                container.append(formatted);
2654
            }
2655
            cssClass=this.opts.formatSelectionCssClass(data, container);
2656
            if (cssClass !== undefined) {
2657
                container.addClass(cssClass);
2658
            }
2659

    
2660
            this.selection.removeClass("select2-default");
2661

    
2662
            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2663
                this.container.addClass("select2-allowclear");
2664
            }
2665
        },
2666

    
2667
        // single
2668
        val: function () {
2669
            var val,
2670
                triggerChange = false,
2671
                data = null,
2672
                self = this,
2673
                oldData = this.data();
2674

    
2675
            if (arguments.length === 0) {
2676
                return this.opts.element.val();
2677
            }
2678

    
2679
            val = arguments[0];
2680

    
2681
            if (arguments.length > 1) {
2682
                triggerChange = arguments[1];
2683

    
2684
                if (this.opts.debug && console && console.warn) {
2685
                    console.warn(
2686
                        'Select2: The second option to `select2("val")` is not supported in Select2 4.0.0. ' +
2687
                        'The `change` event will always be triggered in 4.0.0.'
2688
                    );
2689
                }
2690
            }
2691

    
2692
            if (this.select) {
2693
                if (this.opts.debug && console && console.warn) {
2694
                    console.warn(
2695
                        'Select2: Setting the value on a <select> using `select2("val")` is no longer supported in 4.0.0. ' +
2696
                        'You can use the `.val(newValue).trigger("change")` method provided by jQuery instead.'
2697
                    );
2698
                }
2699

    
2700
                this.select
2701
                    .val(val)
2702
                    .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2703
                        data = self.optionToData(elm);
2704
                        return false;
2705
                    });
2706
                this.updateSelection(data);
2707
                this.setPlaceholder();
2708
                if (triggerChange) {
2709
                    this.triggerChange({added: data, removed:oldData});
2710
                }
2711
            } else {
2712
                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2713
                if (!val && val !== 0) {
2714
                    this.clear(triggerChange);
2715
                    return;
2716
                }
2717
                if (this.opts.initSelection === undefined) {
2718
                    throw new Error("cannot call val() if initSelection() is not defined");
2719
                }
2720
                this.opts.element.val(val);
2721
                this.opts.initSelection(this.opts.element, function(data){
2722
                    self.opts.element.val(!data ? "" : self.id(data));
2723
                    self.updateSelection(data);
2724
                    self.setPlaceholder();
2725
                    if (triggerChange) {
2726
                        self.triggerChange({added: data, removed:oldData});
2727
                    }
2728
                });
2729
            }
2730
        },
2731

    
2732
        // single
2733
        clearSearch: function () {
2734
            this.search.val("");
2735
            this.focusser.val("");
2736
        },
2737

    
2738
        // single
2739
        data: function(value) {
2740
            var data,
2741
                triggerChange = false;
2742

    
2743
            if (arguments.length === 0) {
2744
                data = this.selection.data("select2-data");
2745
                if (data == undefined) data = null;
2746
                return data;
2747
            } else {
2748
                if (this.opts.debug && console && console.warn) {
2749
                    console.warn(
2750
                        'Select2: The `select2("data")` method can no longer set selected values in 4.0.0, ' +
2751
                        'consider using the `.val()` method instead.'
2752
                    );
2753
                }
2754

    
2755
                if (arguments.length > 1) {
2756
                    triggerChange = arguments[1];
2757
                }
2758
                if (!value) {
2759
                    this.clear(triggerChange);
2760
                } else {
2761
                    data = this.data();
2762
                    this.opts.element.val(!value ? "" : this.id(value));
2763
                    this.updateSelection(value);
2764
                    if (triggerChange) {
2765
                        this.triggerChange({added: value, removed:data});
2766
                    }
2767
                }
2768
            }
2769
        }
2770
    });
2771

    
2772
    MultiSelect2 = clazz(AbstractSelect2, {
2773

    
2774
        // multi
2775
        createContainer: function () {
2776
            var container = $(document.createElement("div")).attr({
2777
                "class": "select2-container select2-container-multi"
2778
            }).html([
2779
                "<ul class='select2-choices'>",
2780
                "  <li class='select2-search-field'>",
2781
                "    <label for='' class='select2-offscreen'></label>",
2782
                "    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2783
                "  </li>",
2784
                "</ul>",
2785
                "<div class='select2-drop select2-drop-multi select2-display-none'>",
2786
                "   <ul class='select2-results'>",
2787
                "   </ul>",
2788
                "</div>"].join(""));
2789
            return container;
2790
        },
2791

    
2792
        // multi
2793
        prepareOpts: function () {
2794
            var opts = this.parent.prepareOpts.apply(this, arguments),
2795
                self=this;
2796

    
2797
            // TODO validate placeholder is a string if specified
2798
            if (opts.element.get(0).tagName.toLowerCase() === "select") {
2799
                // install the selection initializer
2800
                opts.initSelection = function (element, callback) {
2801

    
2802
                    var data = [];
2803

    
2804
                    element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
2805
                        data.push(self.optionToData(elm));
2806
                    });
2807
                    callback(data);
2808
                };
2809
            } else if ("data" in opts) {
2810
                // install default initSelection when applied to hidden input and data is local
2811
                opts.initSelection = opts.initSelection || function (element, callback) {
2812
                    var ids = splitVal(element.val(), opts.separator, opts.transformVal);
2813
                    //search in data by array of ids, storing matching items in a list
2814
                    var matches = [];
2815
                    opts.query({
2816
                        matcher: function(term, text, el){
2817
                            var is_match = $.grep(ids, function(id) {
2818
                                return equal(id, opts.id(el));
2819
                            }).length;
2820
                            if (is_match) {
2821
                                matches.push(el);
2822
                            }
2823
                            return is_match;
2824
                        },
2825
                        callback: !$.isFunction(callback) ? $.noop : function() {
2826
                            // reorder matches based on the order they appear in the ids array because right now
2827
                            // they are in the order in which they appear in data array
2828
                            var ordered = [];
2829
                            for (var i = 0; i < ids.length; i++) {
2830
                                var id = ids[i];
2831
                                for (var j = 0; j < matches.length; j++) {
2832
                                    var match = matches[j];
2833
                                    if (equal(id, opts.id(match))) {
2834
                                        ordered.push(match);
2835
                                        matches.splice(j, 1);
2836
                                        break;
2837
                                    }
2838
                                }
2839
                            }
2840
                            callback(ordered);
2841
                        }
2842
                    });
2843
                };
2844
            }
2845

    
2846
            return opts;
2847
        },
2848

    
2849
        // multi
2850
        selectChoice: function (choice) {
2851

    
2852
            var selected = this.container.find(".select2-search-choice-focus");
2853
            if (selected.length && choice && choice[0] == selected[0]) {
2854

    
2855
            } else {
2856
                if (selected.length) {
2857
                    this.opts.element.trigger("choice-deselected", selected);
2858
                }
2859
                selected.removeClass("select2-search-choice-focus");
2860
                if (choice && choice.length) {
2861
                    this.close();
2862
                    choice.addClass("select2-search-choice-focus");
2863
                    this.opts.element.trigger("choice-selected", choice);
2864
                }
2865
            }
2866
        },
2867

    
2868
        // multi
2869
        destroy: function() {
2870
            $("label[for='" + this.search.attr('id') + "']")
2871
                .attr('for', this.opts.element.attr("id"));
2872
            this.parent.destroy.apply(this, arguments);
2873

    
2874
            cleanupJQueryElements.call(this,
2875
                "searchContainer",
2876
                "selection"
2877
            );
2878
        },
2879

    
2880
        // multi
2881
        initContainer: function () {
2882

    
2883
            var selector = ".select2-choices", selection;
2884

    
2885
            this.searchContainer = this.container.find(".select2-search-field");
2886
            this.selection = selection = this.container.find(selector);
2887

    
2888
            var _this = this;
2889
            this.selection.on("click", ".select2-container:not(.select2-container-disabled) .select2-search-choice:not(.select2-locked)", function (e) {
2890
                _this.search[0].focus();
2891
                _this.selectChoice($(this));
2892
            });
2893

    
2894
            // rewrite labels from original element to focusser
2895
            this.search.attr("id", "s2id_autogen"+nextUid());
2896

    
2897
            this.search.prev()
2898
                .text($("label[for='" + this.opts.element.attr("id") + "']").text())
2899
                .attr('for', this.search.attr('id'));
2900
            this.opts.element.on('focus.select2', this.bind(function () { this.focus(); }));
2901

    
2902
            this.search.on("input paste", this.bind(function() {
2903
                if (this.search.attr('placeholder') && this.search.val().length == 0) return;
2904
                if (!this.isInterfaceEnabled()) return;
2905
                if (!this.opened()) {
2906
                    this.open();
2907
                }
2908
            }));
2909

    
2910
            this.search.attr("tabindex", this.elementTabIndex);
2911

    
2912
            this.keydowns = 0;
2913
            this.search.on("keydown", this.bind(function (e) {
2914
                if (!this.isInterfaceEnabled()) return;
2915

    
2916
                ++this.keydowns;
2917
                var selected = selection.find(".select2-search-choice-focus");
2918
                var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2919
                var next = selected.next(".select2-search-choice:not(.select2-locked)");
2920
                var pos = getCursorInfo(this.search);
2921

    
2922
                if (selected.length &&
2923
                    (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2924
                    var selectedChoice = selected;
2925
                    if (e.which == KEY.LEFT && prev.length) {
2926
                        selectedChoice = prev;
2927
                    }
2928
                    else if (e.which == KEY.RIGHT) {
2929
                        selectedChoice = next.length ? next : null;
2930
                    }
2931
                    else if (e.which === KEY.BACKSPACE) {
2932
                        if (this.unselect(selected.first())) {
2933
                            this.search.width(10);
2934
                            selectedChoice = prev.length ? prev : next;
2935
                        }
2936
                    } else if (e.which == KEY.DELETE) {
2937
                        if (this.unselect(selected.first())) {
2938
                            this.search.width(10);
2939
                            selectedChoice = next.length ? next : null;
2940
                        }
2941
                    } else if (e.which == KEY.ENTER) {
2942
                        selectedChoice = null;
2943
                    }
2944

    
2945
                    this.selectChoice(selectedChoice);
2946
                    killEvent(e);
2947
                    if (!selectedChoice || !selectedChoice.length) {
2948
                        this.open();
2949
                    }
2950
                    return;
2951
                } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2952
                    || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2953

    
2954
                    this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2955
                    killEvent(e);
2956
                    return;
2957
                } else {
2958
                    this.selectChoice(null);
2959
                }
2960

    
2961
                if (this.opened()) {
2962
                    switch (e.which) {
2963
                    case KEY.UP:
2964
                    case KEY.DOWN:
2965
                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2966
                        killEvent(e);
2967
                        return;
2968
                    case KEY.ENTER:
2969
                        this.selectHighlighted();
2970
                        killEvent(e);
2971
                        return;
2972
                    case KEY.TAB:
2973
                        this.selectHighlighted({noFocus:true});
2974
                        this.close();
2975
                        return;
2976
                    case KEY.ESC:
2977
                        this.cancel(e);
2978
                        killEvent(e);
2979
                        return;
2980
                    }
2981
                }
2982

    
2983
                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2984
                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2985
                    return;
2986
                }
2987

    
2988
                if (e.which === KEY.ENTER) {
2989
                    if (this.opts.openOnEnter === false) {
2990
                        return;
2991
                    } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2992
                        return;
2993
                    }
2994
                }
2995

    
2996
                this.open();
2997

    
2998
                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2999
                    // prevent the page from scrolling
3000
                    killEvent(e);
3001
                }
3002

    
3003
                if (e.which === KEY.ENTER) {
3004
                    // prevent form from being submitted
3005
                    killEvent(e);
3006
                }
3007

    
3008
            }));
3009

    
3010
            this.search.on("keyup", this.bind(function (e) {
3011
                this.keydowns = 0;
3012
                this.resizeSearch();
3013
            })
3014
            );
3015

    
3016
            this.search.on("blur", this.bind(function(e) {
3017
                this.container.removeClass("select2-container-active");
3018
                this.search.removeClass("select2-focused");
3019
                this.selectChoice(null);
3020
                if (!this.opened()) this.clearSearch();
3021
                e.stopImmediatePropagation();
3022
                this.opts.element.trigger($.Event("select2-blur"));
3023
            }));
3024

    
3025
            this.container.on("click", selector, this.bind(function (e) {
3026
                if (!this.isInterfaceEnabled()) return;
3027
                if ($(e.target).closest(".select2-search-choice").length > 0) {
3028
                    // clicked inside a select2 search choice, do not open
3029
                    return;
3030
                }
3031
                this.selectChoice(null);
3032
                this.clearPlaceholder();
3033
                if (!this.container.hasClass("select2-container-active")) {
3034
                    this.opts.element.trigger($.Event("select2-focus"));
3035
                }
3036
                this.open();
3037
                this.focusSearch();
3038
                e.preventDefault();
3039
            }));
3040

    
3041
            this.container.on("focus", selector, this.bind(function () {
3042
                if (!this.isInterfaceEnabled()) return;
3043
                if (!this.container.hasClass("select2-container-active")) {
3044
                    this.opts.element.trigger($.Event("select2-focus"));
3045
                }
3046
                this.container.addClass("select2-container-active");
3047
                this.dropdown.addClass("select2-drop-active");
3048
                this.clearPlaceholder();
3049
            }));
3050

    
3051
            this.initContainerWidth();
3052
            this.opts.element.hide();
3053

    
3054
            // set the placeholder if necessary
3055
            this.clearSearch();
3056
        },
3057

    
3058
        // multi
3059
        enableInterface: function() {
3060
            if (this.parent.enableInterface.apply(this, arguments)) {
3061
                this.search.prop("disabled", !this.isInterfaceEnabled());
3062
            }
3063
        },
3064

    
3065
        // multi
3066
        initSelection: function () {
3067
            var data;
3068
            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
3069
                this.updateSelection([]);
3070
                this.close();
3071
                // set the placeholder if necessary
3072
                this.clearSearch();
3073
            }
3074
            if (this.select || this.opts.element.val() !== "") {
3075
                var self = this;
3076
                this.opts.initSelection.call(null, this.opts.element, function(data){
3077
                    if (data !== undefined && data !== null) {
3078
                        self.updateSelection(data);
3079
                        self.close();
3080
                        // set the placeholder if necessary
3081
                        self.clearSearch();
3082
                    }
3083
                });
3084
            }
3085
        },
3086

    
3087
        // multi
3088
        clearSearch: function () {
3089
            var placeholder = this.getPlaceholder(),
3090
                maxWidth = this.getMaxSearchWidth();
3091

    
3092
            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
3093
                this.search.val(placeholder).addClass("select2-default");
3094
                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
3095
                // 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
3096
                this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
3097
            } else {
3098
                this.search.val("").width(10);
3099
            }
3100
        },
3101

    
3102
        // multi
3103
        clearPlaceholder: function () {
3104
            if (this.search.hasClass("select2-default")) {
3105
                this.search.val("").removeClass("select2-default");
3106
            }
3107
        },
3108

    
3109
        // multi
3110
        opening: function () {
3111
            this.clearPlaceholder(); // should be done before super so placeholder is not used to search
3112
            this.resizeSearch();
3113

    
3114
            this.parent.opening.apply(this, arguments);
3115

    
3116
            this.focusSearch();
3117

    
3118
            this.prefillNextSearchTerm();
3119
            this.updateResults(true);
3120

    
3121
            if (this.opts.shouldFocusInput(this)) {
3122
                this.search.focus();
3123
            }
3124
            this.opts.element.trigger($.Event("select2-open"));
3125
        },
3126

    
3127
        // multi
3128
        close: function () {
3129
            if (!this.opened()) return;
3130
            this.parent.close.apply(this, arguments);
3131
        },
3132

    
3133
        // multi
3134
        focus: function () {
3135
            this.close();
3136
            this.search.focus();
3137
        },
3138

    
3139
        // multi
3140
        isFocused: function () {
3141
            return this.search.hasClass("select2-focused");
3142
        },
3143

    
3144
        // multi
3145
        updateSelection: function (data) {
3146
            var ids = {}, filtered = [], self = this;
3147

    
3148
            // filter out duplicates
3149
            $(data).each(function () {
3150
                if (!(self.id(this) in ids)) {
3151
                    ids[self.id(this)] = 0;
3152
                    filtered.push(this);
3153
                }
3154
            });
3155

    
3156
            this.selection.find(".select2-search-choice").remove();
3157
            this.addSelectedChoice(filtered);
3158
            self.postprocessResults();
3159
        },
3160

    
3161
        // multi
3162
        tokenize: function() {
3163
            var input = this.search.val();
3164
            input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
3165
            if (input != null && input != undefined) {
3166
                this.search.val(input);
3167
                if (input.length > 0) {
3168
                    this.open();
3169
                }
3170
            }
3171

    
3172
        },
3173

    
3174
        // multi
3175
        onSelect: function (data, options) {
3176

    
3177
            if (!this.triggerSelect(data) || data.text === "") { return; }
3178

    
3179
            this.addSelectedChoice(data);
3180

    
3181
            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
3182

    
3183
            // keep track of the search's value before it gets cleared
3184
            this.lastSearchTerm = this.search.val();
3185

    
3186
            this.clearSearch();
3187
            this.updateResults();
3188

    
3189
            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
3190

    
3191
            if (this.opts.closeOnSelect) {
3192
                this.close();
3193
                this.search.width(10);
3194
            } else {
3195
                if (this.countSelectableResults()>0) {
3196
                    this.search.width(10);
3197
                    this.resizeSearch();
3198
                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
3199
                        // if we reached max selection size repaint the results so choices
3200
                        // are replaced with the max selection reached message
3201
                        this.updateResults(true);
3202
                    } else {
3203
                        // initializes search's value with nextSearchTerm and update search result
3204
                        if (this.prefillNextSearchTerm()) {
3205
                            this.updateResults();
3206
                        }
3207
                    }
3208
                    this.positionDropdown();
3209
                } else {
3210
                    // if nothing left to select close
3211
                    this.close();
3212
                    this.search.width(10);
3213
                }
3214
            }
3215

    
3216
            // since its not possible to select an element that has already been
3217
            // added we do not need to check if this is a new element before firing change
3218
            this.triggerChange({ added: data });
3219

    
3220
            if (!options || !options.noFocus)
3221
                this.focusSearch();
3222
        },
3223

    
3224
        // multi
3225
        cancel: function () {
3226
            this.close();
3227
            this.focusSearch();
3228
        },
3229

    
3230
        addSelectedChoice: function (data) {
3231
            var val = this.getVal(), self = this;
3232
            $(data).each(function () {
3233
                val.push(self.createChoice(this));
3234
            });
3235
            this.setVal(val);
3236
        },
3237

    
3238
        createChoice: function (data) {
3239
            var enableChoice = !data.locked,
3240
                enabledItem = $(
3241
                    "<li class='select2-search-choice'>" +
3242
                    "    <div></div>" +
3243
                    "    <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
3244
                    "</li>"),
3245
                disabledItem = $(
3246
                    "<li class='select2-search-choice select2-locked'>" +
3247
                    "<div></div>" +
3248
                    "</li>");
3249
            var choice = enableChoice ? enabledItem : disabledItem,
3250
                id = this.id(data),
3251
                formatted,
3252
                cssClass;
3253

    
3254
            formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
3255
            if (formatted != undefined) {
3256
                choice.find("div").replaceWith($("<div></div>").html(formatted));
3257
            }
3258
            cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
3259
            if (cssClass != undefined) {
3260
                choice.addClass(cssClass);
3261
            }
3262

    
3263
            if(enableChoice){
3264
              choice.find(".select2-search-choice-close")
3265
                  .on("mousedown", killEvent)
3266
                  .on("click dblclick", this.bind(function (e) {
3267
                  if (!this.isInterfaceEnabled()) return;
3268

    
3269
                  this.unselect($(e.target));
3270
                  this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
3271
                  killEvent(e);
3272
                  this.close();
3273
                  this.focusSearch();
3274
              })).on("focus", this.bind(function () {
3275
                  if (!this.isInterfaceEnabled()) return;
3276
                  this.container.addClass("select2-container-active");
3277
                  this.dropdown.addClass("select2-drop-active");
3278
              }));
3279
            }
3280

    
3281
            choice.data("select2-data", data);
3282
            choice.insertBefore(this.searchContainer);
3283

    
3284
            return id;
3285
        },
3286

    
3287
        // multi
3288
        unselect: function (selected) {
3289
            var val = this.getVal(),
3290
                data,
3291
                index;
3292
            selected = selected.closest(".select2-search-choice");
3293

    
3294
            if (selected.length === 0) {
3295
                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
3296
            }
3297

    
3298
            data = selected.data("select2-data");
3299

    
3300
            if (!data) {
3301
                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
3302
                // and invoked on an element already removed
3303
                return;
3304
            }
3305

    
3306
            var evt = $.Event("select2-removing");
3307
            evt.val = this.id(data);
3308
            evt.choice = data;
3309
            this.opts.element.trigger(evt);
3310

    
3311
            if (evt.isDefaultPrevented()) {
3312
                return false;
3313
            }
3314

    
3315
            while((index = indexOf(this.id(data), val)) >= 0) {
3316
                val.splice(index, 1);
3317
                this.setVal(val);
3318
                if (this.select) this.postprocessResults();
3319
            }
3320

    
3321
            selected.remove();
3322

    
3323
            this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
3324
            this.triggerChange({ removed: data });
3325

    
3326
            return true;
3327
        },
3328

    
3329
        // multi
3330
        postprocessResults: function (data, initial, noHighlightUpdate) {
3331
            var val = this.getVal(),
3332
                choices = this.results.find(".select2-result"),
3333
                compound = this.results.find(".select2-result-with-children"),
3334
                self = this;
3335

    
3336
            choices.each2(function (i, choice) {
3337
                var id = self.id(choice.data("select2-data"));
3338
                if (indexOf(id, val) >= 0) {
3339
                    choice.addClass("select2-selected");
3340
                    // mark all children of the selected parent as selected
3341
                    choice.find(".select2-result-selectable").addClass("select2-selected");
3342
                }
3343
            });
3344

    
3345
            compound.each2(function(i, choice) {
3346
                // hide an optgroup if it doesn't have any selectable children
3347
                if (!choice.is('.select2-result-selectable')
3348
                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
3349
                    choice.addClass("select2-selected");
3350
                }
3351
            });
3352

    
3353
            if (this.highlight() == -1 && noHighlightUpdate !== false && this.opts.closeOnSelect === true){
3354
                self.highlight(0);
3355
            }
3356

    
3357
            //If all results are chosen render formatNoMatches
3358
            if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
3359
                if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
3360
                    if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
3361
                        this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "</li>");
3362
                    }
3363
                }
3364
            }
3365

    
3366
        },
3367

    
3368
        // multi
3369
        getMaxSearchWidth: function() {
3370
            return this.selection.width() - getSideBorderPadding(this.search);
3371
        },
3372

    
3373
        // multi
3374
        resizeSearch: function () {
3375
            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
3376
                sideBorderPadding = getSideBorderPadding(this.search);
3377

    
3378
            minimumWidth = measureTextWidth(this.search) + 10;
3379

    
3380
            left = this.search.offset().left;
3381

    
3382
            maxWidth = this.selection.width();
3383
            containerLeft = this.selection.offset().left;
3384

    
3385
            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
3386

    
3387
            if (searchWidth < minimumWidth) {
3388
                searchWidth = maxWidth - sideBorderPadding;
3389
            }
3390

    
3391
            if (searchWidth < 40) {
3392
                searchWidth = maxWidth - sideBorderPadding;
3393
            }
3394

    
3395
            if (searchWidth <= 0) {
3396
              searchWidth = minimumWidth;
3397
            }
3398

    
3399
            this.search.width(Math.floor(searchWidth));
3400
        },
3401

    
3402
        // multi
3403
        getVal: function () {
3404
            var val;
3405
            if (this.select) {
3406
                val = this.select.val();
3407
                return val === null ? [] : val;
3408
            } else {
3409
                val = this.opts.element.val();
3410
                return splitVal(val, this.opts.separator, this.opts.transformVal);
3411
            }
3412
        },
3413

    
3414
        // multi
3415
        setVal: function (val) {
3416
            if (this.select) {
3417
                this.select.val(val);
3418
            } else {
3419
                var unique = [], valMap = {};
3420
                // filter out duplicates
3421
                $(val).each(function () {
3422
                    if (!(this in valMap)) {
3423
                        unique.push(this);
3424
                        valMap[this] = 0;
3425
                    }
3426
                });
3427
                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
3428
            }
3429
        },
3430

    
3431
        // multi
3432
        buildChangeDetails: function (old, current) {
3433
            var current = current.slice(0),
3434
                old = old.slice(0);
3435

    
3436
            // remove intersection from each array
3437
            for (var i = 0; i < current.length; i++) {
3438
                for (var j = 0; j < old.length; j++) {
3439
                    if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
3440
                        current.splice(i, 1);
3441
                        i--;
3442
                        old.splice(j, 1);
3443
                        break;
3444
                    }
3445
                }
3446
            }
3447

    
3448
            return {added: current, removed: old};
3449
        },
3450

    
3451

    
3452
        // multi
3453
        val: function (val, triggerChange) {
3454
            var oldData, self=this;
3455

    
3456
            if (arguments.length === 0) {
3457
                return this.getVal();
3458
            }
3459

    
3460
            oldData=this.data();
3461
            if (!oldData.length) oldData=[];
3462

    
3463
            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3464
            if (!val && val !== 0) {
3465
                this.opts.element.val("");
3466
                this.updateSelection([]);
3467
                this.clearSearch();
3468
                if (triggerChange) {
3469
                    this.triggerChange({added: this.data(), removed: oldData});
3470
                }
3471
                return;
3472
            }
3473

    
3474
            // val is a list of ids
3475
            this.setVal(val);
3476

    
3477
            if (this.select) {
3478
                this.opts.initSelection(this.select, this.bind(this.updateSelection));
3479
                if (triggerChange) {
3480
                    this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3481
                }
3482
            } else {
3483
                if (this.opts.initSelection === undefined) {
3484
                    throw new Error("val() cannot be called if initSelection() is not defined");
3485
                }
3486

    
3487
                this.opts.initSelection(this.opts.element, function(data){
3488
                    var ids=$.map(data, self.id);
3489
                    self.setVal(ids);
3490
                    self.updateSelection(data);
3491
                    self.clearSearch();
3492
                    if (triggerChange) {
3493
                        self.triggerChange(self.buildChangeDetails(oldData, self.data()));
3494
                    }
3495
                });
3496
            }
3497
            this.clearSearch();
3498
        },
3499

    
3500
        // multi
3501
        onSortStart: function() {
3502
            if (this.select) {
3503
                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3504
            }
3505

    
3506
            // collapse search field into 0 width so its container can be collapsed as well
3507
            this.search.width(0);
3508
            // hide the container
3509
            this.searchContainer.hide();
3510
        },
3511

    
3512
        // multi
3513
        onSortEnd:function() {
3514

    
3515
            var val=[], self=this;
3516

    
3517
            // show search and move it to the end of the list
3518
            this.searchContainer.show();
3519
            // make sure the search container is the last item in the list
3520
            this.searchContainer.appendTo(this.searchContainer.parent());
3521
            // since we collapsed the width in dragStarted, we resize it here
3522
            this.resizeSearch();
3523

    
3524
            // update selection
3525
            this.selection.find(".select2-search-choice").each(function() {
3526
                val.push(self.opts.id($(this).data("select2-data")));
3527
            });
3528
            this.setVal(val);
3529
            this.triggerChange();
3530
        },
3531

    
3532
        // multi
3533
        data: function(values, triggerChange) {
3534
            var self=this, ids, old;
3535
            if (arguments.length === 0) {
3536
                 return this.selection
3537
                     .children(".select2-search-choice")
3538
                     .map(function() { return $(this).data("select2-data"); })
3539
                     .get();
3540
            } else {
3541
                old = this.data();
3542
                if (!values) { values = []; }
3543
                ids = $.map(values, function(e) { return self.opts.id(e); });
3544
                this.setVal(ids);
3545
                this.updateSelection(values);
3546
                this.clearSearch();
3547
                if (triggerChange) {
3548
                    this.triggerChange(this.buildChangeDetails(old, this.data()));
3549
                }
3550
            }
3551
        }
3552
    });
3553

    
3554
    $.fn.select2 = function () {
3555

    
3556
        var args = Array.prototype.slice.call(arguments, 0),
3557
            opts,
3558
            select2,
3559
            method, value, multiple,
3560
            allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3561
            valueMethods = ["opened", "isFocused", "container", "dropdown"],
3562
            propertyMethods = ["val", "data"],
3563
            methodsMap = { search: "externalSearch" };
3564

    
3565
        this.each(function () {
3566
            if (args.length === 0 || typeof(args[0]) === "object") {
3567
                opts = args.length === 0 ? {} : $.extend({}, args[0]);
3568
                opts.element = $(this);
3569

    
3570
                if (opts.element.get(0).tagName.toLowerCase() === "select") {
3571
                    multiple = opts.element.prop("multiple");
3572
                } else {
3573
                    multiple = opts.multiple || false;
3574
                    if ("tags" in opts) {opts.multiple = multiple = true;}
3575
                }
3576

    
3577
                select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
3578
                select2.init(opts);
3579
            } else if (typeof(args[0]) === "string") {
3580

    
3581
                if (indexOf(args[0], allowedMethods) < 0) {
3582
                    throw "Unknown method: " + args[0];
3583
                }
3584

    
3585
                value = undefined;
3586
                select2 = $(this).data("select2");
3587
                if (select2 === undefined) return;
3588

    
3589
                method=args[0];
3590

    
3591
                if (method === "container") {
3592
                    value = select2.container;
3593
                } else if (method === "dropdown") {
3594
                    value = select2.dropdown;
3595
                } else {
3596
                    if (methodsMap[method]) method = methodsMap[method];
3597

    
3598
                    value = select2[method].apply(select2, args.slice(1));
3599
                }
3600
                if (indexOf(args[0], valueMethods) >= 0
3601
                    || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
3602
                    return false; // abort the iteration, ready to return first matched value
3603
                }
3604
            } else {
3605
                throw "Invalid arguments to select2 plugin: " + args;
3606
            }
3607
        });
3608
        return (value === undefined) ? this : value;
3609
    };
3610

    
3611
    // plugin defaults, accessible to users
3612
    $.fn.select2.defaults = {
3613
        debug: false,
3614
        width: "copy",
3615
        loadMorePadding: 0,
3616
        closeOnSelect: true,
3617
        openOnEnter: true,
3618
        containerCss: {},
3619
        dropdownCss: {},
3620
        containerCssClass: "",
3621
        dropdownCssClass: "",
3622
        formatResult: function(result, container, query, escapeMarkup) {
3623
            var markup=[];
3624
            markMatch(this.text(result), query.term, markup, escapeMarkup);
3625
            return markup.join("");
3626
        },
3627
        transformVal: function(val) {
3628
            return $.trim(val);
3629
        },
3630
        formatSelection: function (data, container, escapeMarkup) {
3631
            return data ? escapeMarkup(this.text(data)) : undefined;
3632
        },
3633
        sortResults: function (results, container, query) {
3634
            return results;
3635
        },
3636
        formatResultCssClass: function(data) {return data.css;},
3637
        formatSelectionCssClass: function(data, container) {return undefined;},
3638
        minimumResultsForSearch: 0,
3639
        minimumInputLength: 0,
3640
        maximumInputLength: null,
3641
        maximumSelectionSize: 0,
3642
        id: function (e) { return e == undefined ? null : e.id; },
3643
        text: function (e) {
3644
          if (e && this.data && this.data.text) {
3645
            if ($.isFunction(this.data.text)) {
3646
              return this.data.text(e);
3647
            } else {
3648
              return e[this.data.text];
3649
            }
3650
          } else {
3651
            return e.text;
3652
          }
3653
        },
3654
        matcher: function(term, text) {
3655
            return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3656
        },
3657
        separator: ",",
3658
        tokenSeparators: [],
3659
        tokenizer: defaultTokenizer,
3660
        escapeMarkup: defaultEscapeMarkup,
3661
        blurOnChange: false,
3662
        selectOnBlur: false,
3663
        adaptContainerCssClass: function(c) { return c; },
3664
        adaptDropdownCssClass: function(c) { return null; },
3665
        nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
3666
        searchInputPlaceholder: '',
3667
        createSearchChoicePosition: 'top',
3668
        shouldFocusInput: function (instance) {
3669
            // Attempt to detect touch devices
3670
            var supportsTouchEvents = (('ontouchstart' in window) ||
3671
                                       (navigator.msMaxTouchPoints > 0));
3672

    
3673
            // Only devices which support touch events should be special cased
3674
            if (!supportsTouchEvents) {
3675
                return true;
3676
            }
3677

    
3678
            // Never focus the input if search is disabled
3679
            if (instance.opts.minimumResultsForSearch < 0) {
3680
                return false;
3681
            }
3682

    
3683
            return true;
3684
        }
3685
    };
3686

    
3687
    $.fn.select2.locales = [];
3688

    
3689
    $.fn.select2.locales['en'] = {
3690
         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."; },
3691
         formatNoMatches: function () { return "No matches found"; },
3692
         formatAjaxError: function (jqXHR, textStatus, errorThrown) { return "Loading failed"; },
3693
         formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); },
3694
         formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
3695
         formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3696
         formatLoadMore: function (pageNumber) { return "Loading more results…"; },
3697
         formatSearching: function () { return "Searching…"; }
3698
    };
3699

    
3700
    $.extend($.fn.select2.defaults, $.fn.select2.locales['en']);
3701

    
3702
    $.fn.select2.ajaxDefaults = {
3703
        transport: $.ajax,
3704
        params: {
3705
            type: "GET",
3706
            cache: false,
3707
            dataType: "json"
3708
        }
3709
    };
3710

    
3711
    // exports
3712
    window.Select2 = {
3713
        query: {
3714
            ajax: ajax,
3715
            local: local,
3716
            tags: tags
3717
        }, util: {
3718
            debounce: debounce,
3719
            markMatch: markMatch,
3720
            escapeMarkup: defaultEscapeMarkup,
3721
            stripDiacritics: stripDiacritics
3722
        }, "class": {
3723
            "abstract": AbstractSelect2,
3724
            "single": SingleSelect2,
3725
            "multi": MultiSelect2
3726
        }
3727
    };
3728

    
3729
}(jQuery));
(13-13/62)