source: main/trunk/model-interfaces-dev/heritage-nz/iframe/heritage-nz-dl_files/select2.js@ 32796

Last change on this file since 32796 was 32796, checked in by davidb, 5 years ago

Initial set of files to provide look and feel of Heritage NZ site, plus SVN clickable map in an iframe

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