bootstrap-select.js 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591
  1. /*!
  2. * Bootstrap-select v1.13.0-beta (https://developer.snapappointments.com/bootstrap-select)
  3. *
  4. * Copyright 2012-2018 SnapAppointments, LLC
  5. * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
  6. */
  7. (function (root, factory) {
  8. if (typeof define === 'function' && define.amd) {
  9. // AMD. Register as an anonymous module unless amdModuleId is set
  10. define(["jquery"], function (a0) {
  11. return (factory(a0));
  12. });
  13. } else if (typeof module === 'object' && module.exports) {
  14. // Node. Does not work with strict CommonJS, but
  15. // only CommonJS-like environments that support module.exports,
  16. // like Node.
  17. module.exports = factory(require("jquery"));
  18. } else {
  19. factory(root["jQuery"]);
  20. }
  21. }(this, function (jQuery) {
  22. (function ($) {
  23. 'use strict';
  24. var testElement = document.createElement('_');
  25. testElement.classList.toggle('c3', false);
  26. // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
  27. // support the second argument.
  28. if (testElement.classList.contains('c3')) {
  29. var _toggle = DOMTokenList.prototype.toggle;
  30. DOMTokenList.prototype.toggle = function(token, force) {
  31. if (1 in arguments && !this.contains(token) === !force) {
  32. return force;
  33. } else {
  34. return _toggle.call(this, token);
  35. }
  36. };
  37. }
  38. // shallow array comparison
  39. function isEqual (array1, array2) {
  40. return array1.length === array2.length && array1.every(function(element, index) {
  41. return element === array2[index];
  42. });
  43. };
  44. //<editor-fold desc="Shims">
  45. if (!String.prototype.startsWith) {
  46. (function () {
  47. 'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
  48. var defineProperty = (function () {
  49. // IE 8 only supports `Object.defineProperty` on DOM elements
  50. try {
  51. var object = {};
  52. var $defineProperty = Object.defineProperty;
  53. var result = $defineProperty(object, object, object) && $defineProperty;
  54. } catch (error) {
  55. }
  56. return result;
  57. }());
  58. var toString = {}.toString;
  59. var startsWith = function (search) {
  60. if (this == null) {
  61. throw new TypeError();
  62. }
  63. var string = String(this);
  64. if (search && toString.call(search) == '[object RegExp]') {
  65. throw new TypeError();
  66. }
  67. var stringLength = string.length;
  68. var searchString = String(search);
  69. var searchLength = searchString.length;
  70. var position = arguments.length > 1 ? arguments[1] : undefined;
  71. // `ToInteger`
  72. var pos = position ? Number(position) : 0;
  73. if (pos != pos) { // better `isNaN`
  74. pos = 0;
  75. }
  76. var start = Math.min(Math.max(pos, 0), stringLength);
  77. // Avoid the `indexOf` call if no match is possible
  78. if (searchLength + start > stringLength) {
  79. return false;
  80. }
  81. var index = -1;
  82. while (++index < searchLength) {
  83. if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
  84. return false;
  85. }
  86. }
  87. return true;
  88. };
  89. if (defineProperty) {
  90. defineProperty(String.prototype, 'startsWith', {
  91. 'value': startsWith,
  92. 'configurable': true,
  93. 'writable': true
  94. });
  95. } else {
  96. String.prototype.startsWith = startsWith;
  97. }
  98. }());
  99. }
  100. if (!Object.keys) {
  101. Object.keys = function (
  102. o, // object
  103. k, // key
  104. r // result array
  105. ){
  106. // initialize object and result
  107. r=[];
  108. // iterate over object keys
  109. for (k in o)
  110. // fill result array with non-prototypical keys
  111. r.hasOwnProperty.call(o, k) && r.push(k);
  112. // return result
  113. return r;
  114. };
  115. }
  116. // set data-selected on select element if the value has been programmatically selected
  117. // prior to initialization of bootstrap-select
  118. // * consider removing or replacing an alternative method *
  119. var valHooks = {
  120. useDefault: false,
  121. _set: $.valHooks.select.set
  122. };
  123. $.valHooks.select.set = function (elem, value) {
  124. if (value && !valHooks.useDefault) $(elem).data('selected', true);
  125. return valHooks._set.apply(this, arguments);
  126. };
  127. var changed_arguments = null;
  128. var EventIsSupported = (function () {
  129. try {
  130. new Event('change');
  131. return true;
  132. } catch (e) {
  133. return false;
  134. }
  135. })();
  136. $.fn.triggerNative = function (eventName) {
  137. var el = this[0],
  138. event;
  139. if (el.dispatchEvent) { // for modern browsers & IE9+
  140. if (EventIsSupported) {
  141. // For modern browsers
  142. event = new Event(eventName, {
  143. bubbles: true
  144. });
  145. } else {
  146. // For IE since it doesn't support Event constructor
  147. event = document.createEvent('Event');
  148. event.initEvent(eventName, true, false);
  149. }
  150. el.dispatchEvent(event);
  151. } else if (el.fireEvent) { // for IE8
  152. event = document.createEventObject();
  153. event.eventType = eventName;
  154. el.fireEvent('on' + eventName, event);
  155. } else {
  156. // fall back to jQuery.trigger
  157. this.trigger(eventName);
  158. }
  159. };
  160. //</editor-fold>
  161. function stringSearch(li, searchString, method, normalize) {
  162. var stringTypes = [
  163. 'content',
  164. 'subtext',
  165. 'tokens'
  166. ],
  167. searchSuccess = false;
  168. for (var i = 0; i < stringTypes.length; i++) {
  169. var stringType = stringTypes[i],
  170. string = li[stringType];
  171. if (string) {
  172. if (normalize) string = normalizeToBase(string);
  173. string = string.toUpperCase();
  174. if (method === 'contains') {
  175. searchSuccess = string.indexOf(searchString) >= 0;
  176. } else {
  177. searchSuccess = string.startsWith(searchString);
  178. }
  179. if (searchSuccess) break;
  180. }
  181. }
  182. return searchSuccess;
  183. }
  184. function toInteger(value) {
  185. return parseInt(value, 10) || 0;
  186. }
  187. /**
  188. * Remove all diatrics from the given text.
  189. * @access private
  190. * @param {String} text
  191. * @returns {String}
  192. */
  193. function normalizeToBase(text) {
  194. var rExps = [
  195. {re: /[\xC0-\xC6]/g, ch: "A"},
  196. {re: /[\xE0-\xE6]/g, ch: "a"},
  197. {re: /[\xC8-\xCB]/g, ch: "E"},
  198. {re: /[\xE8-\xEB]/g, ch: "e"},
  199. {re: /[\xCC-\xCF]/g, ch: "I"},
  200. {re: /[\xEC-\xEF]/g, ch: "i"},
  201. {re: /[\xD2-\xD6]/g, ch: "O"},
  202. {re: /[\xF2-\xF6]/g, ch: "o"},
  203. {re: /[\xD9-\xDC]/g, ch: "U"},
  204. {re: /[\xF9-\xFC]/g, ch: "u"},
  205. {re: /[\xC7-\xE7]/g, ch: "c"},
  206. {re: /[\xD1]/g, ch: "N"},
  207. {re: /[\xF1]/g, ch: "n"}
  208. ];
  209. $.each(rExps, function () {
  210. text = text ? text.replace(this.re, this.ch) : '';
  211. });
  212. return text;
  213. }
  214. // List of HTML entities for escaping.
  215. var escapeMap = {
  216. '&': '&amp;',
  217. '<': '&lt;',
  218. '>': '&gt;',
  219. '"': '&quot;',
  220. "'": '&#x27;',
  221. '`': '&#x60;'
  222. };
  223. var unescapeMap = {
  224. '&amp;': '&',
  225. '&lt;': '<',
  226. '&gt;': '>',
  227. '&quot;': '"',
  228. '&#x27;': "'",
  229. '&#x60;': '`'
  230. };
  231. // Functions for escaping and unescaping strings to/from HTML interpolation.
  232. var createEscaper = function (map) {
  233. var escaper = function (match) {
  234. return map[match];
  235. };
  236. // Regexes for identifying a key that needs to be escaped.
  237. var source = '(?:' + Object.keys(map).join('|') + ')';
  238. var testRegexp = RegExp(source);
  239. var replaceRegexp = RegExp(source, 'g');
  240. return function (string) {
  241. string = string == null ? '' : '' + string;
  242. return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
  243. };
  244. };
  245. var htmlEscape = createEscaper(escapeMap);
  246. var htmlUnescape = createEscaper(unescapeMap);
  247. /**
  248. * ------------------------------------------------------------------------
  249. * Constants
  250. * ------------------------------------------------------------------------
  251. */
  252. var keyCodeMap = {
  253. 32: ' ',
  254. 48: '0',
  255. 49: '1',
  256. 50: '2',
  257. 51: '3',
  258. 52: '4',
  259. 53: '5',
  260. 54: '6',
  261. 55: '7',
  262. 56: '8',
  263. 57: '9',
  264. 59: ';',
  265. 65: 'A',
  266. 66: 'B',
  267. 67: 'C',
  268. 68: 'D',
  269. 69: 'E',
  270. 70: 'F',
  271. 71: 'G',
  272. 72: 'H',
  273. 73: 'I',
  274. 74: 'J',
  275. 75: 'K',
  276. 76: 'L',
  277. 77: 'M',
  278. 78: 'N',
  279. 79: 'O',
  280. 80: 'P',
  281. 81: 'Q',
  282. 82: 'R',
  283. 83: 'S',
  284. 84: 'T',
  285. 85: 'U',
  286. 86: 'V',
  287. 87: 'W',
  288. 88: 'X',
  289. 89: 'Y',
  290. 90: 'Z',
  291. 96: '0',
  292. 97: '1',
  293. 98: '2',
  294. 99: '3',
  295. 100: '4',
  296. 101: '5',
  297. 102: '6',
  298. 103: '7',
  299. 104: '8',
  300. 105: '9'
  301. };
  302. var keyCodes = {
  303. ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key
  304. ENTER: 13, // KeyboardEvent.which value for Enter key
  305. SPACE: 32, // KeyboardEvent.which value for space key
  306. TAB: 9, // KeyboardEvent.which value for tab key
  307. ARROW_UP: 38, // KeyboardEvent.which value for up arrow key
  308. ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key
  309. }
  310. var version = {};
  311. version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.');
  312. version.major = version.full[0];
  313. var classNames = {
  314. DISABLED: 'disabled',
  315. DIVIDER: version.major === '4' ? 'dropdown-divider' : 'divider',
  316. SHOW: version.major === '4' ? 'show' : 'open',
  317. DROPUP: 'dropup',
  318. MENURIGHT: 'dropdown-menu-right',
  319. MENULEFT: 'dropdown-menu-left',
  320. // to-do: replace with more advanced template/customization options
  321. BUTTONCLASS: version.major === '4' ? 'btn-light' : 'btn-default'
  322. }
  323. var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN);
  324. var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE);
  325. var REGEXP_ENTER_OR_SPACE = new RegExp(keyCodes.ENTER + '|' + keyCodes.SPACE);
  326. var Selectpicker = function (element, options) {
  327. var that = this;
  328. // bootstrap-select has been initialized - revert valHooks.select.set back to its original function
  329. if (!valHooks.useDefault) {
  330. $.valHooks.select.set = valHooks._set;
  331. valHooks.useDefault = true;
  332. }
  333. this.$element = $(element);
  334. this.$newElement = null;
  335. this.$button = null;
  336. this.$menu = null;
  337. this.options = options;
  338. this.selectpicker = {
  339. main: {
  340. // store originalIndex (key) and newIndex (value) in this.selectpicker.main.map.newIndex for fast accessibility
  341. // allows us to do this.main.elements[this.selectpicker.main.map.newIndex[index]] to select an element based on the originalIndex
  342. map: {
  343. newIndex: {},
  344. originalIndex: {}
  345. }
  346. },
  347. current: {
  348. map: {}
  349. }, // current changes if a search is in progress
  350. search: {
  351. map: {}
  352. },
  353. view: {},
  354. keydown: {
  355. keyHistory: '',
  356. resetKeyHistory: {
  357. start: function () {
  358. return setTimeout(function () {
  359. that.selectpicker.keydown.keyHistory = '';
  360. }, 800);
  361. }
  362. }
  363. }
  364. };
  365. // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a
  366. // data-attribute)
  367. if (this.options.title === null) {
  368. this.options.title = this.$element.attr('title');
  369. }
  370. // Format window padding
  371. var winPad = this.options.windowPadding;
  372. if (typeof winPad === 'number') {
  373. this.options.windowPadding = [winPad, winPad, winPad, winPad];
  374. }
  375. //Expose public methods
  376. this.val = Selectpicker.prototype.val;
  377. this.render = Selectpicker.prototype.render;
  378. this.refresh = Selectpicker.prototype.refresh;
  379. this.setStyle = Selectpicker.prototype.setStyle;
  380. this.selectAll = Selectpicker.prototype.selectAll;
  381. this.deselectAll = Selectpicker.prototype.deselectAll;
  382. this.destroy = Selectpicker.prototype.destroy;
  383. this.remove = Selectpicker.prototype.remove;
  384. this.show = Selectpicker.prototype.show;
  385. this.hide = Selectpicker.prototype.hide;
  386. this.init();
  387. };
  388. Selectpicker.VERSION = '1.13.0-beta';
  389. // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both.
  390. Selectpicker.DEFAULTS = {
  391. noneSelectedText: '',
  392. noneResultsText: 'No results matched {0}',
  393. countSelectedText: function (numSelected, numTotal) {
  394. return (numSelected == 1) ? "{0} item selected" : "{0} items selected";
  395. },
  396. maxOptionsText: function (numAll, numGroup) {
  397. return [
  398. (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)',
  399. (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'
  400. ];
  401. },
  402. selectAllText: 'Select All',
  403. deselectAllText: 'Deselect All',
  404. doneButton: false,
  405. doneButtonText: 'Close',
  406. multipleSeparator: ', ',
  407. styleBase: 'btn',
  408. style: 'btn-default',
  409. size: 'auto',
  410. title: null,
  411. selectedTextFormat: 'values',
  412. width: false,
  413. container: false,
  414. hideDisabled: false,
  415. showSubtext: false,
  416. showIcon: true,
  417. showContent: true,
  418. dropupAuto: true,
  419. header: false,
  420. liveSearch: false,
  421. liveSearchPlaceholder: null,
  422. liveSearchNormalize: false,
  423. liveSearchStyle: 'contains',
  424. actionsBox: false,
  425. iconBase: 'glyphicon',
  426. tickIcon: 'glyphicon-unchecked',
  427. showTick: false,
  428. template: {
  429. caret: '<span class="caret"></span>'
  430. },
  431. maxOptions: false,
  432. mobile: false,
  433. selectOnTab: false,
  434. dropdownAlignRight: false,
  435. windowPadding: 0,
  436. virtualScroll: 600
  437. };
  438. if (version.major === '4') {
  439. Selectpicker.DEFAULTS.style = 'btn-light';
  440. Selectpicker.DEFAULTS.iconBase = '';
  441. Selectpicker.DEFAULTS.tickIcon = 'bs-ok-default';
  442. }
  443. Selectpicker.prototype = {
  444. constructor: Selectpicker,
  445. init: function () {
  446. var that = this,
  447. id = this.$element.attr('id');
  448. this.$element.addClass('bs-select-hidden');
  449. this.multiple = this.$element.prop('multiple');
  450. this.autofocus = this.$element.prop('autofocus');
  451. this.$newElement = this.createDropdown();
  452. this.createLi();
  453. this.$element
  454. .after(this.$newElement)
  455. .prependTo(this.$newElement);
  456. this.$button = this.$newElement.children('button');
  457. this.$menu = this.$newElement.children('.dropdown-menu');
  458. this.$menuInner = this.$menu.children('.inner');
  459. this.$searchbox = this.$menu.find('input');
  460. this.$element.removeClass('bs-select-hidden');
  461. if (this.options.dropdownAlignRight === true) this.$menu.addClass(classNames.MENURIGHT);
  462. if (typeof id !== 'undefined') {
  463. this.$button.attr('data-id', id);
  464. }
  465. this.checkDisabled();
  466. this.clickListener();
  467. if (this.options.liveSearch) this.liveSearchListener();
  468. this.render();
  469. this.setStyle();
  470. this.setWidth();
  471. if (this.options.container) {
  472. this.selectPosition();
  473. } else {
  474. this.$element.on('hide.bs.select', function () {
  475. if (that.isVirtual()) {
  476. // empty menu on close
  477. var menuInner = that.$menuInner[0],
  478. emptyMenu = menuInner.firstChild.cloneNode(false);
  479. // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = ''
  480. menuInner.replaceChild(emptyMenu, menuInner.firstChild);
  481. menuInner.scrollTop = 0;
  482. }
  483. });
  484. }
  485. this.$menu.data('this', this);
  486. this.$newElement.data('this', this);
  487. if (this.options.mobile) this.mobile();
  488. this.$newElement.on({
  489. 'hide.bs.dropdown': function (e) {
  490. that.$menuInner.attr('aria-expanded', false);
  491. that.$element.trigger('hide.bs.select', e);
  492. },
  493. 'hidden.bs.dropdown': function (e) {
  494. that.$element.trigger('hidden.bs.select', e);
  495. },
  496. 'show.bs.dropdown': function (e) {
  497. that.$menuInner.attr('aria-expanded', true);
  498. that.$element.trigger('show.bs.select', e);
  499. },
  500. 'shown.bs.dropdown': function (e) {
  501. that.$element.trigger('shown.bs.select', e);
  502. }
  503. });
  504. if (that.$element[0].hasAttribute('required')) {
  505. this.$element.on('invalid', function () {
  506. that.$button.addClass('bs-invalid');
  507. that.$element.on({
  508. 'shown.bs.select': function () {
  509. that.$element
  510. .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened
  511. .off('shown.bs.select');
  512. },
  513. 'rendered.bs.select': function () {
  514. // if select is no longer invalid, remove the bs-invalid class
  515. if (this.validity.valid) that.$button.removeClass('bs-invalid');
  516. that.$element.off('rendered.bs.select');
  517. }
  518. });
  519. that.$button.on('blur.bs.select', function () {
  520. that.$element.focus().blur();
  521. that.$button.off('blur.bs.select');
  522. });
  523. });
  524. }
  525. setTimeout(function () {
  526. that.$element.trigger('loaded.bs.select');
  527. });
  528. },
  529. createDropdown: function () {
  530. // Options
  531. // If we are multiple or showTick option is set, then add the show-tick class
  532. var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '',
  533. inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '',
  534. autofocus = this.autofocus ? ' autofocus' : '';
  535. // Elements
  536. var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : '';
  537. var searchbox = this.options.liveSearch ?
  538. '<div class="bs-searchbox">' +
  539. '<input type="text" class="form-control" autocomplete="off"' +
  540. (null === this.options.liveSearchPlaceholder ? '' : ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"') + ' role="textbox" aria-label="Search">' +
  541. '</div>'
  542. : '';
  543. var actionsbox = this.multiple && this.options.actionsBox ?
  544. '<div class="bs-actionsbox">' +
  545. '<div class="btn-group btn-group-sm btn-block">' +
  546. '<button type="button" class="actions-btn bs-select-all btn ' + classNames.BUTTONCLASS + '">' +
  547. this.options.selectAllText +
  548. '</button>' +
  549. '<button type="button" class="actions-btn bs-deselect-all btn ' + classNames.BUTTONCLASS + '">' +
  550. this.options.deselectAllText +
  551. '</button>' +
  552. '</div>' +
  553. '</div>'
  554. : '';
  555. var donebutton = this.multiple && this.options.doneButton ?
  556. '<div class="bs-donebutton">' +
  557. '<div class="btn-group btn-block">' +
  558. '<button type="button" class="btn btn-sm ' + classNames.BUTTONCLASS + '">' +
  559. this.options.doneButtonText +
  560. '</button>' +
  561. '</div>' +
  562. '</div>'
  563. : '';
  564. var drop =
  565. '<div class="dropdown bootstrap-select' + showTick + inputGroup + '">' +
  566. '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" data-toggle="dropdown"' + autofocus + ' role="button">' +
  567. '<div class="filter-option">' +
  568. '<div class="filter-option-inner"></div>' +
  569. '</div>&nbsp;' +
  570. '<span class="bs-caret">' +
  571. this.options.template.caret +
  572. '</span>' +
  573. '</button>' +
  574. '<div class="dropdown-menu ' + (version.major === '4' ? '' : classNames.SHOW) + '" role="combobox">' +
  575. header +
  576. searchbox +
  577. actionsbox +
  578. '<div class="inner ' + classNames.SHOW + '" role="listbox" aria-expanded="false" tabindex="-1">' +
  579. '<ul class="dropdown-menu inner ' + (version.major === '4' ? classNames.SHOW : '') + '">' +
  580. '</ul>' +
  581. '</div>' +
  582. donebutton +
  583. '</div>' +
  584. '</div>';
  585. return $(drop);
  586. },
  587. setPositionData: function () {
  588. this.selectpicker.view.canHighlight = [];
  589. for (var i = 0; i < this.selectpicker.current.data.length; i++) {
  590. var li = this.selectpicker.current.data[i],
  591. canHighlight = true;
  592. if (li.type === 'divider') {
  593. canHighlight = false;
  594. li.height = this.sizeInfo.dividerHeight;
  595. } else if (li.type === 'optgroup-label') {
  596. canHighlight = false;
  597. li.height = this.sizeInfo.dropdownHeaderHeight;
  598. } else {
  599. li.height = this.sizeInfo.liHeight;
  600. }
  601. if (li.disabled) canHighlight = false;
  602. this.selectpicker.view.canHighlight.push(canHighlight);
  603. li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height;
  604. }
  605. },
  606. isVirtual: function () {
  607. return (this.options.virtualScroll !== false) && this.selectpicker.main.elements.length >= this.options.virtualScroll || this.options.virtualScroll === true;
  608. },
  609. createView: function (isSearching, scrollTop) {
  610. scrollTop = scrollTop || 0;
  611. var that = this;
  612. this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main;
  613. var $lis;
  614. var active = [];
  615. var selected;
  616. var prevActive;
  617. var activeIndex;
  618. var prevActiveIndex;
  619. this.setPositionData();
  620. scroll(scrollTop, true);
  621. this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) {
  622. if (!that.noScroll) scroll(this.scrollTop, updateValue);
  623. that.noScroll = false;
  624. });
  625. function scroll(scrollTop, init) {
  626. var size = that.selectpicker.current.elements.length,
  627. chunks = [],
  628. chunkSize,
  629. chunkCount,
  630. firstChunk,
  631. lastChunk,
  632. currentChunk = undefined,
  633. prevPositions,
  634. positionIsDifferent,
  635. previousElements,
  636. menuIsDifferent = true,
  637. isVirtual = that.isVirtual();
  638. that.selectpicker.view.scrollTop = scrollTop;
  639. if (isVirtual === true) {
  640. // if an option that is encountered that is wider than the current menu width, update the menu width accordingly
  641. if (that.sizeInfo.hasScrollBar && that.$menu[0].offsetWidth > that.sizeInfo.totalMenuWidth) {
  642. that.sizeInfo.menuWidth = that.$menu[0].offsetWidth;
  643. that.sizeInfo.totalMenuWidth = that.sizeInfo.menuWidth + that.sizeInfo.scrollBarWidth;
  644. that.$menu.css('min-width', that.sizeInfo.menuWidth);
  645. }
  646. }
  647. chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk
  648. chunkCount = Math.round(size / chunkSize) || 1; // number of chunks
  649. for (var i = 0; i < chunkCount; i++) {
  650. var end_of_chunk = (i + 1) * chunkSize;
  651. if (i === chunkCount - 1) {
  652. end_of_chunk = size;
  653. }
  654. chunks[i] = [
  655. (i) * chunkSize + (!i ? 0 : 1),
  656. end_of_chunk
  657. ];
  658. if (!size) break;
  659. if (currentChunk === undefined && scrollTop <= that.selectpicker.current.data[end_of_chunk - 1].position - that.sizeInfo.menuInnerHeight) {
  660. currentChunk = i;
  661. }
  662. }
  663. if (currentChunk === undefined) currentChunk = 0;
  664. prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1];
  665. // always display previous, current, and next chunks
  666. firstChunk = Math.max(0, currentChunk - 1);
  667. lastChunk = Math.min(chunkCount - 1, currentChunk + 1);
  668. that.selectpicker.view.position0 = Math.max(0, chunks[firstChunk][0]) || 0;
  669. that.selectpicker.view.position1 = Math.min(size, chunks[lastChunk][1]) || 0;
  670. positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1;
  671. if (that.activeIndex !== undefined) {
  672. prevActive = that.selectpicker.current.elements[that.selectpicker.current.map.newIndex[that.prevActiveIndex]];
  673. active = that.selectpicker.current.elements[that.selectpicker.current.map.newIndex[that.activeIndex]];
  674. selected = that.selectpicker.current.elements[that.selectpicker.current.map.newIndex[that.selectedIndex]];
  675. if (init) {
  676. if (that.activeIndex !== that.selectedIndex) {
  677. active.classList.remove('active');
  678. if (active.firstChild) active.firstChild.classList.remove('active');
  679. }
  680. that.activeIndex = undefined;
  681. }
  682. if (that.activeIndex && that.activeIndex !== that.selectedIndex && selected && selected.length) {
  683. selected.classList.remove('active');
  684. if (selected.firstChild) selected.firstChild.classList.remove('active');
  685. }
  686. }
  687. if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex && prevActive && prevActive.length) {
  688. prevActive.classList.remove('active');
  689. if (prevActive.firstChild) prevActive.firstChild.classList.remove('active');
  690. }
  691. if (init || positionIsDifferent) {
  692. previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : [];
  693. that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1);
  694. that.setOptionStatus();
  695. // if searching, check to make sure the list has actually been updated before updating DOM
  696. // this prevents unnecessary repaints
  697. if ( isSearching || (isVirtual === false && init) ) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements);
  698. // if virtual scroll is disabled and not searching,
  699. // menu should never need to be updated more than once
  700. if ( (init || isVirtual === true) && menuIsDifferent ) {
  701. var menuInner = that.$menuInner[0],
  702. menuFragment = document.createDocumentFragment(),
  703. emptyMenu = menuInner.firstChild.cloneNode(false),
  704. marginTop,
  705. marginBottom,
  706. elements = isVirtual === true ? that.selectpicker.view.visibleElements : that.selectpicker.current.elements;
  707. // replace the existing UL with an empty one - this is faster than $.empty()
  708. menuInner.replaceChild(emptyMenu, menuInner.firstChild);
  709. for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) {
  710. menuFragment.appendChild(elements[i]);
  711. }
  712. if (isVirtual === true) {
  713. marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position),
  714. marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position);
  715. menuInner.firstChild.style.marginTop = marginTop + 'px';
  716. menuInner.firstChild.style.marginBottom = marginBottom + 'px';
  717. }
  718. menuInner.firstChild.appendChild(menuFragment);
  719. }
  720. }
  721. that.prevActiveIndex = that.activeIndex;
  722. if (!that.options.liveSearch) {
  723. that.$menuInner.focus();
  724. } else if (isSearching && init) {
  725. var index = 0,
  726. newActive;
  727. if (!that.selectpicker.view.canHighlight[index]) {
  728. index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true);
  729. }
  730. newActive = that.selectpicker.view.visibleElements[index];
  731. if (that.selectpicker.view.currentActive) {
  732. that.selectpicker.view.currentActive.classList.remove('active');
  733. if (that.selectpicker.view.currentActive.firstChild) that.selectpicker.view.currentActive.firstChild.classList.remove('active');
  734. }
  735. if (newActive) {
  736. newActive.classList.add('active');
  737. if (newActive.firstChild) newActive.firstChild.classList.add('active');
  738. }
  739. that.activeIndex = that.selectpicker.current.map.originalIndex[index];
  740. }
  741. }
  742. $(window).off('resize.createView').on('resize.createView', function () {
  743. scroll(that.$menuInner[0].scrollTop);
  744. });
  745. },
  746. createLi: function () {
  747. var that = this,
  748. mainElements = [],
  749. widestOption,
  750. availableOptionsCount = 0,
  751. widestOptionLength = 0,
  752. mainData = [],
  753. optID = 0,
  754. headerIndex = 0,
  755. liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure newIndex is correct
  756. if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option');
  757. var elementTemplates = {
  758. span: document.createElement('span'),
  759. subtext: document.createElement('small'),
  760. a: document.createElement('a'),
  761. li: document.createElement('li'),
  762. whitespace: document.createTextNode("\u00A0")
  763. },
  764. checkMark = elementTemplates.span.cloneNode(false),
  765. fragment = document.createDocumentFragment();
  766. checkMark.className = that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark';
  767. elementTemplates.a.appendChild(checkMark);
  768. elementTemplates.a.setAttribute('role', 'option');
  769. elementTemplates.subtext.className = 'text-muted';
  770. elementTemplates.text = elementTemplates.span.cloneNode(false);
  771. elementTemplates.text.className = 'text';
  772. // Helper functions
  773. /**
  774. * @param content
  775. * @param [index]
  776. * @param [classes]
  777. * @param [optgroup]
  778. * @returns {HTMLElement}
  779. */
  780. var generateLI = function (content, index, classes, optgroup) {
  781. var li = elementTemplates.li.cloneNode(false);
  782. if (content) {
  783. if (content.nodeType === 1 || content.nodeType === 11) {
  784. li.appendChild(content);
  785. } else {
  786. li.innerHTML = content;
  787. }
  788. }
  789. if (typeof classes !== 'undefined' && '' !== classes) li.className = classes;
  790. if (typeof optgroup !== 'undefined' && null !== optgroup) li.classList.add('optgroup-' + optgroup);
  791. return li;
  792. };
  793. /**
  794. * @param text
  795. * @param [classes]
  796. * @param [inline]
  797. * @returns {string}
  798. */
  799. var generateA = function (text, classes, inline) {
  800. var a = elementTemplates.a.cloneNode(true);
  801. if (text) {
  802. if (text.nodeType === 11) {
  803. a.appendChild(text);
  804. } else {
  805. a.insertAdjacentHTML('beforeend', text);
  806. }
  807. }
  808. if (typeof classes !== 'undefined' & '' !== classes) a.className = classes;
  809. if (version.major === '4') a.classList.add('dropdown-item');
  810. if (inline) a.setAttribute('style', inline);
  811. return a;
  812. };
  813. var generateText = function (options) {
  814. var textElement = elementTemplates.text.cloneNode(false),
  815. optionSubtextElement,
  816. optionIconElement;
  817. if (options.optionContent) {
  818. textElement.innerHTML = options.optionContent;
  819. } else {
  820. textElement.textContent = options.text;
  821. if (options.optionIcon) {
  822. var whitespace = elementTemplates.whitespace.cloneNode(false);
  823. optionIconElement = elementTemplates.span.cloneNode(false);
  824. optionIconElement.className = that.options.iconBase + ' ' + options.optionIcon;
  825. fragment.appendChild(optionIconElement);
  826. fragment.appendChild(whitespace);
  827. }
  828. if (options.optionSubtext) {
  829. optionSubtextElement = elementTemplates.subtext.cloneNode(false);
  830. optionSubtextElement.textContent = options.optionSubtext;
  831. textElement.appendChild(optionSubtextElement);
  832. }
  833. }
  834. fragment.appendChild(textElement);
  835. return fragment;
  836. };
  837. var generateLabel = function (options) {
  838. var labelTextElement = elementTemplates.text.cloneNode(false),
  839. labelSubtextElement,
  840. labelIconElement;
  841. labelTextElement.textContent = options.labelEscaped;
  842. if (options.labelIcon) {
  843. var whitespace = elementTemplates.whitespace.cloneNode(false);
  844. labelIconElement = elementTemplates.span.cloneNode(false);
  845. labelIconElement.className = that.options.iconBase + ' ' + options.labelIcon;
  846. fragment.appendChild(labelIconElement);
  847. fragment.appendChild(whitespace);
  848. }
  849. if (options.labelSubtext) {
  850. labelSubtextElement = elementTemplates.subtext.cloneNode(false);
  851. labelSubtextElement.textContent = options.labelSubtext;
  852. labelTextElement.appendChild(labelSubtextElement);
  853. }
  854. fragment.appendChild(labelTextElement);
  855. return fragment;
  856. }
  857. if (this.options.title && !this.multiple) {
  858. // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased
  859. // since newIndex is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended
  860. liIndex--;
  861. var element = this.$element[0],
  862. isSelected = false;
  863. if (!this.selectpicker.view.titleOption.parentNode) {
  864. // Use native JS to prepend option (faster)
  865. this.selectpicker.view.titleOption.className = 'bs-title-option';
  866. this.selectpicker.view.titleOption.innerHTML = this.options.title;
  867. this.selectpicker.view.titleOption.value = '';
  868. // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option.
  869. // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs,
  870. // if so, the select will have the data-selected attribute
  871. var $opt = $(element.options[element.selectedIndex]);
  872. isSelected = $opt.attr('selected') === undefined && this.$element.data('selected') === undefined;
  873. }
  874. element.insertBefore(this.selectpicker.view.titleOption, element.firstChild);
  875. // Set selected *after* appending to select,
  876. // otherwise the option doesn't get selected in IE
  877. // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11
  878. if (isSelected) element.selectedIndex = 0;
  879. }
  880. var $selectOptions = this.$element.find('option');
  881. $selectOptions.each(function (index) {
  882. var $this = $(this);
  883. liIndex++;
  884. if ($this.hasClass('bs-title-option')) return;
  885. var thisData = $this.data();
  886. // Get the class and text for the option
  887. var optionClass = this.className || '',
  888. inline = htmlEscape(this.style.cssText),
  889. optionContent = thisData.content,
  890. text = this.textContent,
  891. tokens = thisData.tokens,
  892. subtext = thisData.subtext,
  893. icon = thisData.icon,
  894. $parent = $this.parent(),
  895. parent = $parent[0],
  896. isOptgroup = parent.tagName === 'OPTGROUP',
  897. isOptgroupDisabled = isOptgroup && parent.disabled,
  898. isDisabled = this.disabled || isOptgroupDisabled,
  899. prevHiddenIndex,
  900. showDivider = this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP',
  901. textElement;
  902. var parentData = $parent.data();
  903. if (thisData.hidden === true || that.options.hideDisabled && (isDisabled && !isOptgroup || isOptgroupDisabled)) {
  904. // set prevHiddenIndex - the index of the first hidden option in a group of hidden options
  905. // used to determine whether or not a divider should be placed after an optgroup if there are
  906. // hidden options between the optgroup and the first visible option
  907. prevHiddenIndex = thisData.prevHiddenIndex;
  908. $this.next().data('prevHiddenIndex', (prevHiddenIndex !== undefined ? prevHiddenIndex : index));
  909. liIndex--;
  910. // if previous element is not an optgroup
  911. if (!showDivider) {
  912. if (prevHiddenIndex !== undefined) {
  913. // select the element **before** the first hidden element in the group
  914. var prevHidden = $selectOptions[prevHiddenIndex].previousElementSibling;
  915. if (prevHidden && prevHidden.tagName === 'OPTGROUP' && !prevHidden.disabled) {
  916. showDivider = true;
  917. }
  918. }
  919. }
  920. if (showDivider && mainData[mainData.length - 1].type !== 'divider') {
  921. liIndex++;
  922. mainElements.push(
  923. generateLI(
  924. false,
  925. null,
  926. classNames.DIVIDER,
  927. optID + 'div'
  928. )
  929. );
  930. mainData.push({
  931. type: 'divider',
  932. optID: optID,
  933. originalIndex: index
  934. });
  935. }
  936. return;
  937. }
  938. if (isOptgroup && thisData.divider !== true) {
  939. if (that.options.hideDisabled && isDisabled) {
  940. if (parentData.allOptionsDisabled === undefined) {
  941. var $options = $parent.children();
  942. $parent.data('allOptionsDisabled', $options.filter(':disabled').length === $options.length);
  943. }
  944. if ($parent.data('allOptionsDisabled')) {
  945. liIndex--;
  946. return;
  947. }
  948. }
  949. var optGroupClass = ' ' + parent.className || '';
  950. if (!this.previousElementSibling) { // Is it the first option of the optgroup?
  951. optID += 1;
  952. // Get the opt group label
  953. var label = parent.label,
  954. labelEscaped = htmlEscape(label),
  955. labelSubtext = parentData.subtext,
  956. labelIcon = parentData.icon;
  957. if (index !== 0 && mainElements.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown?
  958. liIndex++;
  959. mainElements.push(
  960. generateLI(
  961. false,
  962. null,
  963. classNames.DIVIDER,
  964. optID + 'div'
  965. )
  966. );
  967. mainData.push({
  968. type: 'divider',
  969. optID: optID,
  970. originalIndex: index
  971. });
  972. }
  973. liIndex++;
  974. var labelElement = generateLabel({
  975. labelEscaped: labelEscaped,
  976. labelSubtext: labelSubtext,
  977. labelIcon: labelIcon
  978. });
  979. mainElements.push(generateLI(labelElement, null, 'dropdown-header' + optGroupClass, optID));
  980. mainData.push({
  981. content: labelEscaped,
  982. subtext: labelSubtext,
  983. type: 'optgroup-label',
  984. optID: optID,
  985. originalIndex: index
  986. });
  987. headerIndex = liIndex - 1;
  988. }
  989. if (that.options.hideDisabled && isDisabled || thisData.hidden === true) {
  990. liIndex--;
  991. return;
  992. }
  993. textElement = generateText({
  994. text: text,
  995. optionContent: optionContent,
  996. optionSubtext: subtext,
  997. optionIcon: icon
  998. });
  999. mainElements.push(generateLI(generateA(textElement, 'opt ' + optionClass + optGroupClass, inline), index, '', optID));
  1000. mainData.push({
  1001. content: text,
  1002. subtext: subtext,
  1003. tokens: tokens,
  1004. type: 'option',
  1005. optID: optID,
  1006. headerIndex: headerIndex,
  1007. lastIndex: headerIndex + parent.childElementCount,
  1008. originalIndex: index
  1009. });
  1010. availableOptionsCount++;
  1011. } else if (thisData.divider === true) {
  1012. mainElements.push(generateLI(false, index, 'divider'));
  1013. mainData.push({
  1014. type: 'divider',
  1015. originalIndex: index
  1016. });
  1017. } else {
  1018. // if previous element is not an optgroup and hideDisabled is true
  1019. if (!showDivider && that.options.hideDisabled) {
  1020. prevHiddenIndex = thisData.prevHiddenIndex;
  1021. if (prevHiddenIndex !== undefined) {
  1022. // select the element **before** the first hidden element in the group
  1023. var prevHidden = $selectOptions[prevHiddenIndex].previousElementSibling;
  1024. if (prevHidden && prevHidden.tagName === 'OPTGROUP' && !prevHidden.disabled) {
  1025. showDivider = true;
  1026. }
  1027. }
  1028. }
  1029. if (showDivider && mainData[mainData.length - 1].type !== 'divider') {
  1030. liIndex++;
  1031. mainElements.push(
  1032. generateLI(
  1033. false,
  1034. null,
  1035. classNames.DIVIDER,
  1036. optID + 'div'
  1037. )
  1038. );
  1039. mainData.push({
  1040. type: 'divider',
  1041. optID: optID,
  1042. originalIndex: index
  1043. });
  1044. }
  1045. textElement = generateText({
  1046. text: text,
  1047. optionContent: optionContent,
  1048. optionSubtext: subtext,
  1049. optionIcon: icon
  1050. });
  1051. mainElements.push(generateLI(generateA(textElement, optionClass, inline), index));
  1052. mainData.push({
  1053. content: text,
  1054. subtext: subtext,
  1055. tokens: tokens,
  1056. type: 'option',
  1057. originalIndex: index
  1058. });
  1059. availableOptionsCount++;
  1060. }
  1061. that.selectpicker.main.map.newIndex[index] = liIndex;
  1062. that.selectpicker.main.map.originalIndex[liIndex] = index;
  1063. // get the most recent option info added to mainData
  1064. var _mainDataLast = mainData[mainData.length - 1];
  1065. _mainDataLast.disabled = isDisabled;
  1066. var combinedLength = 0;
  1067. // count the number of characters in the option - not perfect, but should work in most cases
  1068. if (_mainDataLast.content) combinedLength += _mainDataLast.content.length;
  1069. if (_mainDataLast.subtext) combinedLength += _mainDataLast.subtext.length;
  1070. // if there is an icon, ensure this option's width is checked
  1071. if (icon) combinedLength += 1;
  1072. if (combinedLength > widestOptionLength) {
  1073. widestOptionLength = combinedLength;
  1074. // guess which option is the widest
  1075. // use this when calculating menu width
  1076. // not perfect, but it's fast, and the width will be updating accordingly when scrolling
  1077. widestOption = mainElements[mainElements.length - 1];
  1078. }
  1079. });
  1080. this.selectpicker.main.elements = mainElements;
  1081. this.selectpicker.main.data = mainData;
  1082. this.selectpicker.current = this.selectpicker.main;
  1083. this.selectpicker.view.widestOption = widestOption;
  1084. this.selectpicker.view.availableOptionsCount = availableOptionsCount; // faster way to get # of available options without filter
  1085. },
  1086. findLis: function () {
  1087. return this.$menuInner.find('.inner > li');
  1088. },
  1089. render: function () {
  1090. var that = this,
  1091. $selectOptions = this.$element.find('option'),
  1092. selectedItems = [],
  1093. selectedItemsInTitle = [];
  1094. this.togglePlaceholder();
  1095. this.tabIndex();
  1096. $selectOptions.each(function (index) {
  1097. if (this.selected) {
  1098. selectedItems.push(this);
  1099. if (selectedItemsInTitle.length < 100 && that.options.selectedTextFormat !== 'count') {
  1100. if (that.options.hideDisabled && (this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled)) return;
  1101. var $this = $(this),
  1102. thisData = $this.data(),
  1103. icon = thisData.icon && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + thisData.icon + '"></i> ' : '',
  1104. subtext,
  1105. titleItem;
  1106. if (that.options.showSubtext && thisData.subtext && !that.multiple) {
  1107. subtext = ' <small class="text-muted">' + thisData.subtext + '</small>';
  1108. } else {
  1109. subtext = '';
  1110. }
  1111. if (typeof $this.attr('title') !== 'undefined') {
  1112. titleItem = $this.attr('title');
  1113. } else if (thisData.content && that.options.showContent) {
  1114. titleItem = thisData.content.toString();
  1115. } else {
  1116. titleItem = icon + $this.html() + subtext;
  1117. }
  1118. selectedItemsInTitle.push(titleItem);
  1119. }
  1120. }
  1121. });
  1122. //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled
  1123. //Convert all the values into a comma delimited string
  1124. var title = !this.multiple ? selectedItemsInTitle[0] : selectedItemsInTitle.join(this.options.multipleSeparator);
  1125. // add ellipsis
  1126. if (selectedItems.length > 100) title += '...';
  1127. //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
  1128. if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) {
  1129. var max = this.options.selectedTextFormat.split('>');
  1130. if ((max.length > 1 && selectedItems.length > max[1]) || (max.length === 1 && selectedItems.length >= 2)) {
  1131. var totalCount = this.selectpicker.view.availableOptionsCount,
  1132. tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText;
  1133. title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString());
  1134. }
  1135. }
  1136. if (this.options.title == undefined) {
  1137. this.options.title = this.$element.attr('title');
  1138. }
  1139. if (this.options.selectedTextFormat == 'static') {
  1140. title = this.options.title;
  1141. }
  1142. //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
  1143. if (!title) {
  1144. title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText;
  1145. }
  1146. //strip all HTML tags and trim the result, then unescape any escaped tags
  1147. this.$button.attr('title', htmlUnescape($.trim(title.replace(/<[^>]*>?/g, ''))));
  1148. this.$button.find('.filter-option-inner').html(title);
  1149. this.$element.trigger('rendered.bs.select');
  1150. },
  1151. /**
  1152. * @param [style]
  1153. * @param [status]
  1154. */
  1155. setStyle: function (style, status) {
  1156. if (this.$element.attr('class')) {
  1157. this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, ''));
  1158. }
  1159. var buttonClass = style ? style : this.options.style;
  1160. if (status == 'add') {
  1161. this.$button.addClass(buttonClass);
  1162. } else if (status == 'remove') {
  1163. this.$button.removeClass(buttonClass);
  1164. } else {
  1165. this.$button.removeClass(this.options.style);
  1166. this.$button.addClass(buttonClass);
  1167. }
  1168. },
  1169. liHeight: function (refresh) {
  1170. if (!refresh && (this.options.size === false || this.sizeInfo)) return;
  1171. if (!this.sizeInfo) this.sizeInfo = {};
  1172. var newElement = document.createElement('div'),
  1173. menu = document.createElement('div'),
  1174. menuInner = document.createElement('div'),
  1175. menuInnerInner = document.createElement('ul'),
  1176. divider = document.createElement('li'),
  1177. dropdownHeader = document.createElement('li'),
  1178. li = document.createElement('li'),
  1179. a = document.createElement('a'),
  1180. text = document.createElement('span'),
  1181. header = this.options.header && this.$menu.find('.popover-title').length > 0 ? this.$menu.find('.popover-title')[0].cloneNode(true) : null,
  1182. search = this.options.liveSearch ? document.createElement('div') : null,
  1183. actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null,
  1184. doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null;
  1185. this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth;
  1186. text.className = 'text';
  1187. a.className = 'dropdown-item';
  1188. newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW;
  1189. newElement.style.width = this.sizeInfo.selectWidth + 'px';
  1190. menu.className = 'dropdown-menu ' + classNames.SHOW;
  1191. menuInner.className = 'inner ' + classNames.SHOW;
  1192. menuInnerInner.className = 'dropdown-menu inner ' + (version.major === '4' ? classNames.SHOW : '');
  1193. divider.className = classNames.DIVIDER;
  1194. dropdownHeader.className = 'dropdown-header';
  1195. text.appendChild(document.createTextNode('Inner text'));
  1196. a.appendChild(text);
  1197. li.appendChild(a);
  1198. dropdownHeader.appendChild(text.cloneNode(true));
  1199. if (this.selectpicker.view.widestOption) {
  1200. menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true));
  1201. }
  1202. menuInnerInner.appendChild(li);
  1203. menuInnerInner.appendChild(divider);
  1204. menuInnerInner.appendChild(dropdownHeader);
  1205. if (header) menu.appendChild(header);
  1206. if (search) {
  1207. var input = document.createElement('input');
  1208. search.className = 'bs-searchbox';
  1209. input.className = 'form-control';
  1210. search.appendChild(input);
  1211. menu.appendChild(search);
  1212. }
  1213. if (actions) menu.appendChild(actions);
  1214. menuInner.appendChild(menuInnerInner);
  1215. menu.appendChild(menuInner);
  1216. if (doneButton) menu.appendChild(doneButton);
  1217. newElement.appendChild(menu);
  1218. document.body.appendChild(newElement);
  1219. var liHeight = a.offsetHeight,
  1220. dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0,
  1221. headerHeight = header ? header.offsetHeight : 0,
  1222. searchHeight = search ? search.offsetHeight : 0,
  1223. actionsHeight = actions ? actions.offsetHeight : 0,
  1224. doneButtonHeight = doneButton ? doneButton.offsetHeight : 0,
  1225. dividerHeight = $(divider).outerHeight(true),
  1226. // fall back to jQuery if getComputedStyle is not supported
  1227. menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false,
  1228. menuWidth = menu.offsetWidth,
  1229. $menu = menuStyle ? null : $(menu),
  1230. menuPadding = {
  1231. vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) +
  1232. toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) +
  1233. toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) +
  1234. toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')),
  1235. horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) +
  1236. toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) +
  1237. toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) +
  1238. toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth'))
  1239. },
  1240. menuExtras = {
  1241. vert: menuPadding.vert +
  1242. toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) +
  1243. toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2,
  1244. horiz: menuPadding.horiz +
  1245. toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) +
  1246. toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2
  1247. },
  1248. scrollBarWidth;
  1249. menuInner.style.overflowY = 'scroll';
  1250. scrollBarWidth = menu.offsetWidth - menuWidth;
  1251. document.body.removeChild(newElement);
  1252. this.sizeInfo.liHeight = liHeight;
  1253. this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight;
  1254. this.sizeInfo.headerHeight = headerHeight;
  1255. this.sizeInfo.searchHeight = searchHeight;
  1256. this.sizeInfo.actionsHeight = actionsHeight;
  1257. this.sizeInfo.doneButtonHeight = doneButtonHeight;
  1258. this.sizeInfo.dividerHeight = dividerHeight;
  1259. this.sizeInfo.menuPadding = menuPadding;
  1260. this.sizeInfo.menuExtras = menuExtras;
  1261. this.sizeInfo.menuWidth = menuWidth;
  1262. this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth;
  1263. this.sizeInfo.scrollBarWidth = scrollBarWidth;
  1264. this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight;
  1265. this.setPositionData();
  1266. },
  1267. getSelectPosition: function () {
  1268. var that = this,
  1269. $window = $(window),
  1270. pos = that.$newElement.offset(),
  1271. $container = $(that.options.container),
  1272. containerPos;
  1273. if (that.options.container && !$container.is('body')) {
  1274. containerPos = $container.offset();
  1275. containerPos.top += parseInt($container.css('borderTopWidth'));
  1276. containerPos.left += parseInt($container.css('borderLeftWidth'));
  1277. } else {
  1278. containerPos = { top: 0, left: 0 };
  1279. }
  1280. var winPad = that.options.windowPadding;
  1281. this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop();
  1282. this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo['selectHeight'] - containerPos.top - winPad[2];
  1283. this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft();
  1284. this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo['selectWidth'] - containerPos.left - winPad[1];
  1285. this.sizeInfo.selectOffsetTop -= winPad[0];
  1286. this.sizeInfo.selectOffsetLeft -= winPad[3];
  1287. },
  1288. setMenuSize: function (isAuto) {
  1289. this.getSelectPosition();
  1290. var selectWidth = this.sizeInfo['selectWidth'],
  1291. liHeight = this.sizeInfo['liHeight'],
  1292. headerHeight = this.sizeInfo['headerHeight'],
  1293. searchHeight = this.sizeInfo['searchHeight'],
  1294. actionsHeight = this.sizeInfo['actionsHeight'],
  1295. doneButtonHeight = this.sizeInfo['doneButtonHeight'],
  1296. divHeight = this.sizeInfo['dividerHeight'],
  1297. menuPadding = this.sizeInfo['menuPadding'],
  1298. menuInnerHeight,
  1299. menuHeight,
  1300. divLength = 0,
  1301. minHeight,
  1302. _minHeight,
  1303. maxHeight,
  1304. menuInnerMinHeight,
  1305. estimate;
  1306. if (this.options.dropupAuto) {
  1307. // Get the estimated height of the menu without scrollbars.
  1308. // This is useful for smaller menus, where there might be plenty of room
  1309. // below the button without setting dropup, but we can't know
  1310. // the exact height of the menu until createView is called later
  1311. estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert;
  1312. this.$newElement.toggleClass(classNames.DROPUP, this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot);
  1313. }
  1314. if (this.options.size === 'auto') {
  1315. _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0;
  1316. menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert;
  1317. minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight;
  1318. menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0);
  1319. if (this.$newElement.hasClass(classNames.DROPUP)) {
  1320. menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert;
  1321. }
  1322. maxHeight = menuHeight;
  1323. menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert;
  1324. } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) {
  1325. for (var i = 0; i < this.options.size; i++) {
  1326. if (this.selectpicker.current.data[i].type === 'divider') divLength++;
  1327. }
  1328. menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert;
  1329. menuInnerHeight = menuHeight - menuPadding.vert;
  1330. maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight;
  1331. minHeight = menuInnerMinHeight = '';
  1332. }
  1333. if (this.options.dropdownAlignRight === 'auto') {
  1334. this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.$menu[0].offsetWidth - selectWidth));
  1335. }
  1336. this.$menu.css({
  1337. 'max-height': maxHeight + 'px',
  1338. 'overflow': 'hidden',
  1339. 'min-height': minHeight + 'px'
  1340. });
  1341. this.$menuInner.css({
  1342. 'max-height': menuInnerHeight + 'px',
  1343. 'overflow-y': 'auto',
  1344. 'min-height': menuInnerMinHeight + 'px'
  1345. });
  1346. this.sizeInfo['menuInnerHeight'] = menuInnerHeight;
  1347. if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) {
  1348. this.sizeInfo.hasScrollBar = true;
  1349. this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth;
  1350. this.$menu.css('min-width', this.sizeInfo.totalMenuWidth);
  1351. }
  1352. if (this.dropdown) this.dropdown._popper.update();
  1353. },
  1354. setSize: function (refresh) {
  1355. this.liHeight(refresh);
  1356. if (this.options.header) this.$menu.css('padding-top', 0);
  1357. if (this.options.size === false) return;
  1358. var that = this,
  1359. $window = $(window),
  1360. selectedIndex,
  1361. offset = 0;
  1362. this.setMenuSize();
  1363. if (this.options.size === 'auto') {
  1364. this.$searchbox.off('input.setMenuSize propertychange.setMenuSize').on('input.setMenuSize propertychange.setMenuSize', function() {
  1365. return that.setMenuSize();
  1366. });
  1367. $window.off('resize.setMenuSize scroll.setMenuSize').on('resize.setMenuSize scroll.setMenuSize', function() {
  1368. return that.setMenuSize();
  1369. });
  1370. } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) {
  1371. this.$searchbox.off('input.setMenuSize propertychange.setMenuSize');
  1372. $window.off('resize.setMenuSize scroll.setMenuSize');
  1373. }
  1374. if (refresh) {
  1375. offset = this.$menuInner[0].scrollTop;
  1376. } else if (!that.multiple) {
  1377. selectedIndex = that.selectpicker.main.map.newIndex[that.$element[0].selectedIndex];
  1378. if (typeof selectedIndex === 'number' && that.options.size !== false) {
  1379. offset = that.sizeInfo.liHeight * selectedIndex;
  1380. offset = offset - (that.sizeInfo.menuInnerHeight / 2) + (that.sizeInfo.liHeight / 2);
  1381. }
  1382. }
  1383. that.createView(false, offset);
  1384. },
  1385. setWidth: function () {
  1386. var that = this;
  1387. if (this.options.width === 'auto') {
  1388. requestAnimationFrame(function() {
  1389. that.$menu.css('min-width', '0');
  1390. that.liHeight();
  1391. that.setMenuSize();
  1392. // Get correct width if element is hidden
  1393. var $selectClone = that.$newElement.clone().appendTo('body'),
  1394. btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth();
  1395. $selectClone.remove();
  1396. // Set width to whatever's larger, button title or longest option
  1397. that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth);
  1398. that.$newElement.css('width', that.sizeInfo.selectWidth + 'px');
  1399. });
  1400. } else if (this.options.width === 'fit') {
  1401. // Remove inline min-width so width can be changed from 'auto'
  1402. this.$menu.css('min-width', '');
  1403. this.$newElement.css('width', '').addClass('fit-width');
  1404. } else if (this.options.width) {
  1405. // Remove inline min-width so width can be changed from 'auto'
  1406. this.$menu.css('min-width', '');
  1407. this.$newElement.css('width', this.options.width);
  1408. } else {
  1409. // Remove inline min-width/width so width can be changed
  1410. this.$menu.css('min-width', '');
  1411. this.$newElement.css('width', '');
  1412. }
  1413. // Remove fit-width class if width is changed programmatically
  1414. if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {
  1415. this.$newElement.removeClass('fit-width');
  1416. }
  1417. },
  1418. selectPosition: function () {
  1419. this.$bsContainer = $('<div class="bs-container" />');
  1420. var that = this,
  1421. $container = $(this.options.container),
  1422. pos,
  1423. containerPos,
  1424. actualHeight,
  1425. getPlacement = function ($element) {
  1426. var containerPosition = {};
  1427. that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP));
  1428. pos = $element.offset();
  1429. if (!$container.is('body')) {
  1430. containerPos = $container.offset();
  1431. containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop();
  1432. containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft();
  1433. } else {
  1434. containerPos = { top: 0, left: 0 };
  1435. }
  1436. actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight;
  1437. // Bootstrap 4+ uses Popper for menu positioning
  1438. if (version.major < 4) {
  1439. containerPosition['top'] = pos.top - containerPos.top + actualHeight;
  1440. containerPosition['left'] = pos.left - containerPos.left;
  1441. }
  1442. containerPosition['width'] = $element[0].offsetWidth;
  1443. that.$bsContainer.css(containerPosition);
  1444. };
  1445. this.$button.on('click.bs.dropdown.data-api', function () {
  1446. if (that.isDisabled()) {
  1447. return;
  1448. }
  1449. getPlacement(that.$newElement);
  1450. that.$bsContainer
  1451. .appendTo(that.options.container)
  1452. .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW))
  1453. .append(that.$menu);
  1454. });
  1455. $(window).on('resize scroll', function () {
  1456. getPlacement(that.$newElement);
  1457. });
  1458. this.$element.on('hide.bs.select', function () {
  1459. that.$menu.data('height', that.$menu.height());
  1460. that.$bsContainer.detach();
  1461. });
  1462. },
  1463. setOptionStatus: function () {
  1464. var that = this,
  1465. $selectOptions = this.$element.find('option');
  1466. that.noScroll = false;
  1467. if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) {
  1468. for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) {
  1469. var index = that.selectpicker.current.map.originalIndex[i + that.selectpicker.view.position0], // faster than $(li).data('originalIndex')
  1470. option = $selectOptions[index];
  1471. if (option) {
  1472. var liIndex = this.selectpicker.main.map.newIndex[index],
  1473. li = this.selectpicker.main.elements[liIndex];
  1474. that.setDisabled(
  1475. index,
  1476. option.disabled || option.parentNode.tagName === 'OPTGROUP' && option.parentNode.disabled,
  1477. liIndex,
  1478. li
  1479. );
  1480. that.setSelected(
  1481. index,
  1482. option.selected,
  1483. liIndex,
  1484. li
  1485. );
  1486. }
  1487. }
  1488. }
  1489. },
  1490. /**
  1491. * @param {number} index - the index of the option that is being changed
  1492. * @param {boolean} selected - true if the option is being selected, false if being deselected
  1493. */
  1494. setSelected: function (index, selected, liIndex, li) {
  1495. var activeIndexIsSet = this.activeIndex !== undefined,
  1496. thisIsActive = this.activeIndex === index,
  1497. prevActiveIndex,
  1498. prevActive,
  1499. a,
  1500. keepActive = thisIsActive || selected && !this.multiple && !activeIndexIsSet;
  1501. if (!liIndex) liIndex = this.selectpicker.main.map.newIndex[index];
  1502. if (!li) li = this.selectpicker.main.elements[liIndex];
  1503. a = li.firstChild;
  1504. if (selected) {
  1505. this.selectedIndex = index;
  1506. }
  1507. li.classList.toggle('selected', selected);
  1508. li.classList.toggle('active', keepActive);
  1509. if (keepActive) {
  1510. this.selectpicker.view.currentActive = li;
  1511. this.activeIndex = index
  1512. }
  1513. if (a) {
  1514. a.classList.toggle('selected', selected);
  1515. a.classList.toggle('active', keepActive);
  1516. a.setAttribute('aria-selected', selected);
  1517. }
  1518. if (!keepActive) {
  1519. if (!activeIndexIsSet && selected && this.prevActiveIndex) {
  1520. prevActiveIndex = this.selectpicker.main.map.newIndex[this.prevActiveIndex];
  1521. prevActive = this.selectpicker.main.elements[prevActiveIndex];
  1522. prevActive.classList.remove('active');
  1523. if (prevActive.firstChild) prevActive.firstChild.classList.remove('active');
  1524. }
  1525. }
  1526. },
  1527. /**
  1528. * @param {number} index - the index of the option that is being disabled
  1529. * @param {boolean} disabled - true if the option is being disabled, false if being enabled
  1530. */
  1531. setDisabled: function (index, disabled, liIndex, li) {
  1532. var a;
  1533. if (!liIndex) liIndex = this.selectpicker.main.map.newIndex[index];
  1534. if (!li) li = this.selectpicker.main.elements[liIndex];
  1535. a = li.firstChild;
  1536. li.classList.toggle(classNames.DISABLED, disabled);
  1537. if (a) {
  1538. if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled);
  1539. a.setAttribute('aria-disabled', disabled);
  1540. if (disabled) {
  1541. a.setAttribute('tabindex', -1);
  1542. } else {
  1543. a.setAttribute('tabindex', 0);
  1544. }
  1545. }
  1546. },
  1547. isDisabled: function () {
  1548. return this.$element[0].disabled;
  1549. },
  1550. checkDisabled: function () {
  1551. var that = this;
  1552. if (this.isDisabled()) {
  1553. this.$newElement.addClass(classNames.DISABLED);
  1554. this.$button.addClass(classNames.DISABLED).attr('tabindex', -1).attr('aria-disabled', true);
  1555. } else {
  1556. if (this.$button.hasClass(classNames.DISABLED)) {
  1557. this.$newElement.removeClass(classNames.DISABLED);
  1558. this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false);
  1559. }
  1560. if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) {
  1561. this.$button.removeAttr('tabindex');
  1562. }
  1563. }
  1564. this.$button.click(function () {
  1565. return !that.isDisabled();
  1566. });
  1567. },
  1568. togglePlaceholder: function () {
  1569. // much faster than calling $.val()
  1570. var element = this.$element[0],
  1571. selectedIndex = element.selectedIndex,
  1572. nothingSelected = selectedIndex === -1;
  1573. if (!nothingSelected && !element.options[selectedIndex].value) nothingSelected = true;
  1574. this.$button.toggleClass('bs-placeholder', nothingSelected);
  1575. },
  1576. tabIndex: function () {
  1577. if (this.$element.data('tabindex') !== this.$element.attr('tabindex') &&
  1578. (this.$element.attr('tabindex') !== -98 && this.$element.attr('tabindex') !== '-98')) {
  1579. this.$element.data('tabindex', this.$element.attr('tabindex'));
  1580. this.$button.attr('tabindex', this.$element.data('tabindex'));
  1581. }
  1582. this.$element.attr('tabindex', -98);
  1583. },
  1584. clickListener: function () {
  1585. var that = this,
  1586. $document = $(document);
  1587. $document.data('spaceSelect', false);
  1588. this.$button.on('keyup', function (e) {
  1589. if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) {
  1590. e.preventDefault();
  1591. $document.data('spaceSelect', false);
  1592. }
  1593. });
  1594. this.$newElement.on('show.bs.dropdown', function() {
  1595. if (version.major > 3 && !that.dropdown) {
  1596. that.dropdown = that.$button.data('bs.dropdown');
  1597. that.dropdown._menu = that.$menu[0];
  1598. }
  1599. });
  1600. this.$button.on('click.bs.dropdown.data-api', function () {
  1601. if (!that.$newElement.hasClass(classNames.SHOW)) {
  1602. that.setSize();
  1603. }
  1604. });
  1605. this.$element.on('shown.bs.select', function () {
  1606. if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) {
  1607. that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop;
  1608. }
  1609. if (that.options.liveSearch) {
  1610. that.$searchbox.focus();
  1611. } else {
  1612. that.$menuInner.focus();
  1613. }
  1614. });
  1615. this.$menuInner.on('click', 'li a', function (e, retainActive) {
  1616. var $this = $(this),
  1617. position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0,
  1618. clickedIndex = that.selectpicker.current.map.originalIndex[$this.parent().index() + position0],
  1619. prevValue = that.$element.val(),
  1620. prevIndex = that.$element.prop('selectedIndex'),
  1621. triggerChange = true;
  1622. // Don't close on multi choice menu
  1623. if (that.multiple && that.options.maxOptions !== 1) {
  1624. e.stopPropagation();
  1625. }
  1626. e.preventDefault();
  1627. //Don't run if we have been disabled
  1628. if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) {
  1629. var $options = that.$element.find('option'),
  1630. $option = $options.eq(clickedIndex),
  1631. state = $option.prop('selected'),
  1632. $optgroup = $option.parent('optgroup'),
  1633. maxOptions = that.options.maxOptions,
  1634. maxOptionsGrp = $optgroup.data('maxOptions') || false;
  1635. if (!that.multiple) { // Deselect all others if not multi select box
  1636. $options.prop('selected', false);
  1637. $option.prop('selected', true);
  1638. that.setSelected(clickedIndex, true);
  1639. } else { // Toggle the one we have chosen if we are multi select.
  1640. $option.prop('selected', !state);
  1641. if (clickedIndex === that.activeIndex) retainActive = true;
  1642. if (!retainActive) {
  1643. that.prevActiveIndex = that.activeIndex;
  1644. that.activeIndex = undefined;
  1645. }
  1646. that.setSelected(clickedIndex, !state);
  1647. $this.blur();
  1648. if (maxOptions !== false || maxOptionsGrp !== false) {
  1649. var maxReached = maxOptions < $options.filter(':selected').length,
  1650. maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length;
  1651. if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) {
  1652. if (maxOptions && maxOptions == 1) {
  1653. $options.prop('selected', false);
  1654. $option.prop('selected', true);
  1655. that.$menuInner.find('.selected').removeClass('selected');
  1656. that.setSelected(clickedIndex, true);
  1657. } else if (maxOptionsGrp && maxOptionsGrp == 1) {
  1658. $optgroup.find('option:selected').prop('selected', false);
  1659. $option.prop('selected', true);
  1660. var optgroupID = that.selectpicker.current.data[$this.parent().index() + that.selectpicker.view.position0].optID;
  1661. that.$menuInner.find('.optgroup-' + optgroupID).removeClass('selected');
  1662. that.setSelected(clickedIndex, true);
  1663. } else {
  1664. var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText,
  1665. maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText,
  1666. maxTxt = maxOptionsArr[0].replace('{n}', maxOptions),
  1667. maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp),
  1668. $notify = $('<div class="notify"></div>');
  1669. // If {var} is set in array, replace it
  1670. /** @deprecated */
  1671. if (maxOptionsArr[2]) {
  1672. maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]);
  1673. maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]);
  1674. }
  1675. $option.prop('selected', false);
  1676. that.$menu.append($notify);
  1677. if (maxOptions && maxReached) {
  1678. $notify.append($('<div>' + maxTxt + '</div>'));
  1679. triggerChange = false;
  1680. that.$element.trigger('maxReached.bs.select');
  1681. }
  1682. if (maxOptionsGrp && maxReachedGrp) {
  1683. $notify.append($('<div>' + maxTxtGrp + '</div>'));
  1684. triggerChange = false;
  1685. that.$element.trigger('maxReachedGrp.bs.select');
  1686. }
  1687. setTimeout(function () {
  1688. that.setSelected(clickedIndex, false);
  1689. }, 10);
  1690. $notify.delay(750).fadeOut(300, function () {
  1691. $(this).remove();
  1692. });
  1693. }
  1694. }
  1695. }
  1696. }
  1697. if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) {
  1698. that.$button.focus();
  1699. } else if (that.options.liveSearch) {
  1700. that.$searchbox.focus();
  1701. }
  1702. // Trigger select 'change'
  1703. if (triggerChange) {
  1704. if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) {
  1705. // $option.prop('selected') is current option state (selected/unselected). state is previous option state.
  1706. changed_arguments = [clickedIndex, $option.prop('selected'), state];
  1707. that.$element
  1708. .triggerNative('change');
  1709. }
  1710. }
  1711. }
  1712. });
  1713. this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .popover-title, .popover-title :not(.close)', function (e) {
  1714. if (e.currentTarget == this) {
  1715. e.preventDefault();
  1716. e.stopPropagation();
  1717. if (that.options.liveSearch && !$(e.target).hasClass('close')) {
  1718. that.$searchbox.focus();
  1719. } else {
  1720. that.$button.focus();
  1721. }
  1722. }
  1723. });
  1724. this.$menuInner.on('click', '.divider, .dropdown-header', function (e) {
  1725. e.preventDefault();
  1726. e.stopPropagation();
  1727. if (that.options.liveSearch) {
  1728. that.$searchbox.focus();
  1729. } else {
  1730. that.$button.focus();
  1731. }
  1732. });
  1733. this.$menu.on('click', '.popover-title .close', function () {
  1734. that.$button.click();
  1735. });
  1736. this.$searchbox.on('click', function (e) {
  1737. e.stopPropagation();
  1738. });
  1739. this.$menu.on('click', '.actions-btn', function (e) {
  1740. if (that.options.liveSearch) {
  1741. that.$searchbox.focus();
  1742. } else {
  1743. that.$button.focus();
  1744. }
  1745. e.preventDefault();
  1746. e.stopPropagation();
  1747. if ($(this).hasClass('bs-select-all')) {
  1748. that.selectAll();
  1749. } else {
  1750. that.deselectAll();
  1751. }
  1752. });
  1753. this.$element.on({
  1754. 'change': function () {
  1755. that.render();
  1756. that.$element.trigger('changed.bs.select', changed_arguments);
  1757. changed_arguments = null;
  1758. },
  1759. 'focus': function () {
  1760. that.$button.focus();
  1761. }
  1762. });
  1763. },
  1764. liveSearchListener: function () {
  1765. var that = this,
  1766. no_results = document.createElement('li');
  1767. this.$button.on('click.bs.dropdown.data-api', function () {
  1768. if (!!that.$searchbox.val()) {
  1769. that.$searchbox.val('');
  1770. }
  1771. });
  1772. this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) {
  1773. e.stopPropagation();
  1774. });
  1775. this.$searchbox.on('input propertychange', function () {
  1776. var searchValue = that.$searchbox.val();
  1777. that.selectpicker.search.map.newIndex = {};
  1778. that.selectpicker.search.map.originalIndex = {};
  1779. that.selectpicker.search.elements = [];
  1780. that.selectpicker.search.data = [];
  1781. if (searchValue) {
  1782. var i,
  1783. searchMatch = [],
  1784. q = searchValue.toUpperCase(),
  1785. cache = {},
  1786. cacheArr = [],
  1787. searchStyle = that._searchStyle(),
  1788. normalizeSearch = that.options.liveSearchNormalize;
  1789. that._$lisSelected = that.$menuInner.find('.selected');
  1790. for (var i = 0; i < that.selectpicker.main.data.length; i++) {
  1791. var li = that.selectpicker.main.data[i];
  1792. if (!cache[i]) {
  1793. cache[i] = stringSearch(li, q, searchStyle, normalizeSearch);
  1794. }
  1795. if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) {
  1796. if (li.headerIndex > 0) {
  1797. cache[li.headerIndex - 1] = true;
  1798. cacheArr.push(li.headerIndex - 1);
  1799. }
  1800. cache[li.headerIndex] = true;
  1801. cacheArr.push(li.headerIndex);
  1802. cache[li.lastIndex + 1] = true;
  1803. }
  1804. if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i);
  1805. }
  1806. for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) {
  1807. var index = cacheArr[i],
  1808. prevIndex = cacheArr[i - 1],
  1809. li = that.selectpicker.main.data[index],
  1810. liPrev = that.selectpicker.main.data[prevIndex];
  1811. if ( li.type !== 'divider' || ( li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i ) ) {
  1812. that.selectpicker.search.data.push(li);
  1813. searchMatch.push(that.selectpicker.main.elements[index]);
  1814. that.selectpicker.search.map.newIndex[li.originalIndex] = searchMatch.length - 1;
  1815. that.selectpicker.search.map.originalIndex[searchMatch.length - 1] = li.originalIndex;
  1816. }
  1817. }
  1818. that.activeIndex = undefined;
  1819. that.noScroll = true;
  1820. that.$menuInner.scrollTop(0);
  1821. that.selectpicker.search.elements = searchMatch;
  1822. that.createView(true);
  1823. if (!searchMatch.length) {
  1824. no_results.className = 'no-results';
  1825. no_results.innerHTML = that.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"');
  1826. that.$menuInner[0].firstChild.appendChild(no_results);
  1827. }
  1828. } else {
  1829. that.$menuInner.scrollTop(0);
  1830. that.createView(false);
  1831. }
  1832. });
  1833. },
  1834. _searchStyle: function () {
  1835. return this.options.liveSearchStyle || 'contains';
  1836. },
  1837. val: function (value) {
  1838. if (typeof value !== 'undefined') {
  1839. this.$element
  1840. .val(value)
  1841. .triggerNative('change');
  1842. return this.$element;
  1843. } else {
  1844. return this.$element.val();
  1845. }
  1846. },
  1847. changeAll: function (status) {
  1848. if (!this.multiple) return;
  1849. if (typeof status === 'undefined') status = true;
  1850. var $selectOptions = this.$element.find('option'),
  1851. previousSelected = 0,
  1852. currentSelected = 0;
  1853. for (var i = 0; i < this.selectpicker.current.elements.length; i++) {
  1854. var index = this.selectpicker.current.map.originalIndex[i], // faster than $(li).data('originalIndex')
  1855. option = $selectOptions[index];
  1856. if (option) {
  1857. if (option.selected) previousSelected++;
  1858. option.selected = status;
  1859. if (option.selected) currentSelected++;
  1860. }
  1861. }
  1862. if (previousSelected === currentSelected) return;
  1863. this.setOptionStatus();
  1864. this.togglePlaceholder();
  1865. this.$element
  1866. .triggerNative('change');
  1867. },
  1868. selectAll: function () {
  1869. return this.changeAll(true);
  1870. },
  1871. deselectAll: function () {
  1872. return this.changeAll(false);
  1873. },
  1874. toggle: function (e) {
  1875. e = e || window.event;
  1876. if (e) e.stopPropagation();
  1877. this.$button.trigger('click.bs.dropdown.data-api');
  1878. },
  1879. keydown: function (e) {
  1880. var $this = $(this),
  1881. $parent = $this.is('input') ? $this.parent().parent() : $this.parent(),
  1882. that = $parent.data('this'),
  1883. $items = that.findLis(),
  1884. index,
  1885. isActive,
  1886. liActive,
  1887. activeLi,
  1888. offset,
  1889. updateScroll = false,
  1890. downOnTab = e.which === keyCodes.TAB && !$this.hasClass('dropdown-toggle') && !that.options.selectOnTab,
  1891. isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab,
  1892. scrollTop = that.$menuInner[0].scrollTop,
  1893. isVirtual = that.isVirtual(),
  1894. position0 = isVirtual === true ? that.selectpicker.view.position0 : 0;
  1895. isActive = that.$newElement.hasClass(classNames.SHOW);
  1896. if (
  1897. !isActive &&
  1898. (
  1899. isArrowKey ||
  1900. e.which >= 48 && e.which <= 57 ||
  1901. e.which >= 96 && e.which <= 105 ||
  1902. e.which >= 65 && e.which <= 90
  1903. )
  1904. ) {
  1905. that.$button.trigger('click.bs.dropdown.data-api');
  1906. }
  1907. if (e.which === keyCodes.ESCAPE && isActive) {
  1908. e.preventDefault();
  1909. that.$button.trigger('click.bs.dropdown.data-api').focus();
  1910. }
  1911. if (isArrowKey) { // if up or down
  1912. if (!$items.length) return;
  1913. // $items.index/.filter is too slow with a large list and no virtual scroll
  1914. index = isVirtual === true ? $items.index($items.filter('.active')) : that.selectpicker.current.map.newIndex[that.activeIndex];
  1915. if (index === undefined) index = -1;
  1916. if (index !== -1) {
  1917. liActive = that.selectpicker.current.elements[index + position0];
  1918. liActive.classList.remove('active');
  1919. if (liActive.firstChild) liActive.firstChild.classList.remove('active');
  1920. }
  1921. if (e.which === keyCodes.ARROW_UP) { // up
  1922. if (index !== -1) index--;
  1923. if (index + position0 < 0) index += $items.length;
  1924. if (!that.selectpicker.view.canHighlight[index + position0]) {
  1925. index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0;
  1926. if (index === -1) index = $items.length - 1;
  1927. }
  1928. } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down
  1929. index++;
  1930. if (index + position0 >= that.selectpicker.view.canHighlight.length) index = 0;
  1931. if (!that.selectpicker.view.canHighlight[index + position0]) {
  1932. index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true);
  1933. }
  1934. }
  1935. e.preventDefault();
  1936. var liActiveIndex = position0 + index;
  1937. if (e.which === keyCodes.ARROW_UP) { // up
  1938. // scroll to bottom and highlight last option
  1939. if (position0 === 0 && index === $items.length - 1) {
  1940. that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight;
  1941. liActiveIndex = that.selectpicker.current.elements.length - 1;
  1942. } else {
  1943. activeLi = that.selectpicker.current.data[liActiveIndex];
  1944. offset = activeLi.position - activeLi.height;
  1945. updateScroll = offset < scrollTop;
  1946. }
  1947. } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down
  1948. // scroll to top and highlight first option
  1949. if (position0 !== 0 && index === 0) {
  1950. that.$menuInner[0].scrollTop = 0;
  1951. liActiveIndex = 0;
  1952. } else {
  1953. activeLi = that.selectpicker.current.data[liActiveIndex];
  1954. offset = activeLi.position - that.sizeInfo.menuInnerHeight;
  1955. updateScroll = offset > scrollTop;
  1956. }
  1957. }
  1958. liActive = that.selectpicker.current.elements[liActiveIndex];
  1959. liActive.classList.add('active');
  1960. if (liActive.firstChild) liActive.firstChild.classList.add('active');
  1961. that.activeIndex = that.selectpicker.current.map.originalIndex[liActiveIndex];
  1962. that.selectpicker.view.currentActive = liActive;
  1963. if (updateScroll) that.$menuInner[0].scrollTop = offset;
  1964. if (that.options.liveSearch) {
  1965. that.$searchbox.focus();
  1966. } else {
  1967. $this.focus();
  1968. }
  1969. } else if (
  1970. !$this.is('input') &&
  1971. !REGEXP_TAB_OR_ESCAPE.test(e.which) ||
  1972. (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory)
  1973. ) {
  1974. var searchMatch,
  1975. matches = [],
  1976. keyHistory;
  1977. e.preventDefault();
  1978. that.selectpicker.keydown.keyHistory += keyCodeMap[e.which];
  1979. if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel);
  1980. that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start();
  1981. keyHistory = that.selectpicker.keydown.keyHistory;
  1982. // if all letters are the same, set keyHistory to just the first character when searching
  1983. if (/^(.)\1+$/.test(keyHistory)) {
  1984. keyHistory = keyHistory.charAt(0);
  1985. }
  1986. // find matches
  1987. for (var i = 0; i < that.selectpicker.current.data.length; i++) {
  1988. var li = that.selectpicker.current.data[i],
  1989. hasMatch;
  1990. hasMatch = stringSearch(li, keyHistory, 'startsWith', true);
  1991. if (hasMatch && that.selectpicker.view.canHighlight[i]) {
  1992. li.index = i;
  1993. matches.push(li.originalIndex);
  1994. }
  1995. }
  1996. if (matches.length) {
  1997. var matchIndex = 0;
  1998. $items.removeClass('active').find('a').removeClass('active');
  1999. // either only one key has been pressed or they are all the same key
  2000. if (keyHistory.length === 1) {
  2001. matchIndex = matches.indexOf(that.activeIndex);
  2002. if (matchIndex === -1 || matchIndex === matches.length - 1) {
  2003. matchIndex = 0;
  2004. } else {
  2005. matchIndex++;
  2006. }
  2007. }
  2008. searchMatch = that.selectpicker.current.map.newIndex[matches[matchIndex]];
  2009. activeLi = that.selectpicker.current.data[searchMatch];
  2010. if (scrollTop - activeLi.position > 0) {
  2011. offset = activeLi.position - activeLi.height;
  2012. updateScroll = true;
  2013. } else {
  2014. offset = activeLi.position - that.sizeInfo.menuInnerHeight;
  2015. // if the option is already visible at the current scroll position, just keep it the same
  2016. updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight;
  2017. }
  2018. liActive = that.selectpicker.current.elements[searchMatch];
  2019. liActive.classList.add('active');
  2020. if (liActive.firstChild) liActive.firstChild.classList.add('active');
  2021. that.activeIndex = matches[matchIndex];
  2022. liActive.firstChild.focus();
  2023. if (updateScroll) that.$menuInner[0].scrollTop = offset;
  2024. $this.focus();
  2025. }
  2026. }
  2027. // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.
  2028. if (
  2029. isActive &&
  2030. (
  2031. (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) ||
  2032. e.which === keyCodes.ENTER ||
  2033. (e.which === keyCodes.TAB && that.options.selectOnTab)
  2034. )
  2035. ) {
  2036. if (e.which !== keyCodes.SPACE) e.preventDefault();
  2037. if (!that.options.liveSearch || e.which !== keyCodes.SPACE) {
  2038. that.$menuInner.find('.active a').trigger('click', true); // retain active class
  2039. $this.focus();
  2040. if (!that.options.liveSearch) {
  2041. // Prevent screen from scrolling if the user hits the spacebar
  2042. e.preventDefault();
  2043. // Fixes spacebar selection of dropdown items in FF & IE
  2044. $(document).data('spaceSelect', true);
  2045. }
  2046. }
  2047. }
  2048. },
  2049. mobile: function () {
  2050. this.$element.addClass('mobile-device');
  2051. },
  2052. refresh: function () {
  2053. // update options if data attributes have been changed
  2054. var config = $.extend({}, this.options, this.$element.data());
  2055. this.options = config;
  2056. this.selectpicker.main.map.newIndex = {};
  2057. this.selectpicker.main.map.originalIndex = {};
  2058. this.createLi();
  2059. this.checkDisabled();
  2060. this.render();
  2061. this.setStyle();
  2062. this.setWidth();
  2063. this.setSize(true);
  2064. this.$element.trigger('refreshed.bs.select');
  2065. },
  2066. hide: function () {
  2067. this.$newElement.hide();
  2068. },
  2069. show: function () {
  2070. this.$newElement.show();
  2071. },
  2072. remove: function () {
  2073. this.$newElement.remove();
  2074. this.$element.remove();
  2075. },
  2076. destroy: function () {
  2077. this.$newElement.before(this.$element).remove();
  2078. if (this.$bsContainer) {
  2079. this.$bsContainer.remove();
  2080. } else {
  2081. this.$menu.remove();
  2082. }
  2083. this.$element
  2084. .off('.bs.select')
  2085. .removeData('selectpicker')
  2086. .removeClass('bs-select-hidden selectpicker');
  2087. }
  2088. };
  2089. // SELECTPICKER PLUGIN DEFINITION
  2090. // ==============================
  2091. function Plugin(option) {
  2092. // get the args of the outer function..
  2093. var args = arguments;
  2094. // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
  2095. // to get lost/corrupted in android 2.3 and IE9 #715 #775
  2096. var _option = option;
  2097. [].shift.apply(args);
  2098. var value;
  2099. var chain = this.each(function () {
  2100. var $this = $(this);
  2101. if ($this.is('select')) {
  2102. var data = $this.data('selectpicker'),
  2103. options = typeof _option == 'object' && _option;
  2104. if (!data) {
  2105. var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options);
  2106. config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), $this.data().template, options.template);
  2107. $this.data('selectpicker', (data = new Selectpicker(this, config)));
  2108. } else if (options) {
  2109. for (var i in options) {
  2110. if (options.hasOwnProperty(i)) {
  2111. data.options[i] = options[i];
  2112. }
  2113. }
  2114. }
  2115. if (typeof _option == 'string') {
  2116. if (data[_option] instanceof Function) {
  2117. value = data[_option].apply(data, args);
  2118. } else {
  2119. value = data.options[_option];
  2120. }
  2121. }
  2122. }
  2123. });
  2124. if (typeof value !== 'undefined') {
  2125. //noinspection JSUnusedAssignment
  2126. return value;
  2127. } else {
  2128. return chain;
  2129. }
  2130. }
  2131. var old = $.fn.selectpicker;
  2132. $.fn.selectpicker = Plugin;
  2133. $.fn.selectpicker.Constructor = Selectpicker;
  2134. // SELECTPICKER NO CONFLICT
  2135. // ========================
  2136. $.fn.selectpicker.noConflict = function () {
  2137. $.fn.selectpicker = old;
  2138. return this;
  2139. };
  2140. $(document)
  2141. .off('keydown.bs.dropdown.data-api')
  2142. .on('keydown.bs.select', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bs-searchbox input', Selectpicker.prototype.keydown)
  2143. .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bs-searchbox input', function (e) {
  2144. e.stopPropagation();
  2145. });
  2146. // SELECTPICKER DATA-API
  2147. // =====================
  2148. $(window).on('load.bs.select.data-api', function () {
  2149. $('.selectpicker').each(function () {
  2150. var $selectpicker = $(this);
  2151. Plugin.call($selectpicker, $selectpicker.data());
  2152. })
  2153. });
  2154. })(jQuery);
  2155. }));