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