source: pro-violet-viettel/docs/template/assets/js/uncompressed/select2.js @ 400

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