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