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