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