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