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