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