https://www.swt-sports.de/templates/xt_swt/components/bootstrap-se…?524040b4d767d245ce6beb22bcbef7c6
vendor: 116,018 bytes, lines 1-3248
1/*! 2 * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) 3 * 4 * Copyright 2012-2020 SnapAppointments, LLC 5 * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) 6 */ 7 8(function (root, factory) { 9 if (root === undefined && window !== undefined) root = window; 10 if (typeof define === 'function' && define.amd) { 11 // AMD. Register as an anonymous module unless amdModuleId is set 12 define(["jquery"], function (a0) { 13 return (factory(a0)); 14 }); 15 } else if (typeof module === 'object' && module.exports) { 16 // Node. Does not work with strict CommonJS, but 17 // only CommonJS-like environments that support module.exports, 18 // like Node. 19 module.exports = factory(require("jquery")); 20 } else { 21 factory(root["jQuery"]); 22 } 23}(this, function (jQuery) { 24 25(function ($) { 26 'use strict'; 27 28 var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; 29 30 var uriAttrs = [ 31 'background', 32 'cite', 33 'href', 34 'itemtype', 35 'longdesc', 36 'poster', 37 'src', 38 'xlink:href' 39 ]; 40 41 var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; 42 43 var DefaultWhitelist = { 44 // Global attributes allowed on any supplied element below. 45 '*': ['class', 'dir', 'id', 'lang', 'role', 'tabindex', 'style', ARIA_ATTRIBUTE_PATTERN], 46 a: ['target', 'href', 'title', 'rel'], 47 area: [], 48 b: [], 49 br: [], 50 col: [], 51 code: [], 52 div: [], 53 em: [], 54 hr: [], 55 h1: [], 56 h2: [], 57 h3: [], 58 h4: [], 59 h5: [], 60 h6: [], 61 i: [], 62 img: ['src', 'alt', 'title', 'width', 'height'], 63 li: [], 64 ol: [], 65 p: [], 66 pre: [], 67 s: [], 68 small: [], 69 span: [], 70 sub: [], 71 sup: [], 72 strong: [], 73 u: [], 74 ul: [] 75 } 76 77 /** 78 * A pattern that recognizes a commonly useful subset of URLs that are safe. 79 * 80 * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts 81 */ 82 var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; 83 84 /** 85 * A pattern that matches safe data URLs. Only matches image, video and audio types. 86 * 87 * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts 88 */ 89 var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; 90 91 function allowedAttribute (attr, allowedAttributeList) { 92 var attrName = attr.nodeName.toLowerCase() 93 94 if ($.inArray(attrName, allowedAttributeList) !== -1) { 95 if ($.inArray(attrName, uriAttrs) !== -1) { 96 return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) 97 } 98 99 return true 100 } 101 102 var regExp = $(allowedAttributeList).filter(function (index, value) { 103 return value instanceof RegExp 104 }) 105 106 // Check if a regular expression validates the attribute. 107 for (var i = 0, l = regExp.length; i < l; i++) { 108 if (attrName.match(regExp[i])) { 109 return true 110 } 111 } 112 113 return false 114 } 115 116 function sanitizeHtml (unsafeElements, whiteList, sanitizeFn) { 117 if (sanitizeFn && typeof sanitizeFn === 'function') { 118 return sanitizeFn(unsafeElements); 119 } 120 121 var whitelistKeys = Object.keys(whiteList); 122 123 for (var i = 0, len = unsafeElements.length; i < len; i++) { 124 var elements = unsafeElements[i].querySelectorAll('*'); 125 126 for (var j = 0, len2 = elements.length; j < len2; j++) { 127 var el = elements[j]; 128 var elName = el.nodeName.toLowerCase(); 129 130 if (whitelistKeys.indexOf(elName) === -1) { 131 el.parentNode.removeChild(el); 132 133 continue; 134 } 135 136 var attributeList = [].slice.call(el.attributes); 137 var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); 138 139 for (var k = 0, len3 = attributeList.length; k < len3; k++) { 140 var attr = attributeList[k]; 141 142 if (!allowedAttribute(attr, whitelistedAttributes)) { 143 el.removeAttribute(attr.nodeName); 144 } 145 } 146 } 147 } 148 } 149 150 // Polyfill for browsers with no classList support 151 // Remove in v2 152 if (!('classList' in document.createElement('_'))) { 153 (function (view) { 154 if (!('Element' in view)) return; 155 156 var classListProp = 'classList', 157 protoProp = 'prototype', 158 elemCtrProto = view.Element[protoProp], 159 objCtr = Object, 160 classListGetter = function () { 161 var $elem = $(this); 162 163 return { 164 add: function (classes) { 165 classes = Array.prototype.slice.call(arguments).join(' '); 166 return $elem.addClass(classes); 167 }, 168 remove: function (classes) { 169 classes = Array.prototype.slice.call(arguments).join(' '); 170 return $elem.removeClass(classes); 171 }, 172 toggle: function (classes, force) { 173 return $elem.toggleClass(classes, force); 174 }, 175 contains: function (classes) { 176 return $elem.hasClass(classes); 177 } 178 } 179 }; 180 181 if (objCtr.defineProperty) { 182 var classListPropDesc = { 183 get: classListGetter, 184 enumerable: true, 185 configurable: true 186 }; 187 try { 188 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 189 } catch (ex) { // IE 8 doesn't support enumerable:true 190 // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 191 // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected 192 if (ex.number === undefined || ex.number === -0x7FF5EC54) { 193 classListPropDesc.enumerable = false; 194 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 195 } 196 } 197 } else if (objCtr[protoProp].__defineGetter__) { 198 elemCtrProto.__defineGetter__(classListProp, classListGetter); 199 } 200 }(window)); 201 } 202 203 var testElement = document.createElement('_'); 204 205 testElement.classList.add('c1', 'c2'); 206 207 if (!testElement.classList.contains('c2')) { 208 var _add = DOMTokenList.prototype.add, 209 _remove = DOMTokenList.prototype.remove; 210 211 DOMTokenList.prototype.add = function () { 212 Array.prototype.forEach.call(arguments, _add.bind(this)); 213 } 214 215 DOMTokenList.prototype.remove = function () { 216 Array.prototype.forEach.call(arguments, _remove.bind(this)); 217 } 218 } 219 220 testElement.classList.toggle('c3', false); 221 222 // Polyfill for IE 10 and Firefox <24, where classList.toggle does not 223 // support the second argument. 224 if (testElement.classList.contains('c3')) { 225 var _toggle = DOMTokenList.prototype.toggle; 226 227 DOMTokenList.prototype.toggle = function (token, force) { 228 if (1 in arguments && !this.contains(token) === !force) { 229 return force; 230 } else { 231 return _toggle.call(this, token); 232 } 233 }; 234 } 235 236 testElement = null; 237 238 // shallow array comparison 239 function isEqual (array1, array2) { 240 return array1.length === array2.length && array1.every(function (element, index) { 241 return element === array2[index]; 242 }); 243 }; 244 245 // <editor-fold desc="Shims"> 246 if (!String.prototype.startsWith) { 247 (function () { 248 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` 249 var defineProperty = (function () { 250 // IE 8 only supports `Object.defineProperty` on DOM elements 251 try { 252 var object = {}; 253 var $defineProperty = Object.defineProperty; 254 var result = $defineProperty(object, object, object) && $defineProperty; 255 } catch (error) { 256 } 257 return result; 258 }()); 259 var toString = {}.toString; 260 var startsWith = function (search) { 261 if (this == null) { 262 throw new TypeError(); 263 } 264 var string = String(this); 265 if (search && toString.call(search) == '[object RegExp]') { 266 throw new TypeError(); 267 } 268 var stringLength = string.length; 269 var searchString = String(search); 270 var searchLength = searchString.length; 271 var position = arguments.length > 1 ? arguments[1] : undefined; 272 // `ToInteger` 273 var pos = position ? Number(position) : 0; 274 if (pos != pos) { // better `isNaN` 275 pos = 0; 276 } 277 var start = Math.min(Math.max(pos, 0), stringLength); 278 // Avoid the `indexOf` call if no match is possible 279 if (searchLength + start > stringLength) { 280 return false; 281 } 282 var index = -1; 283 while (++index < searchLength) { 284 if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { 285 return false; 286 } 287 } 288 return true; 289 }; 290 if (defineProperty) { 291 defineProperty(String.prototype, 'startsWith', { 292 'value': startsWith, 293 'configurable': true, 294 'writable': true 295 }); 296 } else { 297 String.prototype.startsWith = startsWith; 298 } 299 }()); 300 } 301 302 if (!Object.keys) { 303 Object.keys = function ( 304 o, // object 305 k, // key 306 r // result array 307 ) { 308 // initialize object and result 309 r = []; 310 // iterate over object keys 311 for (k in o) { 312 // fill result array with non-prototypical keys 313 r.hasOwnProperty.call(o, k) && r.push(k); 314 } 315 // return result 316 return r; 317 }; 318 } 319 320 if (HTMLSelectElement && !HTMLSelectElement.prototype.hasOwnProperty('selectedOptions')) { 321 Object.defineProperty(HTMLSelectElement.prototype, 'selectedOptions', { 322 get: function () { 323 return this.querySelectorAll(':checked'); 324 } 325 }); 326 } 327 328 function getSelectedOptions (select, ignoreDisabled) { 329 var selectedOptions = select.selectedOptions, 330 options = [], 331 opt; 332 333 if (ignoreDisabled) { 334 for (var i = 0, len = selectedOptions.length; i < len; i++) { 335 opt = selectedOptions[i]; 336 337 if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { 338 options.push(opt); 339 } 340 } 341 342 return options; 343 } 344 345 return selectedOptions; 346 } 347 348 // much faster than $.val() 349 function getSelectValues (select, selectedOptions) { 350 var value = [], 351 options = selectedOptions || select.selectedOptions, 352 opt; 353 354 for (var i = 0, len = options.length; i < len; i++) { 355 opt = options[i]; 356 357 if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { 358 value.push(opt.value); 359 } 360 } 361 362 if (!select.multiple) { 363 return !value.length ? null : value[0]; 364 } 365 366 return value; 367 } 368 369 // set data-selected on select element if the value has been programmatically selected 370 // prior to initialization of bootstrap-select 371 // * consider removing or replacing an alternative method * 372 var valHooks = { 373 useDefault: false, 374 _set: $.valHooks.select.set 375 }; 376 377 $.valHooks.select.set = function (elem, value) { 378 if (value && !valHooks.useDefault) $(elem).data('selected', true); 379 380 return valHooks._set.apply(this, arguments); 381 }; 382 383 var changedArguments = null; 384 385 var EventIsSupported = (function () { 386 try { 387 new Event('change'); 388 return true; 389 } catch (e) { 390 return false; 391 } 392 })(); 393 394 $.fn.triggerNative = function (eventName) { 395 var el = this[0], 396 event; 397 398 if (el.dispatchEvent) { // for modern browsers & IE9+ 399 if (EventIsSupported) { 400 // For modern browsers 401 event = new Event(eventName, { 402 bubbles: true 403 }); 404 } else { 405 // For IE since it doesn't support Event constructor 406 event = document.createEvent('Event'); 407 event.initEvent(eventName, true, false); 408 } 409 410 el.dispatchEvent(event); 411 } else if (el.fireEvent) { // for IE8 412 event = document.createEventObject(); 413 event.eventType = eventName; 414 el.fireEvent('on' + eventName, event); 415 } else { 416 // fall back to jQuery.trigger 417 this.trigger(eventName); 418 } 419 }; 420 // </editor-fold> 421 422 function stringSearch (li, searchString, method, normalize) { 423 var stringTypes = [ 424 'display', 425 'subtext', 426 'tokens' 427 ], 428 searchSuccess = false; 429 430 for (var i = 0; i < stringTypes.length; i++) { 431 var stringType = stringTypes[i], 432 string = li[stringType]; 433 434 if (string) { 435 string = string.toString(); 436 437 // Strip HTML tags. This isn't perfect, but it's much faster than any other method 438 if (stringType === 'display') { 439 string = string.replace(/<[^>]+>/g, ''); 440 } 441 442 if (normalize) string = normalizeToBase(string); 443 string = string.toUpperCase(); 444 445 if (method === 'contains') { 446 searchSuccess = string.indexOf(searchString) >= 0; 447 } else { 448 searchSuccess = string.startsWith(searchString); 449 } 450 451 if (searchSuccess) break; 452 } 453 } 454 455 return searchSuccess; 456 } 457 458 function toInteger (value) { 459 return parseInt(value, 10) || 0; 460 } 461 462 // Borrowed from Lodash (_.deburr) 463 /** Used to map Latin Unicode letters to basic Latin letters. */ 464 var deburredLetters = { 465 // Latin-1 Supplement block. 466 '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', 467 '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', 468 '\xc7': 'C', '\xe7': 'c', 469 '\xd0': 'D', '\xf0': 'd', 470 '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', 471 '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', 472 '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', 473 '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', 474 '\xd1': 'N', '\xf1': 'n', 475 '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', 476 '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', 477 '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', 478 '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', 479 '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', 480 '\xc6': 'Ae', '\xe6': 'ae', 481 '\xde': 'Th', '\xfe': 'th', 482 '\xdf': 'ss', 483 // Latin Extended-A block. 484 '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', 485 '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', 486 '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', 487 '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', 488 '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', 489 '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', 490 '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', 491 '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', 492 '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', 493 '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', 494 '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', 495 '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', 496 '\u0134': 'J', '\u0135': 'j', 497 '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', 498 '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', 499 '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', 500 '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', 501 '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', 502 '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', 503 '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', 504 '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', 505 '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', 506 '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', 507 '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', 508 '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', 509 '\u0163': 't', '\u0165': 't', '\u0167': 't', 510 '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', 511 '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', 512 '\u0174': 'W', '\u0175': 'w', 513 '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', 514 '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', 515 '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', 516 '\u0132': 'IJ', '\u0133': 'ij', 517 '\u0152': 'Oe', '\u0153': 'oe', 518 '\u0149': "'n", '\u017f': 's' 519 }; 520 521 /** Used to match Latin Unicode letters (excluding mathematical operators). */ 522 var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; 523 524 /** Used to compose unicode character classes. */ 525 var rsComboMarksRange = '\\u0300-\\u036f', 526 reComboHalfMarksRange = '\\ufe20-\\ufe2f', 527 rsComboSymbolsRange = '\\u20d0-\\u20ff', 528 rsComboMarksExtendedRange = '\\u1ab0-\\u1aff', 529 rsComboMarksSupplementRange = '\\u1dc0-\\u1dff', 530 rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange; 531 532 /** Used to compose unicode capture groups. */ 533 var rsCombo = '[' + rsComboRange + ']'; 534 535 /** 536 * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and 537 * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). 538 */ 539 var reComboMark = RegExp(rsCombo, 'g'); 540 541 function deburrLetter (key) { 542 return deburredLetters[key]; 543 }; 544 545 function normalizeToBase (string) { 546 string = string.toString(); 547 return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); 548 } 549 550 // List of HTML entities for escaping. 551 var escapeMap = { 552 '&': '&', 553 '<': '<', 554 '>': '>', 555 '"': '"', 556 "'": ''', 557 '`': '`' 558 }; 559 560 // Functions for escaping and unescaping strings to/from HTML interpolation. 561 var createEscaper = function (map) { 562 var escaper = function (match) { 563 return map[match]; 564 }; 565 // Regexes for identifying a key that needs to be escaped. 566 var source = '(?:' + Object.keys(map).join('|') + ')'; 567 var testRegexp = RegExp(source); 568 var replaceRegexp = RegExp(source, 'g'); 569 return function (string) { 570 string = string == null ? '' : '' + string; 571 return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; 572 }; 573 }; 574 575 var htmlEscape = createEscaper(escapeMap); 576 577 /** 578 * ------------------------------------------------------------------------ 579 * Constants 580 * ------------------------------------------------------------------------ 581 */ 582 583 var keyCodeMap = { 584 32: ' ', 585 48: '0', 586 49: '1', 587 50: '2', 588 51: '3', 589 52: '4', 590 53: '5', 591 54: '6', 592 55: '7', 593 56: '8', 594 57: '9', 595 59: ';', 596 65: 'A', 597 66: 'B', 598 67: 'C', 599 68: 'D', 600 69: 'E', 601 70: 'F', 602 71: 'G', 603 72: 'H', 604 73: 'I', 605 74: 'J', 606 75: 'K', 607 76: 'L', 608 77: 'M', 609 78: 'N', 610 79: 'O', 611 80: 'P', 612 81: 'Q', 613 82: 'R', 614 83: 'S', 615 84: 'T', 616 85: 'U', 617 86: 'V', 618 87: 'W', 619 88: 'X', 620 89: 'Y', 621 90: 'Z', 622 96: '0', 623 97: '1', 624 98: '2', 625 99: '3', 626 100: '4', 627 101: '5', 628 102: '6', 629 103: '7', 630 104: '8', 631 105: '9' 632 }; 633 634 var keyCodes = { 635 ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key 636 ENTER: 13, // KeyboardEvent.which value for Enter key 637 SPACE: 32, // KeyboardEvent.which value for space key 638 TAB: 9, // KeyboardEvent.which value for tab key 639 ARROW_UP: 38, // KeyboardEvent.which value for up arrow key 640 ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key 641 } 642 643 var version = { 644 success: false, 645 major: '3' 646 }; 647 648 try { 649 version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); 650 version.major = version.full[0]; 651 version.success = true; 652 } catch (err) { 653 // do nothing 654 } 655 656 var selectId = 0; 657 658 var EVENT_KEY = '.bs.select'; 659 660 var classNames = { 661 DISABLED: 'disabled', 662 DIVIDER: 'divider', 663 SHOW: 'open', 664 DROPUP: 'dropup', 665 MENU: 'dropdown-menu', 666 MENURIGHT: 'dropdown-menu-right', 667 MENULEFT: 'dropdown-menu-left', 668 // to-do: replace with more advanced template/customization options 669 BUTTONCLASS: 'btn-default', 670 POPOVERHEADER: 'popover-title', 671 ICONBASE: 'glyphicon', 672 TICKICON: 'glyphicon-ok' 673 } 674 675 var Selector = { 676 MENU: '.' + classNames.MENU 677 } 678 679 var elementTemplates = { 680 div: document.createElement('div'), 681 span: document.createElement('span'), 682 i: document.createElement('i'), 683 subtext: document.createElement('small'), 684 a: document.createElement('a'), 685 li: document.createElement('li'), 686 whitespace: document.createTextNode('\u00A0'), 687 fragment: document.createDocumentFragment() 688 } 689 690 elementTemplates.noResults = elementTemplates.li.cloneNode(false); 691 elementTemplates.noResults.className = 'no-results'; 692 693 elementTemplates.a.setAttribute('role', 'option'); 694 elementTemplates.a.className = 'dropdown-item'; 695 696 elementTemplates.subtext.className = 'text-muted'; 697 698 elementTemplates.text = elementTemplates.span.cloneNode(false); 699 elementTemplates.text.className = 'text'; 700 701 elementTemplates.checkMark = elementTemplates.span.cloneNode(false); 702 703 var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN); 704 var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE); 705 706 var generateOption = { 707 li: function (content, classes, optgroup) { 708 var li = elementTemplates.li.cloneNode(false); 709 710 if (content) { 711 if (content.nodeType === 1 || content.nodeType === 11) { 712 li.appendChild(content); 713 } else { 714 li.innerHTML = content; 715 } 716 } 717 718 if (typeof classes !== 'undefined' && classes !== '') li.className = classes; 719 if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup); 720 721 return li; 722 }, 723 724 a: function (text, classes, inline) { 725 var a = elementTemplates.a.cloneNode(true); 726 727 if (text) { 728 if (text.nodeType === 11) { 729 a.appendChild(text); 730 } else { 731 a.insertAdjacentHTML('beforeend', text); 732 } 733 } 734 735 if (typeof classes !== 'undefined' && classes !== '') a.classList.add.apply(a.classList, classes.split(/\s+/)); 736 if (inline) a.setAttribute('style', inline); 737 738 return a; 739 }, 740 741 text: function (options, useFragment) { 742 var textElement = elementTemplates.text.cloneNode(false), 743 subtextElement, 744 iconElement; 745 746 if (options.content) { 747 textElement.innerHTML = options.content; 748 } else { 749 textElement.textContent = options.text; 750 751 if (options.icon) { 752 var whitespace = elementTemplates.whitespace.cloneNode(false); 753 754 // need to use <i> for icons in the button to prevent a breaking change 755 // note: switch to span in next major release 756 iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false); 757 iconElement.className = this.options.iconBase + ' ' + options.icon; 758 759 elementTemplates.fragment.appendChild(iconElement); 760 elementTemplates.fragment.appendChild(whitespace); 761 } 762 763 if (options.subtext) { 764 subtextElement = elementTemplates.subtext.cloneNode(false); 765 subtextElement.textContent = options.subtext; 766 textElement.appendChild(subtextElement); 767 } 768 } 769 770 if (useFragment === true) { 771 while (textElement.childNodes.length > 0) { 772 elementTemplates.fragment.appendChild(textElement.childNodes[0]); 773 } 774 } else { 775 elementTemplates.fragment.appendChild(textElement); 776 } 777 778 return elementTemplates.fragment; 779 }, 780 781 label: function (options) { 782 var textElement = elementTemplates.text.cloneNode(false), 783 subtextElement, 784 iconElement; 785 786 textElement.innerHTML = options.display; 787 788 if (options.icon) { 789 var whitespace = elementTemplates.whitespace.cloneNode(false); 790 791 iconElement = elementTemplates.span.cloneNode(false); 792 iconElement.className = this.options.iconBase + ' ' + options.icon; 793 794 elementTemplates.fragment.appendChild(iconElement); 795 elementTemplates.fragment.appendChild(whitespace); 796 } 797 798 if (options.subtext) { 799 subtextElement = elementTemplates.subtext.cloneNode(false); 800 subtextElement.textContent = options.subtext; 801 textElement.appendChild(subtextElement); 802 } 803 804 elementTemplates.fragment.appendChild(textElement); 805 806 return elementTemplates.fragment; 807 } 808 } 809 810 function showNoResults (searchMatch, searchValue) { 811 if (!searchMatch.length) { 812 elementTemplates.noResults.innerHTML = this.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"'); 813 this.$menuInner[0].firstChild.appendChild(elementTemplates.noResults); 814 } 815 } 816 817 var Selectpicker = function (element, options) { 818 var that = this; 819 820 // bootstrap-select has been initialized - revert valHooks.select.set back to its original function 821 if (!valHooks.useDefault) { 822 $.valHooks.select.set = valHooks._set; 823 valHooks.useDefault = true; 824 } 825 826 this.$element = $(element); 827 this.$newElement = null; 828 this.$button = null; 829 this.$menu = null; 830 this.options = options; 831 this.selectpicker = { 832 main: {}, 833 search: {}, 834 current: {}, // current changes if a search is in progress 835 view: {}, 836 isSearching: false, 837 keydown: { 838 keyHistory: '', 839 resetKeyHistory: { 840 start: function () { 841 return setTimeout(function () { 842 that.selectpicker.keydown.keyHistory = ''; 843 }, 800); 844 } 845 } 846 } 847 }; 848 849 this.sizeInfo = {}; 850 851 // 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 852 // data-attribute) 853 if (this.options.title === null) { 854 this.options.title = this.$element.attr('title'); 855 } 856 857 // Format window padding 858 var winPad = this.options.windowPadding; 859 if (typeof winPad === 'number') { 860 this.options.windowPadding = [winPad, winPad, winPad, winPad]; 861 } 862 863 // Expose public methods 864 this.val = Selectpicker.prototype.val; 865 this.render = Selectpicker.prototype.render; 866 this.refresh = Selectpicker.prototype.refresh; 867 this.setStyle = Selectpicker.prototype.setStyle; 868 this.selectAll = Selectpicker.prototype.selectAll; 869 this.deselectAll = Selectpicker.prototype.deselectAll; 870 this.destroy = Selectpicker.prototype.destroy; 871 this.remove = Selectpicker.prototype.remove; 872 this.show = Selectpicker.prototype.show; 873 this.hide = Selectpicker.prototype.hide; 874 875 this.init(); 876 }; 877 878 Selectpicker.VERSION = '1.13.18'; 879 880 // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. 881 Selectpicker.DEFAULTS = { 882 noneSelectedText: 'Nothing selected', 883 noneResultsText: 'No results matched {0}', 884 countSelectedText: function (numSelected, numTotal) { 885 return (numSelected == 1) ? '{0} item selected' : '{0} items selected'; 886 }, 887 maxOptionsText: function (numAll, numGroup) { 888 return [ 889 (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', 890 (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' 891 ]; 892 }, 893 selectAllText: 'Select All', 894 deselectAllText: 'Deselect All', 895 doneButton: false, 896 doneButtonText: 'Close', 897 multipleSeparator: ', ', 898 styleBase: 'btn', 899 style: classNames.BUTTONCLASS, 900 size: 'auto', 901 title: null, 902 selectedTextFormat: 'values', 903 width: false, 904 container: false, 905 hideDisabled: false, 906 showSubtext: false, 907 showIcon: true, 908 showContent: true, 909 dropupAuto: true, 910 header: false, 911 liveSearch: false, 912 liveSearchPlaceholder: null, 913 liveSearchNormalize: false, 914 liveSearchStyle: 'contains', 915 actionsBox: false, 916 iconBase: classNames.ICONBASE, 917 tickIcon: classNames.TICKICON, 918 showTick: false, 919 template: { 920 caret: '<span class="caret"></span>' 921 }, 922 maxOptions: false, 923 mobile: false, 924 selectOnTab: false, 925 dropdownAlignRight: false, 926 windowPadding: 0, 927 virtualScroll: 600, 928 display: false, 929 sanitize: true, 930 sanitizeFn: null, 931 whiteList: DefaultWhitelist 932 }; 933 934 Selectpicker.prototype = { 935 936 constructor: Selectpicker, 937 938 init: function () { 939 var that = this, 940 id = this.$element.attr('id'), 941 element = this.$element[0], 942 form = element.form; 943 944 selectId++; 945 this.selectId = 'bs-select-' + selectId; 946 947 element.classList.add('bs-select-hidden'); 948 949 this.multiple = this.$element.prop('multiple'); 950 this.autofocus = this.$element.prop('autofocus'); 951 952 if (element.classList.contains('show-tick')) { 953 this.options.showTick = true; 954 } 955 956 this.$newElement = this.createDropdown(); 957 this.buildData(); 958 this.$element 959 .after(this.$newElement) 960 .prependTo(this.$newElement); 961 962 // ensure select is associated with form element if it got unlinked after moving it inside newElement 963 if (form && element.form === null) { 964 if (!form.id) form.id = 'form-' + this.selectId; 965 element.setAttribute('form', form.id); 966 } 967 968 this.$button = this.$newElement.children('button'); 969 this.$menu = this.$newElement.children(Selector.MENU); 970 this.$menuInner = this.$menu.children('.inner'); 971 this.$searchbox = this.$menu.find('input'); 972 973 element.classList.remove('bs-select-hidden'); 974 975 if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT); 976 977 if (typeof id !== 'undefined') { 978 this.$button.attr('data-id', id); 979 } 980 981 this.checkDisabled(); 982 this.clickListener(); 983 984 if (this.options.liveSearch) { 985 this.liveSearchListener(); 986 this.focusedParent = this.$searchbox[0]; 987 } else { 988 this.focusedParent = this.$menuInner[0]; 989 } 990 991 this.setStyle(); 992 this.render(); 993 this.setWidth(); 994 if (this.options.container) { 995 this.selectPosition(); 996 } else { 997 this.$element.on('hide' + EVENT_KEY, function () { 998 if (that.isVirtual()) { 999 // empty menu on close 1000 var menuInner = that.$menuInner[0], 1001 emptyMenu = menuInner.firstChild.cloneNode(false); 1002 1003 // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = '' 1004 menuInner.replaceChild(emptyMenu, menuInner.firstChild); 1005 menuInner.scrollTop = 0; 1006 } 1007 }); 1008 } 1009 this.$menu.data('this', this); 1010 this.$newElement.data('this', this); 1011 if (this.options.mobile) this.mobile(); 1012 1013 this.$newElement.on({ 1014 'hide.bs.dropdown': function (e) { 1015 that.$element.trigger('hide' + EVENT_KEY, e); 1016 }, 1017 'hidden.bs.dropdown': function (e) { 1018 that.$element.trigger('hidden' + EVENT_KEY, e); 1019 }, 1020 'show.bs.dropdown': function (e) { 1021 that.$element.trigger('show' + EVENT_KEY, e); 1022 }, 1023 'shown.bs.dropdown': function (e) { 1024 that.$element.trigger('shown' + EVENT_KEY, e); 1025 } 1026 }); 1027 1028 if (element.hasAttribute('required')) { 1029 this.$element.on('invalid' + EVENT_KEY, function () { 1030 that.$button[0].classList.add('bs-invalid'); 1031 1032 that.$element 1033 .on('shown' + EVENT_KEY + '.invalid', function () { 1034 that.$element 1035 .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened 1036 .off('shown' + EVENT_KEY + '.invalid'); 1037 }) 1038 .on('rendered' + EVENT_KEY, function () { 1039 // if select is no longer invalid, remove the bs-invalid class 1040 if (this.validity.valid) that.$button[0].classList.remove('bs-invalid'); 1041 that.$element.off('rendered' + EVENT_KEY); 1042 }); 1043 1044 that.$button.on('blur' + EVENT_KEY, function () { 1045 that.$element.trigger('focus').trigger('blur'); 1046 that.$button.off('blur' + EVENT_KEY); 1047 }); 1048 }); 1049 } 1050 1051 setTimeout(function () { 1052 that.buildList(); 1053 that.$element.trigger('loaded' + EVENT_KEY); 1054 }); 1055 }, 1056 1057 createDropdown: function () { 1058 // Options 1059 // If we are multiple or showTick option is set, then add the show-tick class 1060 var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', 1061 multiselectable = this.multiple ? ' aria-multiselectable="true"' : '', 1062 inputGroup = '', 1063 autofocus = this.autofocus ? ' autofocus' : ''; 1064 1065 if (version.major < 4 && this.$element.parent().hasClass('input-group')) { 1066 inputGroup = ' input-group-btn'; 1067 } 1068 1069 // Elements 1070 var drop, 1071 header = '', 1072 searchbox = '', 1073 actionsbox = '', 1074 donebutton = ''; 1075 1076 if (this.options.header) { 1077 header = 1078 '<div class="' + classNames.POPOVERHEADER + '">' + 1079 '<button type="button" class="close" aria-hidden="true">×</button>' + 1080 this.options.header + 1081 '</div>'; 1082 } 1083 1084 if (this.options.liveSearch) { 1085 searchbox = 1086 '<div class="bs-searchbox">' + 1087 '<input type="search" class="form-control" autocomplete="off"' + 1088 ( 1089 this.options.liveSearchPlaceholder === null ? '' 1090 : 1091 ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"' 1092 ) + 1093 ' role="combobox" aria-label="Search" aria-controls="' + this.selectId + '" aria-autocomplete="list">' + 1094 '</div>'; 1095 } 1096 1097 if (this.multiple && this.options.actionsBox) { 1098 actionsbox = 1099 '<div class="bs-actionsbox">' + 1100 '<div class="btn-group btn-group-sm btn-block">' + 1101 '<button type="button" class="actions-btn bs-select-all btn ' + classNames.BUTTONCLASS + '">' + 1102 this.options.selectAllText + 1103 '</button>' + 1104 '<button type="button" class="actions-btn bs-deselect-all btn ' + classNames.BUTTONCLASS + '">' + 1105 this.options.deselectAllText + 1106 '</button>' + 1107 '</div>' + 1108 '</div>'; 1109 } 1110 1111 if (this.multiple && this.options.doneButton) { 1112 donebutton = 1113 '<div class="bs-donebutton">' + 1114 '<div class="btn-group btn-block">' + 1115 '<button type="button" class="btn btn-sm ' + classNames.BUTTONCLASS + '">' + 1116 this.options.doneButtonText + 1117 '</button>' + 1118 '</div>' + 1119 '</div>'; 1120 } 1121 1122 drop = 1123 '<div class="dropdown bootstrap-select' + showTick + inputGroup + '">' + 1124 '<button type="button" tabindex="-1" class="' + this.options.styleBase + ' dropdown-toggle" ' + (this.options.display === 'static' ? 'data-display="static"' : '') + 'data-toggle="dropdown"' + autofocus + ' role="combobox" aria-controls="' + this.selectId + '" aria-haspopup="listbox" aria-expanded="false">' + 1125 '<div class="filter-option">' + 1126 '<div class="filter-option-inner">' + 1127 '<div class="filter-option-inner-inner"></div>' + 1128 '</div> ' + 1129 '</div>' + 1130 ( 1131 version.major === '4' ? '' 1132 : 1133 '<span class="bs-caret">' + 1134 this.options.template.caret + 1135 '</span>' 1136 ) + 1137 '</button>' + 1138 '<div class="' + classNames.MENU + ' ' + (version.major === '4' ? '' : classNames.SHOW) + '">' + 1139 header + 1140 searchbox + 1141 actionsbox + 1142 '<div class="inner ' + classNames.SHOW + '" role="listbox" id="' + this.selectId + '" tabindex="-1" ' + multiselectable + '>' + 1143 '<ul class="' + classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : '') + '" role="presentation">' + 1144 '</ul>' + 1145 '</div>' + 1146 donebutton + 1147 '</div>' + 1148 '</div>'; 1149 1150 return $(drop); 1151 }, 1152 1153 setPositionData: function () { 1154 this.selectpicker.view.canHighlight = []; 1155 this.selectpicker.view.size = 0; 1156 this.selectpicker.view.firstHighlightIndex = false; 1157 1158 for (var i = 0; i < this.selectpicker.current.data.length; i++) { 1159 var li = this.selectpicker.current.data[i], 1160 canHighlight = true; 1161 1162 if (li.type === 'divider') { 1163 canHighlight = false; 1164 li.height = this.sizeInfo.dividerHeight; 1165 } else if (li.type === 'optgroup-label') { 1166 canHighlight = false; 1167 li.height = this.sizeInfo.dropdownHeaderHeight; 1168 } else { 1169 li.height = this.sizeInfo.liHeight; 1170 } 1171 1172 if (li.disabled) canHighlight = false; 1173 1174 this.selectpicker.view.canHighlight.push(canHighlight); 1175 1176 if (canHighlight) { 1177 this.selectpicker.view.size++; 1178 li.posinset = this.selectpicker.view.size; 1179 if (this.selectpicker.view.firstHighlightIndex === false) this.selectpicker.view.firstHighlightIndex = i; 1180 } 1181 1182 li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height; 1183 } 1184 }, 1185 1186 isVirtual: function () { 1187 return (this.options.virtualScroll !== false) && (this.selectpicker.main.elements.length >= this.options.virtualScroll) || this.options.virtualScroll === true; 1188 }, 1189 1190 createView: function (isSearching, setSize, refresh) { 1191 var that = this, 1192 scrollTop = 0, 1193 active = [], 1194 selected, 1195 prevActive; 1196 1197 this.selectpicker.isSearching = isSearching; 1198 this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main; 1199 1200 this.setPositionData(); 1201 1202 if (setSize) { 1203 if (refresh) { 1204 scrollTop = this.$menuInner[0].scrollTop; 1205 } else if (!that.multiple) { 1206 var element = that.$element[0], 1207 selectedIndex = (element.options[element.selectedIndex] || {}).liIndex; 1208 1209 if (typeof selectedIndex === 'number' && that.options.size !== false) { 1210 var selectedData = that.selectpicker.main.data[selectedIndex], 1211 position = selectedData && selectedData.position; 1212 1213 if (position) { 1214 scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2); 1215 } 1216 } 1217 } 1218 } 1219 1220 scroll(scrollTop, true); 1221 1222 this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) { 1223 if (!that.noScroll) scroll(this.scrollTop, updateValue); 1224 that.noScroll = false; 1225 }); 1226 1227 function scroll (scrollTop, init) { 1228 var size = that.selectpicker.current.elements.length, 1229 chunks = [], 1230 chunkSize, 1231 chunkCount, 1232 firstChunk, 1233 lastChunk, 1234 currentChunk, 1235 prevPositions, 1236 positionIsDifferent, 1237 previousElements, 1238 menuIsDifferent = true, 1239 isVirtual = that.isVirtual(); 1240 1241 that.selectpicker.view.scrollTop = scrollTop; 1242 1243 chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk 1244 chunkCount = Math.round(size / chunkSize) || 1; // number of chunks 1245 1246 for (var i = 0; i < chunkCount; i++) { 1247 var endOfChunk = (i + 1) * chunkSize; 1248 1249 if (i === chunkCount - 1) { 1250 endOfChunk = size; 1251 } 1252 1253 chunks[i] = [ 1254 (i) * chunkSize + (!i ? 0 : 1), 1255 endOfChunk 1256 ]; 1257 1258 if (!size) break; 1259 1260 if (currentChunk === undefined && scrollTop - 1 <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) { 1261 currentChunk = i; 1262 } 1263 } 1264 1265 if (currentChunk === undefined) currentChunk = 0; 1266 1267 prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1]; 1268 1269 // always display previous, current, and next chunks 1270 firstChunk = Math.max(0, currentChunk - 1); 1271 lastChunk = Math.min(chunkCount - 1, currentChunk + 1); 1272 1273 that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0); 1274 that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0); 1275 1276 positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1; 1277 1278 if (that.activeIndex !== undefined) { 1279 prevActive = that.selectpicker.main.elements[that.prevActiveIndex]; 1280 active = that.selectpicker.main.elements[that.activeIndex]; 1281 selected = that.selectpicker.main.elements[that.selectedIndex]; 1282 1283 if (init) { 1284 if (that.activeIndex !== that.selectedIndex) { 1285 that.defocusItem(active); 1286 } 1287 that.activeIndex = undefined; 1288 } 1289 1290 if (that.activeIndex && that.activeIndex !== that.selectedIndex) { 1291 that.defocusItem(selected); 1292 } 1293 } 1294 1295 if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex) { 1296 that.defocusItem(prevActive); 1297 } 1298 1299 if (init || positionIsDifferent) { 1300 previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : []; 1301 1302 if (isVirtual === false) { 1303 that.selectpicker.view.visibleElements = that.selectpicker.current.elements; 1304 } else { 1305 that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1); 1306 } 1307 1308 that.setOptionStatus(); 1309 1310 // if searching, check to make sure the list has actually been updated before updating DOM 1311 // this prevents unnecessary repaints 1312 if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements); 1313 1314 // if virtual scroll is disabled and not searching, 1315 // menu should never need to be updated more than once 1316 if ((init || isVirtual === true) && menuIsDifferent) { 1317 var menuInner = that.$menuInner[0], 1318 menuFragment = document.createDocumentFragment(), 1319 emptyMenu = menuInner.firstChild.cloneNode(false), 1320 marginTop, 1321 marginBottom, 1322 elements = that.selectpicker.view.visibleElements, 1323 toSanitize = []; 1324 1325 // replace the existing UL with an empty one - this is faster than $.empty() 1326 menuInner.replaceChild(emptyMenu, menuInner.firstChild); 1327 1328 for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) { 1329 var element = elements[i], 1330 elText, 1331 elementData; 1332 1333 if (that.options.sanitize) { 1334 elText = element.lastChild; 1335 1336 if (elText) { 1337 elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0]; 1338 1339 if (elementData && elementData.content && !elementData.sanitized) { 1340 toSanitize.push(elText); 1341 elementData.sanitized = true; 1342 } 1343 } 1344 } 1345 1346 menuFragment.appendChild(element); 1347 } 1348 1349 if (that.options.sanitize && toSanitize.length) { 1350 sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn); 1351 } 1352 1353 if (isVirtual === true) { 1354 marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position); 1355 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); 1356 1357 menuInner.firstChild.style.marginTop = marginTop + 'px'; 1358 menuInner.firstChild.style.marginBottom = marginBottom + 'px'; 1359 } else { 1360 menuInner.firstChild.style.marginTop = 0; 1361 menuInner.firstChild.style.marginBottom = 0; 1362 } 1363 1364 menuInner.firstChild.appendChild(menuFragment); 1365 1366 // if an option is encountered that is wider than the current menu width, update the menu width accordingly 1367 // switch to ResizeObserver with increased browser support 1368 if (isVirtual === true && that.sizeInfo.hasScrollBar) { 1369 var menuInnerInnerWidth = menuInner.firstChild.offsetWidth; 1370 1371 if (init && menuInnerInnerWidth < that.sizeInfo.menuInnerInnerWidth && that.sizeInfo.totalMenuWidth > that.sizeInfo.selectWidth) { 1372 menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; 1373 } else if (menuInnerInnerWidth > that.sizeInfo.menuInnerInnerWidth) { 1374 // set to 0 to get actual width of menu 1375 that.$menu[0].style.minWidth = 0; 1376 1377 var actualMenuWidth = menuInner.firstChild.offsetWidth; 1378 1379 if (actualMenuWidth > that.sizeInfo.menuInnerInnerWidth) { 1380 that.sizeInfo.menuInnerInnerWidth = actualMenuWidth; 1381 menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; 1382 } 1383 1384 // reset to default CSS styling 1385 that.$menu[0].style.minWidth = ''; 1386 } 1387 } 1388 } 1389 } 1390 1391 that.prevActiveIndex = that.activeIndex; 1392 1393 if (!that.options.liveSearch) { 1394 that.$menuInner.trigger('focus'); 1395 } else if (isSearching && init) { 1396 var index = 0, 1397 newActive; 1398 1399 if (!that.selectpicker.view.canHighlight[index]) { 1400 index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true); 1401 } 1402 1403 newActive = that.selectpicker.view.visibleElements[index]; 1404 1405 that.defocusItem(that.selectpicker.view.currentActive); 1406 1407 that.activeIndex = (that.selectpicker.current.data[index] || {}).index; 1408 1409 that.focusItem(newActive); 1410 } 1411 } 1412 1413 $(window) 1414 .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView') 1415 .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () { 1416 var isActive = that.$newElement.hasClass(classNames.SHOW); 1417 1418 if (isActive) scroll(that.$menuInner[0].scrollTop); 1419 }); 1420 }, 1421 1422 focusItem: function (li, liData, noStyle) { 1423 if (li) { 1424 liData = liData || this.selectpicker.main.data[this.activeIndex]; 1425 var a = li.firstChild; 1426 1427 if (a) { 1428 a.setAttribute('aria-setsize', this.selectpicker.view.size); 1429 a.setAttribute('aria-posinset', liData.posinset); 1430 1431 if (noStyle !== true) { 1432 this.focusedParent.setAttribute('aria-activedescendant', a.id); 1433 li.classList.add('active'); 1434 a.classList.add('active'); 1435 } 1436 } 1437 } 1438 }, 1439 1440 defocusItem: function (li) { 1441 if (li) { 1442 li.classList.remove('active'); 1443 if (li.firstChild) li.firstChild.classList.remove('active'); 1444 } 1445 }, 1446 1447 setPlaceholder: function () { 1448 var that = this, 1449 updateIndex = false; 1450 1451 if (this.options.title && !this.multiple) { 1452 if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option'); 1453 1454 // this option doesn't create a new <li> element, but does add a new option at the start, 1455 // so startIndex should increase to prevent having to check every option for the bs-title-option class 1456 updateIndex = true; 1457 1458 var element = this.$element[0], 1459 selectTitleOption = false, 1460 titleNotAppended = !this.selectpicker.view.titleOption.parentNode, 1461 selectedIndex = element.selectedIndex, 1462 selectedOption = element.options[selectedIndex], 1463 navigation = window.performance && window.performance.getEntriesByType('navigation'), 1464 // Safari doesn't support getEntriesByType('navigation') - fall back to performance.navigation 1465 isNotBackForward = (navigation && navigation.length) ? navigation[0].type !== 'back_forward' : window.performance.navigation.type !== 2; 1466 1467 if (titleNotAppended) { 1468 // Use native JS to prepend option (faster) 1469 this.selectpicker.view.titleOption.className = 'bs-title-option'; 1470 this.selectpicker.view.titleOption.value = ''; 1471 1472 // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. 1473 // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, 1474 // if so, the select will have the data-selected attribute 1475 selectTitleOption = !selectedOption || (selectedIndex === 0 && selectedOption.defaultSelected === false && this.$element.data('selected') === undefined); 1476 } 1477 1478 if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) { 1479 element.insertBefore(this.selectpicker.view.titleOption, element.firstChild); 1480 } 1481 1482 // Set selected *after* appending to select, 1483 // otherwise the option doesn't get selected in IE 1484 // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11 1485 if (selectTitleOption && isNotBackForward) { 1486 element.selectedIndex = 0; 1487 } else if (document.readyState !== 'complete') { 1488 // if navigation type is back_forward, there's a chance the select will have its value set by BFCache 1489 // wait for that value to be set, then run render again 1490 window.addEventListener('pageshow', function () { 1491 if (that.selectpicker.view.displayedValue !== element.value) that.render(); 1492 }); 1493 } 1494 } 1495 1496 return updateIndex; 1497 }, 1498 1499 buildData: function () { 1500 var optionSelector = ':not([hidden]):not([data-hidden="true"])', 1501 mainData = [], 1502 optID = 0, 1503 startIndex = this.setPlaceholder() ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop 1504 1505 if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; 1506 1507 var selectOptions = this.$element[0].querySelectorAll('select > *' + optionSelector); 1508 1509 function addDivider (config) { 1510 var previousData = mainData[mainData.length - 1]; 1511 1512 // ensure optgroup doesn't create back-to-back dividers 1513 if ( 1514 previousData && 1515 previousData.type === 'divider' && 1516 (previousData.optID || config.optID) 1517 ) { 1518 return; 1519 } 1520 1521 config = config || {}; 1522 config.type = 'divider'; 1523 1524 mainData.push(config); 1525 } 1526 1527 function addOption (option, config) { 1528 config = config || {}; 1529 1530 config.divider = option.getAttribute('data-divider') === 'true'; 1531 1532 if (config.divider) { 1533 addDivider({ 1534 optID: config.optID 1535 }); 1536 } else { 1537 var liIndex = mainData.length, 1538 cssText = option.style.cssText, 1539 inlineStyle = cssText ? htmlEscape(cssText) : '', 1540 optionClass = (option.className || '') + (config.optgroupClass || ''); 1541 1542 if (config.optID) optionClass = 'opt ' + optionClass; 1543 1544 config.optionClass = optionClass.trim(); 1545 config.inlineStyle = inlineStyle; 1546 config.text = option.textContent; 1547 1548 config.content = option.getAttribute('data-content'); 1549 config.tokens = option.getAttribute('data-tokens'); 1550 config.subtext = option.getAttribute('data-subtext'); 1551 config.icon = option.getAttribute('data-icon'); 1552 1553 option.liIndex = liIndex; 1554 1555 config.display = config.content || config.text; 1556 config.type = 'option'; 1557 config.index = liIndex; 1558 config.option = option; 1559 config.selected = !!option.selected; 1560 config.disabled = config.disabled || !!option.disabled; 1561 1562 mainData.push(config); 1563 } 1564 } 1565 1566 function addOptgroup (index, selectOptions) { 1567 var optgroup = selectOptions[index], 1568 // skip placeholder option 1569 previous = index - 1 < startIndex ? false : selectOptions[index - 1], 1570 next = selectOptions[index + 1], 1571 options = optgroup.querySelectorAll('option' + optionSelector); 1572 1573 if (!options.length) return; 1574 1575 var config = { 1576 display: htmlEscape(optgroup.label), 1577 subtext: optgroup.getAttribute('data-subtext'), 1578 icon: optgroup.getAttribute('data-icon'), 1579 type: 'optgroup-label', 1580 optgroupClass: ' ' + (optgroup.className || '') 1581 }, 1582 headerIndex, 1583 lastIndex; 1584 1585 optID++; 1586 1587 if (previous) { 1588 addDivider({ optID: optID }); 1589 } 1590 1591 config.optID = optID; 1592 1593 mainData.push(config); 1594 1595 for (var j = 0, len = options.length; j < len; j++) { 1596 var option = options[j]; 1597 1598 if (j === 0) { 1599 headerIndex = mainData.length - 1; 1600 lastIndex = headerIndex + len; 1601 } 1602 1603 addOption(option, { 1604 headerIndex: headerIndex, 1605 lastIndex: lastIndex, 1606 optID: config.optID, 1607 optgroupClass: config.optgroupClass, 1608 disabled: optgroup.disabled 1609 }); 1610 } 1611 1612 if (next) { 1613 addDivider({ optID: optID }); 1614 } 1615 } 1616 1617 for (var len = selectOptions.length, i = startIndex; i < len; i++) { 1618 var item = selectOptions[i]; 1619 1620 if (item.tagName !== 'OPTGROUP') { 1621 addOption(item, {}); 1622 } else { 1623 addOptgroup(i, selectOptions); 1624 } 1625 } 1626 1627 this.selectpicker.main.data = this.selectpicker.current.data = mainData; 1628 }, 1629 1630 buildList: function () { 1631 var that = this, 1632 selectData = this.selectpicker.main.data, 1633 mainElements = [], 1634 widestOptionLength = 0; 1635 1636 if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) { 1637 elementTemplates.checkMark.className = this.options.iconBase + ' ' + that.options.tickIcon + ' check-mark'; 1638 elementTemplates.a.appendChild(elementTemplates.checkMark); 1639 } 1640 1641 function buildElement (item) { 1642 var liElement, 1643 combinedLength = 0; 1644 1645 switch (item.type) { 1646 case 'divider': 1647 liElement = generateOption.li( 1648 false, 1649 classNames.DIVIDER, 1650 (item.optID ? item.optID + 'div' : undefined) 1651 ); 1652 1653 break; 1654 1655 case 'option': 1656 liElement = generateOption.li( 1657 generateOption.a( 1658 generateOption.text.call(that, item), 1659 item.optionClass, 1660 item.inlineStyle 1661 ), 1662 '', 1663 item.optID 1664 ); 1665 1666 if (liElement.firstChild) { 1667 liElement.firstChild.id = that.selectId + '-' + item.index; 1668 } 1669 1670 break; 1671 1672 case 'optgroup-label': 1673 liElement = generateOption.li( 1674 generateOption.label.call(that, item), 1675 'dropdown-header' + item.optgroupClass, 1676 item.optID 1677 ); 1678 1679 break; 1680 } 1681 1682 item.element = liElement; 1683 mainElements.push(liElement); 1684 1685 // count the number of characters in the option - not perfect, but should work in most cases 1686 if (item.display) combinedLength += item.display.length; 1687 if (item.subtext) combinedLength += item.subtext.length; 1688 // if there is an icon, ensure this option's width is checked 1689 if (item.icon) combinedLength += 1; 1690 1691 if (combinedLength > widestOptionLength) { 1692 widestOptionLength = combinedLength; 1693 1694 // guess which option is the widest 1695 // use this when calculating menu width 1696 // not perfect, but it's fast, and the width will be updating accordingly when scrolling 1697 that.selectpicker.view.widestOption = mainElements[mainElements.length - 1]; 1698 } 1699 } 1700 1701 for (var len = selectData.length, i = 0; i < len; i++) { 1702 var item = selectData[i]; 1703 1704 buildElement(item); 1705 } 1706 1707 this.selectpicker.main.elements = this.selectpicker.current.elements = mainElements; 1708 }, 1709 1710 findLis: function () { 1711 return this.$menuInner.find('.inner > li'); 1712 }, 1713 1714 render: function () { 1715 var that = this, 1716 element = this.$element[0], 1717 // ensure titleOption is appended and selected (if necessary) before getting selectedOptions 1718 placeholderSelected = this.setPlaceholder() && element.selectedIndex === 0, 1719 selectedOptions = getSelectedOptions(element, this.options.hideDisabled), 1720 selectedCount = selectedOptions.length, 1721 button = this.$button[0], 1722 buttonInner = button.querySelector('.filter-option-inner-inner'), 1723 multipleSeparator = document.createTextNode(this.options.multipleSeparator), 1724 titleFragment = elementTemplates.fragment.cloneNode(false), 1725 showCount, 1726 countMax, 1727 hasContent = false; 1728 1729 button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !getSelectValues(element, selectedOptions)); 1730 1731 if (!that.multiple && selectedOptions.length === 1) { 1732 that.selectpicker.view.displayedValue = getSelectValues(element, selectedOptions); 1733 } 1734 1735 if (this.options.selectedTextFormat === 'static') { 1736 titleFragment = generateOption.text.call(this, { text: this.options.title }, true); 1737 } else { 1738 showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 1; 1739 1740 // determine if the number of selected options will be shown (showCount === true) 1741 if (showCount) { 1742 countMax = this.options.selectedTextFormat.split('>'); 1743 showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2); 1744 } 1745 1746 // only loop through all selected options if the count won't be shown 1747 if (showCount === false) { 1748 if (!placeholderSelected) { 1749 for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) { 1750 if (selectedIndex < 50) { 1751 var option = selectedOptions[selectedIndex], 1752 thisData = this.selectpicker.main.data[option.liIndex], 1753 titleOptions = {}; 1754 1755 if (this.multiple && selectedIndex > 0) { 1756 titleFragment.appendChild(multipleSeparator.cloneNode(false)); 1757 } 1758 1759 if (option.title) { 1760 titleOptions.text = option.title; 1761 } else if (thisData) { 1762 if (thisData.content && that.options.showContent) { 1763 titleOptions.content = thisData.content.toString(); 1764 hasContent = true; 1765 } else { 1766 if (that.options.showIcon) { 1767 titleOptions.icon = thisData.icon; 1768 } 1769 if (that.options.showSubtext && !that.multiple && thisData.subtext) titleOptions.subtext = ' ' + thisData.subtext; 1770 titleOptions.text = option.textContent.trim(); 1771 } 1772 } 1773 1774 titleFragment.appendChild(generateOption.text.call(this, titleOptions, true)); 1775 } else { 1776 break; 1777 } 1778 } 1779 1780 // add ellipsis 1781 if (selectedCount > 49) { 1782 titleFragment.appendChild(document.createTextNode('...')); 1783 } 1784 } 1785 } else { 1786 var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])'; 1787 if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; 1788 1789 // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc. 1790 var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length, 1791 tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText; 1792 1793 titleFragment = generateOption.text.call(this, { 1794 text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString()) 1795 }, true); 1796 } 1797 } 1798 1799 if (this.options.title == undefined) { 1800 // use .attr to ensure undefined is returned if title attribute is not set 1801 this.options.title = this.$element.attr('title'); 1802 } 1803 1804 // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText 1805 if (!titleFragment.childNodes.length) { 1806 titleFragment = generateOption.text.call(this, { 1807 text: typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText 1808 }, true); 1809 } 1810 1811 // strip all HTML tags and trim the result, then unescape any escaped tags 1812 button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim(); 1813 1814 if (this.options.sanitize && hasContent) { 1815 sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn); 1816 } 1817 1818 buttonInner.innerHTML = ''; 1819 buttonInner.appendChild(titleFragment); 1820 1821 if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) { 1822 var filterExpand = button.querySelector('.filter-expand'), 1823 clone = buttonInner.cloneNode(true); 1824 1825 clone.className = 'filter-expand'; 1826 1827 if (filterExpand) { 1828 button.replaceChild(clone, filterExpand); 1829 } else { 1830 button.appendChild(clone); 1831 } 1832 } 1833 1834 this.$element.trigger('rendered' + EVENT_KEY); 1835 }, 1836 1837 /** 1838 * @param [style] 1839 * @param [status] 1840 */ 1841 setStyle: function (newStyle, status) { 1842 var button = this.$button[0], 1843 newElement = this.$newElement[0], 1844 style = this.options.style.trim(), 1845 buttonClass; 1846 1847 if (this.$element.attr('class')) { 1848 this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); 1849 } 1850 1851 if (version.major < 4) { 1852 newElement.classList.add('bs3'); 1853 1854 if (newElement.parentNode.classList && newElement.parentNode.classList.contains('input-group') && 1855 (newElement.previousElementSibling || newElement.nextElementSibling) && 1856 (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon') 1857 ) { 1858 newElement.classList.add('bs3-has-addon'); 1859 } 1860 } 1861 1862 if (newStyle) { 1863 buttonClass = newStyle.trim(); 1864 } else { 1865 buttonClass = style; 1866 } 1867 1868 if (status == 'add') { 1869 if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); 1870 } else if (status == 'remove') { 1871 if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' ')); 1872 } else { 1873 if (style) button.classList.remove.apply(button.classList, style.split(' ')); 1874 if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); 1875 } 1876 }, 1877 1878 liHeight: function (refresh) { 1879 if (!refresh && (this.options.size === false || Object.keys(this.sizeInfo).length)) return; 1880 1881 var newElement = elementTemplates.div.cloneNode(false), 1882 menu = elementTemplates.div.cloneNode(false), 1883 menuInner = elementTemplates.div.cloneNode(false), 1884 menuInnerInner = document.createElement('ul'), 1885 divider = elementTemplates.li.cloneNode(false), 1886 dropdownHeader = elementTemplates.li.cloneNode(false), 1887 li, 1888 a = elementTemplates.a.cloneNode(false), 1889 text = elementTemplates.span.cloneNode(false), 1890 header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null, 1891 search = this.options.liveSearch ? elementTemplates.div.cloneNode(false) : null, 1892 actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, 1893 doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null, 1894 firstOption = this.$element.find('option')[0]; 1895 1896 this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth; 1897 1898 text.className = 'text'; 1899 a.className = 'dropdown-item ' + (firstOption ? firstOption.className : ''); 1900 newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW; 1901 newElement.style.width = 0; // ensure button width doesn't affect natural width of menu when calculating 1902 if (this.options.width === 'auto') menu.style.minWidth = 0; 1903 menu.className = classNames.MENU + ' ' + classNames.SHOW; 1904 menuInner.className = 'inner ' + classNames.SHOW; 1905 menuInnerInner.className = classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : ''); 1906 divider.className = classNames.DIVIDER; 1907 dropdownHeader.className = 'dropdown-header'; 1908 1909 text.appendChild(document.createTextNode('\u200b')); 1910 1911 if (this.selectpicker.current.data.length) { 1912 for (var i = 0; i < this.selectpicker.current.data.length; i++) { 1913 var data = this.selectpicker.current.data[i]; 1914 if (data.type === 'option') { 1915 li = data.element; 1916 break; 1917 } 1918 } 1919 } else { 1920 li = elementTemplates.li.cloneNode(false); 1921 a.appendChild(text); 1922 li.appendChild(a); 1923 } 1924 1925 dropdownHeader.appendChild(text.cloneNode(true)); 1926 1927 if (this.selectpicker.view.widestOption) { 1928 menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true)); 1929 } 1930 1931 menuInnerInner.appendChild(li); 1932 menuInnerInner.appendChild(divider); 1933 menuInnerInner.appendChild(dropdownHeader); 1934 if (header) menu.appendChild(header); 1935 if (search) { 1936 var input = document.createElement('input'); 1937 search.className = 'bs-searchbox'; 1938 input.className = 'form-control'; 1939 search.appendChild(input); 1940 menu.appendChild(search); 1941 } 1942 if (actions) menu.appendChild(actions); 1943 menuInner.appendChild(menuInnerInner); 1944 menu.appendChild(menuInner); 1945 if (doneButton) menu.appendChild(doneButton); 1946 newElement.appendChild(menu); 1947 1948 document.body.appendChild(newElement); 1949 1950 var liHeight = li.offsetHeight, 1951 dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0, 1952 headerHeight = header ? header.offsetHeight : 0, 1953 searchHeight = search ? search.offsetHeight : 0, 1954 actionsHeight = actions ? actions.offsetHeight : 0, 1955 doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, 1956 dividerHeight = $(divider).outerHeight(true), 1957 // fall back to jQuery if getComputedStyle is not supported 1958 menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false, 1959 menuWidth = menu.offsetWidth, 1960 $menu = menuStyle ? null : $(menu), 1961 menuPadding = { 1962 vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + 1963 toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + 1964 toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + 1965 toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), 1966 horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + 1967 toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + 1968 toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + 1969 toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) 1970 }, 1971 menuExtras = { 1972 vert: menuPadding.vert + 1973 toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + 1974 toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, 1975 horiz: menuPadding.horiz + 1976 toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + 1977 toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 1978 }, 1979 scrollBarWidth; 1980 1981 menuInner.style.overflowY = 'scroll'; 1982 1983 scrollBarWidth = menu.offsetWidth - menuWidth; 1984 1985 document.body.removeChild(newElement); 1986 1987 this.sizeInfo.liHeight = liHeight; 1988 this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight; 1989 this.sizeInfo.headerHeight = headerHeight; 1990 this.sizeInfo.searchHeight = searchHeight; 1991 this.sizeInfo.actionsHeight = actionsHeight; 1992 this.sizeInfo.doneButtonHeight = doneButtonHeight; 1993 this.sizeInfo.dividerHeight = dividerHeight; 1994 this.sizeInfo.menuPadding = menuPadding; 1995 this.sizeInfo.menuExtras = menuExtras; 1996 this.sizeInfo.menuWidth = menuWidth; 1997 this.sizeInfo.menuInnerInnerWidth = menuWidth - menuPadding.horiz; 1998 this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth; 1999 this.sizeInfo.scrollBarWidth = scrollBarWidth; 2000 this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight; 2001 2002 this.setPositionData(); 2003 }, 2004 2005 getSelectPosition: function () { 2006 var that = this, 2007 $window = $(window), 2008 pos = that.$newElement.offset(), 2009 $container = $(that.options.container), 2010 containerPos; 2011 2012 if (that.options.container && $container.length && !$container.is('body')) { 2013 containerPos = $container.offset(); 2014 containerPos.top += parseInt($container.css('borderTopWidth')); 2015 containerPos.left += parseInt($container.css('borderLeftWidth')); 2016 } else { 2017 containerPos = { top: 0, left: 0 }; 2018 } 2019 2020 var winPad = that.options.windowPadding; 2021 2022 this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); 2023 this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2]; 2024 this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); 2025 this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1]; 2026 this.sizeInfo.selectOffsetTop -= winPad[0]; 2027 this.sizeInfo.selectOffsetLeft -= winPad[3]; 2028 }, 2029 2030 setMenuSize: function (isAuto) { 2031 this.getSelectPosition(); 2032 2033 var selectWidth = this.sizeInfo.selectWidth, 2034 liHeight = this.sizeInfo.liHeight, 2035 headerHeight = this.sizeInfo.headerHeight, 2036 searchHeight = this.sizeInfo.searchHeight, 2037 actionsHeight = this.sizeInfo.actionsHeight, 2038 doneButtonHeight = this.sizeInfo.doneButtonHeight, 2039 divHeight = this.sizeInfo.dividerHeight, 2040 menuPadding = this.sizeInfo.menuPadding, 2041 menuInnerHeight, 2042 menuHeight, 2043 divLength = 0, 2044 minHeight, 2045 _minHeight, 2046 maxHeight, 2047 menuInnerMinHeight, 2048 estimate, 2049 isDropup; 2050 2051 if (this.options.dropupAuto) { 2052 // Get the estimated height of the menu without scrollbars. 2053 // This is useful for smaller menus, where there might be plenty of room 2054 // below the button without setting dropup, but we can't know 2055 // the exact height of the menu until createView is called later 2056 estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert; 2057 2058 isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot; 2059 2060 // ensure dropup doesn't change while searching (so menu doesn't bounce back and forth) 2061 if (this.selectpicker.isSearching === true) { 2062 isDropup = this.selectpicker.dropup; 2063 } 2064 2065 this.$newElement.toggleClass(classNames.DROPUP, isDropup); 2066 this.selectpicker.dropup = isDropup; 2067 } 2068 2069 if (this.options.size === 'auto') { 2070 _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0; 2071 menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert; 2072 minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; 2073 menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0); 2074 2075 if (this.$newElement.hasClass(classNames.DROPUP)) { 2076 menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert; 2077 } 2078 2079 maxHeight = menuHeight; 2080 menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert; 2081 } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { 2082 for (var i = 0; i < this.options.size; i++) { 2083 if (this.selectpicker.current.data[i].type === 'divider') divLength++; 2084 } 2085 2086 menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; 2087 menuInnerHeight = menuHeight - menuPadding.vert; 2088 maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; 2089 minHeight = menuInnerMinHeight = ''; 2090 } 2091 2092 this.$menu.css({ 2093 'max-height': maxHeight + 'px', 2094 'overflow': 'hidden', 2095 'min-height': minHeight + 'px' 2096 }); 2097 2098 this.$menuInner.css({ 2099 'max-height': menuInnerHeight + 'px', 2100 'overflow-y': 'auto', 2101 'min-height': menuInnerMinHeight + 'px' 2102 }); 2103 2104 // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView 2105 this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1); 2106 2107 if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) { 2108 this.sizeInfo.hasScrollBar = true; 2109 this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth; 2110 } 2111 2112 if (this.options.dropdownAlignRight === 'auto') { 2113 this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth)); 2114 } 2115 2116 if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update(); 2117 }, 2118 2119 setSize: function (refresh) { 2120 this.liHeight(refresh); 2121 2122 if (this.options.header) this.$menu.css('padding-top', 0); 2123 2124 if (this.options.size !== false) { 2125 var that = this, 2126 $window = $(window); 2127 2128 this.setMenuSize(); 2129 2130 if (this.options.liveSearch) { 2131 this.$searchbox 2132 .off('input.setMenuSize propertychange.setMenuSize') 2133 .on('input.setMenuSize propertychange.setMenuSize', function () { 2134 return that.setMenuSize(); 2135 }); 2136 } 2137 2138 if (this.options.size === 'auto') { 2139 $window 2140 .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize') 2141 .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () { 2142 return that.setMenuSize(); 2143 }); 2144 } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { 2145 $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize'); 2146 } 2147 } 2148 2149 this.createView(false, true, refresh); 2150 }, 2151 2152 setWidth: function () { 2153 var that = this; 2154 2155 if (this.options.width === 'auto') { 2156 requestAnimationFrame(function () { 2157 that.$menu.css('min-width', '0'); 2158 2159 that.$element.on('loaded' + EVENT_KEY, function () { 2160 that.liHeight(); 2161 that.setMenuSize(); 2162 2163 // Get correct width if element is hidden 2164 var $selectClone = that.$newElement.clone().appendTo('body'), 2165 btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth(); 2166 2167 $selectClone.remove(); 2168 2169 // Set width to whatever's larger, button title or longest option 2170 that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth); 2171 that.$newElement.css('width', that.sizeInfo.selectWidth + 'px'); 2172 }); 2173 }); 2174 } else if (this.options.width === 'fit') { 2175 // Remove inline min-width so width can be changed from 'auto' 2176 this.$menu.css('min-width', ''); 2177 this.$newElement.css('width', '').addClass('fit-width'); 2178 } else if (this.options.width) { 2179 // Remove inline min-width so width can be changed from 'auto' 2180 this.$menu.css('min-width', ''); 2181 this.$newElement.css('width', this.options.width); 2182 } else { 2183 // Remove inline min-width/width so width can be changed 2184 this.$menu.css('min-width', ''); 2185 this.$newElement.css('width', ''); 2186 } 2187 // Remove fit-width class if width is changed programmatically 2188 if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { 2189 this.$newElement[0].classList.remove('fit-width'); 2190 } 2191 }, 2192 2193 selectPosition: function () { 2194 this.$bsContainer = $('<div class="bs-container" />'); 2195 2196 var that = this, 2197 $container = $(this.options.container), 2198 pos, 2199 containerPos, 2200 actualHeight, 2201 getPlacement = function ($element) { 2202 var containerPosition = {}, 2203 // fall back to dropdown's default display setting if display is not manually set 2204 display = that.options.display || ( 2205 // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default 2206 $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display 2207 : false 2208 ); 2209 2210 that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP)); 2211 pos = $element.offset(); 2212 2213 if (!$container.is('body')) { 2214 containerPos = $container.offset(); 2215 containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); 2216 containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); 2217 } else { 2218 containerPos = { top: 0, left: 0 }; 2219 } 2220 2221 actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight; 2222 2223 // Bootstrap 4+ uses Popper for menu positioning 2224 if (version.major < 4 || display === 'static') { 2225 containerPosition.top = pos.top - containerPos.top + actualHeight; 2226 containerPosition.left = pos.left - containerPos.left; 2227 } 2228 2229 containerPosition.width = $element[0].offsetWidth; 2230 2231 that.$bsContainer.css(containerPosition); 2232 }; 2233 2234 this.$button.on('click.bs.dropdown.data-api', function () { 2235 if (that.isDisabled()) { 2236 return; 2237 } 2238 2239 getPlacement(that.$newElement); 2240 2241 that.$bsContainer 2242 .appendTo(that.options.container) 2243 .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW)) 2244 .append(that.$menu); 2245 }); 2246 2247 $(window) 2248 .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId) 2249 .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () { 2250 var isActive = that.$newElement.hasClass(classNames.SHOW); 2251 2252 if (isActive) getPlacement(that.$newElement); 2253 }); 2254 2255 this.$element.on('hide' + EVENT_KEY, function () { 2256 that.$menu.data('height', that.$menu.height()); 2257 that.$bsContainer.detach(); 2258 }); 2259 }, 2260 2261 setOptionStatus: function (selectedOnly) { 2262 var that = this; 2263 2264 that.noScroll = false; 2265 2266 if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) { 2267 for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) { 2268 var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0], 2269 option = liData.option; 2270 2271 if (option) { 2272 if (selectedOnly !== true) { 2273 that.setDisabled( 2274 liData.index, 2275 liData.disabled 2276 ); 2277 } 2278 2279 that.setSelected( 2280 liData.index, 2281 option.selected 2282 ); 2283 } 2284 } 2285 } 2286 }, 2287 2288 /** 2289 * @param {number} index - the index of the option that is being changed 2290 * @param {boolean} selected - true if the option is being selected, false if being deselected 2291 */ 2292 setSelected: function (index, selected) { 2293 var li = this.selectpicker.main.elements[index], 2294 liData = this.selectpicker.main.data[index], 2295 activeIndexIsSet = this.activeIndex !== undefined, 2296 thisIsActive = this.activeIndex === index, 2297 prevActive, 2298 a, 2299 // if current option is already active 2300 // OR 2301 // if the current option is being selected, it's NOT multiple, and 2302 // activeIndex is undefined: 2303 // - when the menu is first being opened, OR 2304 // - after a search has been performed, OR 2305 // - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeIndex) 2306 keepActive = thisIsActive || (selected && !this.multiple && !activeIndexIsSet); 2307 2308 liData.selected = selected; 2309 2310 a = li.firstChild; 2311 2312 if (selected) { 2313 this.selectedIndex = index; 2314 } 2315 2316 li.classList.toggle('selected', selected); 2317 2318 if (keepActive) { 2319 this.focusItem(li, liData); 2320 this.selectpicker.view.currentActive = li; 2321 this.activeIndex = index; 2322 } else { 2323 this.defocusItem(li); 2324 } 2325 2326 if (a) { 2327 a.classList.toggle('selected', selected); 2328 2329 if (selected) { 2330 a.setAttribute('aria-selected', true); 2331 } else { 2332 if (this.multiple) { 2333 a.setAttribute('aria-selected', false); 2334 } else { 2335 a.removeAttribute('aria-selected'); 2336 } 2337 } 2338 } 2339 2340 if (!keepActive && !activeIndexIsSet && selected && this.prevActiveIndex !== undefined) { 2341 prevActive = this.selectpicker.main.elements[this.prevActiveIndex]; 2342 2343 this.defocusItem(prevActive); 2344 } 2345 }, 2346 2347 /** 2348 * @param {number} index - the index of the option that is being disabled 2349 * @param {boolean} disabled - true if the option is being disabled, false if being enabled 2350 */ 2351 setDisabled: function (index, disabled) { 2352 var li = this.selectpicker.main.elements[index], 2353 a; 2354 2355 this.selectpicker.main.data[index].disabled = disabled; 2356 2357 a = li.firstChild; 2358 2359 li.classList.toggle(classNames.DISABLED, disabled); 2360 2361 if (a) { 2362 if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled); 2363 2364 if (disabled) { 2365 a.setAttribute('aria-disabled', disabled); 2366 a.setAttribute('tabindex', -1); 2367 } else { 2368 a.removeAttribute('aria-disabled'); 2369 a.setAttribute('tabindex', 0); 2370 } 2371 } 2372 }, 2373 2374 isDisabled: function () { 2375 return this.$element[0].disabled; 2376 }, 2377 2378 checkDisabled: function () { 2379 if (this.isDisabled()) { 2380 this.$newElement[0].classList.add(classNames.DISABLED); 2381 this.$button.addClass(classNames.DISABLED).attr('aria-disabled', true); 2382 } else { 2383 if (this.$button[0].classList.contains(classNames.DISABLED)) { 2384 this.$newElement[0].classList.remove(classNames.DISABLED); 2385 this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false); 2386 } 2387 } 2388 }, 2389 2390 clickListener: function () { 2391 var that = this, 2392 $document = $(document); 2393 2394 $document.data('spaceSelect', false); 2395 2396 this.$button.on('keyup', function (e) { 2397 if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { 2398 e.preventDefault(); 2399 $document.data('spaceSelect', false); 2400 } 2401 }); 2402 2403 this.$newElement.on('show.bs.dropdown', function () { 2404 if (version.major > 3 && !that.dropdown) { 2405 that.dropdown = that.$button.data('bs.dropdown'); 2406 that.dropdown._menu = that.$menu[0]; 2407 } 2408 }); 2409 2410 this.$button.on('click.bs.dropdown.data-api', function () { 2411 if (!that.$newElement.hasClass(classNames.SHOW)) { 2412 that.setSize(); 2413 } 2414 }); 2415 2416 function setFocus () { 2417 if (that.options.liveSearch) { 2418 that.$searchbox.trigger('focus'); 2419 } else { 2420 that.$menuInner.trigger('focus'); 2421 } 2422 } 2423 2424 function checkPopperExists () { 2425 if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state.isCreated) { 2426 setFocus(); 2427 } else { 2428 requestAnimationFrame(checkPopperExists); 2429 } 2430 } 2431 2432 this.$element.on('shown' + EVENT_KEY, function () { 2433 if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) { 2434 that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop; 2435 } 2436 2437 if (version.major > 3) { 2438 requestAnimationFrame(checkPopperExists); 2439 } else { 2440 setFocus(); 2441 } 2442 }); 2443 2444 // ensure posinset and setsize are correct before selecting an option via a click 2445 this.$menuInner.on('mouseenter', 'li a', function (e) { 2446 var hoverLi = this.parentElement, 2447 position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, 2448 index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi), 2449 hoverData = that.selectpicker.current.data[index + position0]; 2450 2451 that.focusItem(hoverLi, hoverData, true); 2452 }); 2453 2454 this.$menuInner.on('click', 'li a', function (e, retainActive) { 2455 var $this = $(this), 2456 element = that.$element[0], 2457 position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, 2458 clickedData = that.selectpicker.current.data[$this.parent().index() + position0], 2459 clickedIndex = clickedData.index, 2460 prevValue = getSelectValues(element), 2461 prevIndex = element.selectedIndex, 2462 prevOption = element.options[prevIndex], 2463 triggerChange = true; 2464 2465 // Don't close on multi choice menu 2466 if (that.multiple && that.options.maxOptions !== 1) { 2467 e.stopPropagation(); 2468 } 2469 2470 e.preventDefault(); 2471 2472 // Don't run if the select is disabled 2473 if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) { 2474 var option = clickedData.option, 2475 $option = $(option), 2476 state = option.selected, 2477 $optgroup = $option.parent('optgroup'), 2478 $optgroupOptions = $optgroup.find('option'), 2479 maxOptions = that.options.maxOptions, 2480 maxOptionsGrp = $optgroup.data('maxOptions') || false; 2481 2482 if (clickedIndex === that.activeIndex) retainActive = true; 2483 2484 if (!retainActive) { 2485 that.prevActiveIndex = that.activeIndex; 2486 that.activeIndex = undefined; 2487 } 2488 2489 if (!that.multiple) { // Deselect all others if not multi select box 2490 if (prevOption) prevOption.selected = false; 2491 option.selected = true; 2492 that.setSelected(clickedIndex, true); 2493 } else { // Toggle the one we have chosen if we are multi select. 2494 option.selected = !state; 2495 2496 that.setSelected(clickedIndex, !state); 2497 that.focusedParent.focus(); 2498 2499 if (maxOptions !== false || maxOptionsGrp !== false) { 2500 var maxReached = maxOptions < getSelectedOptions(element).length, 2501 maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; 2502 2503 if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { 2504 if (maxOptions && maxOptions == 1) { 2505 element.selectedIndex = -1; 2506 option.selected = true; 2507 that.setOptionStatus(true); 2508 } else if (maxOptionsGrp && maxOptionsGrp == 1) { 2509 for (var i = 0; i < $optgroupOptions.length; i++) { 2510 var _option = $optgroupOptions[i]; 2511 _option.selected = false; 2512 that.setSelected(_option.liIndex, false); 2513 } 2514 2515 option.selected = true; 2516 that.setSelected(clickedIndex, true); 2517 } else { 2518 var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, 2519 maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, 2520 maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), 2521 maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), 2522 $notify = $('<div class="notify"></div>'); 2523 // If {var} is set in array, replace it 2524 /** @deprecated */ 2525 if (maxOptionsArr[2]) { 2526 maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); 2527 maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); 2528 } 2529 2530 option.selected = false; 2531 2532 that.$menu.append($notify); 2533 2534 if (maxOptions && maxReached) { 2535 $notify.append($('<div>' + maxTxt + '</div>')); 2536 triggerChange = false; 2537 that.$element.trigger('maxReached' + EVENT_KEY); 2538 } 2539 2540 if (maxOptionsGrp && maxReachedGrp) { 2541 $notify.append($('<div>' + maxTxtGrp + '</div>')); 2542 triggerChange = false; 2543 that.$element.trigger('maxReachedGrp' + EVENT_KEY); 2544 } 2545 2546 setTimeout(function () { 2547 that.setSelected(clickedIndex, false); 2548 }, 10); 2549 2550 $notify[0].classList.add('fadeOut'); 2551 2552 setTimeout(function () { 2553 $notify.remove(); 2554 }, 1050); 2555 } 2556 } 2557 } 2558 } 2559 2560 if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { 2561 that.$button.trigger('focus'); 2562 } else if (that.options.liveSearch) { 2563 that.$searchbox.trigger('focus'); 2564 } 2565 2566 // Trigger select 'change' 2567 if (triggerChange) { 2568 if (that.multiple || prevIndex !== element.selectedIndex) { 2569 // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed. 2570 changedArguments = [option.index, $option.prop('selected'), prevValue]; 2571 that.$element 2572 .triggerNative('change'); 2573 } 2574 } 2575 } 2576 }); 2577 2578 this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) { 2579 if (e.currentTarget == this) { 2580 e.preventDefault(); 2581 e.stopPropagation(); 2582 if (that.options.liveSearch && !$(e.target).hasClass('close')) { 2583 that.$searchbox.trigger('focus'); 2584 } else { 2585 that.$button.trigger('focus'); 2586 } 2587 } 2588 }); 2589 2590 this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { 2591 e.preventDefault(); 2592 e.stopPropagation(); 2593 if (that.options.liveSearch) { 2594 that.$searchbox.trigger('focus'); 2595 } else { 2596 that.$button.trigger('focus'); 2597 } 2598 }); 2599 2600 this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () { 2601 that.$button.trigger('click'); 2602 }); 2603 2604 this.$searchbox.on('click', function (e) { 2605 e.stopPropagation(); 2606 }); 2607 2608 this.$menu.on('click', '.actions-btn', function (e) { 2609 if (that.options.liveSearch) { 2610 that.$searchbox.trigger('focus'); 2611 } else { 2612 that.$button.trigger('focus'); 2613 } 2614 2615 e.preventDefault(); 2616 e.stopPropagation(); 2617 2618 if ($(this).hasClass('bs-select-all')) { 2619 that.selectAll(); 2620 } else { 2621 that.deselectAll(); 2622 } 2623 }); 2624 2625 this.$button 2626 .on('focus' + EVENT_KEY, function (e) { 2627 var tabindex = that.$element[0].getAttribute('tabindex'); 2628 2629 // only change when button is actually focused 2630 if (tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { 2631 // apply select element's tabindex to ensure correct order is followed when tabbing to the next element 2632 this.setAttribute('tabindex', tabindex); 2633 // set element's tabindex to -1 to allow for reverse tabbing 2634 that.$element[0].setAttribute('tabindex', -1); 2635 that.selectpicker.view.tabindex = tabindex; 2636 } 2637 }) 2638 .on('blur' + EVENT_KEY, function (e) { 2639 // revert everything to original tabindex 2640 if (that.selectpicker.view.tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { 2641 that.$element[0].setAttribute('tabindex', that.selectpicker.view.tabindex); 2642 this.setAttribute('tabindex', -1); 2643 that.selectpicker.view.tabindex = undefined; 2644 } 2645 }); 2646 2647 this.$element 2648 .on('change' + EVENT_KEY, function () { 2649 that.render(); 2650 that.$element.trigger('changed' + EVENT_KEY, changedArguments); 2651 changedArguments = null; 2652 }) 2653 .on('focus' + EVENT_KEY, function () { 2654 if (!that.options.mobile) that.$button[0].focus(); 2655 }); 2656 }, 2657 2658 liveSearchListener: function () { 2659 var that = this; 2660 2661 this.$button.on('click.bs.dropdown.data-api', function () { 2662 if (!!that.$searchbox.val()) { 2663 that.$searchbox.val(''); 2664 that.selectpicker.search.previousValue = undefined; 2665 } 2666 }); 2667 2668 this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) { 2669 e.stopPropagation(); 2670 }); 2671 2672 this.$searchbox.on('input propertychange', function () { 2673 var searchValue = that.$searchbox[0].value; 2674 2675 that.selectpicker.search.elements = []; 2676 that.selectpicker.search.data = []; 2677 2678 if (searchValue) { 2679 var i, 2680 searchMatch = [], 2681 q = searchValue.toUpperCase(), 2682 cache = {}, 2683 cacheArr = [], 2684 searchStyle = that._searchStyle(), 2685 normalizeSearch = that.options.liveSearchNormalize; 2686 2687 if (normalizeSearch) q = normalizeToBase(q); 2688 2689 for (var i = 0; i < that.selectpicker.main.data.length; i++) { 2690 var li = that.selectpicker.main.data[i]; 2691 2692 if (!cache[i]) { 2693 cache[i] = stringSearch(li, q, searchStyle, normalizeSearch); 2694 } 2695 2696 if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) { 2697 if (li.headerIndex > 0) { 2698 cache[li.headerIndex - 1] = true; 2699 cacheArr.push(li.headerIndex - 1); 2700 } 2701 2702 cache[li.headerIndex] = true; 2703 cacheArr.push(li.headerIndex); 2704 2705 cache[li.lastIndex + 1] = true; 2706 } 2707 2708 if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i); 2709 } 2710 2711 for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) { 2712 var index = cacheArr[i], 2713 prevIndex = cacheArr[i - 1], 2714 li = that.selectpicker.main.data[index], 2715 liPrev = that.selectpicker.main.data[prevIndex]; 2716 2717 if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) { 2718 that.selectpicker.search.data.push(li); 2719 searchMatch.push(that.selectpicker.main.elements[index]); 2720 } 2721 } 2722 2723 that.activeIndex = undefined; 2724 that.noScroll = true; 2725 that.$menuInner.scrollTop(0); 2726 that.selectpicker.search.elements = searchMatch; 2727 that.createView(true); 2728 showNoResults.call(that, searchMatch, searchValue); 2729 } else if (that.selectpicker.search.previousValue) { // for IE11 (#2402) 2730 that.$menuInner.scrollTop(0); 2731 that.createView(false); 2732 } 2733 2734 that.selectpicker.search.previousValue = searchValue; 2735 }); 2736 }, 2737 2738 _searchStyle: function () { 2739 return this.options.liveSearchStyle || 'contains'; 2740 }, 2741 2742 val: function (value) { 2743 var element = this.$element[0]; 2744 2745 if (typeof value !== 'undefined') { 2746 var prevValue = getSelectValues(element); 2747 2748 changedArguments = [null, null, prevValue]; 2749 2750 this.$element 2751 .val(value) 2752 .trigger('changed' + EVENT_KEY, changedArguments); 2753 2754 if (this.$newElement.hasClass(classNames.SHOW)) { 2755 if (this.multiple) { 2756 this.setOptionStatus(true); 2757 } else { 2758 var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex; 2759 2760 if (typeof liSelectedIndex === 'number') { 2761 this.setSelected(this.selectedIndex, false); 2762 this.setSelected(liSelectedIndex, true); 2763 } 2764 } 2765 } 2766 2767 this.render(); 2768 2769 changedArguments = null; 2770 2771 return this.$element; 2772 } else { 2773 return this.$element.val(); 2774 } 2775 }, 2776 2777 changeAll: function (status) { 2778 if (!this.multiple) return; 2779 if (typeof status === 'undefined') status = true; 2780 2781 var element = this.$element[0], 2782 previousSelected = 0, 2783 currentSelected = 0, 2784 prevValue = getSelectValues(element); 2785 2786 element.classList.add('bs-select-hidden'); 2787 2788 for (var i = 0, data = this.selectpicker.current.data, len = data.length; i < len; i++) { 2789 var liData = data[i], 2790 option = liData.option; 2791 2792 if (option && !liData.disabled && liData.type !== 'divider') { 2793 if (liData.selected) previousSelected++; 2794 option.selected = status; 2795 if (status === true) currentSelected++; 2796 } 2797 } 2798 2799 element.classList.remove('bs-select-hidden'); 2800 2801 if (previousSelected === currentSelected) return; 2802 2803 this.setOptionStatus(); 2804 2805 changedArguments = [null, null, prevValue]; 2806 2807 this.$element 2808 .triggerNative('change'); 2809 }, 2810 2811 selectAll: function () { 2812 return this.changeAll(true); 2813 }, 2814 2815 deselectAll: function () { 2816 return this.changeAll(false); 2817 }, 2818 2819 toggle: function (e) { 2820 e = e || window.event; 2821 2822 if (e) e.stopPropagation(); 2823 2824 this.$button.trigger('click.bs.dropdown.data-api'); 2825 }, 2826 2827 keydown: function (e) { 2828 var $this = $(this), 2829 isToggle = $this.hasClass('dropdown-toggle'), 2830 $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU), 2831 that = $parent.data('this'), 2832 $items = that.findLis(), 2833 index, 2834 isActive, 2835 liActive, 2836 activeLi, 2837 offset, 2838 updateScroll = false, 2839 downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab, 2840 isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab, 2841 scrollTop = that.$menuInner[0].scrollTop, 2842 isVirtual = that.isVirtual(), 2843 position0 = isVirtual === true ? that.selectpicker.view.position0 : 0; 2844 2845 // do nothing if a function key is pressed 2846 if (e.which >= 112 && e.which <= 123) return; 2847 2848 isActive = that.$newElement.hasClass(classNames.SHOW); 2849 2850 if ( 2851 !isActive && 2852 ( 2853 isArrowKey || 2854 (e.which >= 48 && e.which <= 57) || 2855 (e.which >= 96 && e.which <= 105) || 2856 (e.which >= 65 && e.which <= 90) 2857 ) 2858 ) { 2859 that.$button.trigger('click.bs.dropdown.data-api'); 2860 2861 if (that.options.liveSearch) { 2862 that.$searchbox.trigger('focus'); 2863 return; 2864 } 2865 } 2866 2867 if (e.which === keyCodes.ESCAPE && isActive) { 2868 e.preventDefault(); 2869 that.$button.trigger('click.bs.dropdown.data-api').trigger('focus'); 2870 } 2871 2872 if (isArrowKey) { // if up or down 2873 if (!$items.length) return; 2874 2875 liActive = that.selectpicker.main.elements[that.activeIndex]; 2876 index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1; 2877 2878 if (index !== -1) { 2879 that.defocusItem(liActive); 2880 } 2881 2882 if (e.which === keyCodes.ARROW_UP) { // up 2883 if (index !== -1) index--; 2884 if (index + position0 < 0) index += $items.length; 2885 2886 if (!that.selectpicker.view.canHighlight[index + position0]) { 2887 index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0; 2888 if (index === -1) index = $items.length - 1; 2889 } 2890 } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down 2891 index++; 2892 if (index + position0 >= that.selectpicker.view.canHighlight.length) index = that.selectpicker.view.firstHighlightIndex; 2893 2894 if (!that.selectpicker.view.canHighlight[index + position0]) { 2895 index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true); 2896 } 2897 } 2898 2899 e.preventDefault(); 2900 2901 var liActiveIndex = position0 + index; 2902 2903 if (e.which === keyCodes.ARROW_UP) { // up 2904 // scroll to bottom and highlight last option 2905 if (position0 === 0 && index === $items.length - 1) { 2906 that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight; 2907 2908 liActiveIndex = that.selectpicker.current.elements.length - 1; 2909 } else { 2910 activeLi = that.selectpicker.current.data[liActiveIndex]; 2911 offset = activeLi.position - activeLi.height; 2912 2913 updateScroll = offset < scrollTop; 2914 } 2915 } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down 2916 // scroll to top and highlight first option 2917 if (index === that.selectpicker.view.firstHighlightIndex) { 2918 that.$menuInner[0].scrollTop = 0; 2919 2920 liActiveIndex = that.selectpicker.view.firstHighlightIndex; 2921 } else { 2922 activeLi = that.selectpicker.current.data[liActiveIndex]; 2923 offset = activeLi.position - that.sizeInfo.menuInnerHeight; 2924 2925 updateScroll = offset > scrollTop; 2926 } 2927 } 2928 2929 liActive = that.selectpicker.current.elements[liActiveIndex]; 2930 2931 that.activeIndex = that.selectpicker.current.data[liActiveIndex].index; 2932 2933 that.focusItem(liActive); 2934 2935 that.selectpicker.view.currentActive = liActive; 2936 2937 if (updateScroll) that.$menuInner[0].scrollTop = offset; 2938 2939 if (that.options.liveSearch) { 2940 that.$searchbox.trigger('focus'); 2941 } else { 2942 $this.trigger('focus'); 2943 } 2944 } else if ( 2945 (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) || 2946 (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory) 2947 ) { 2948 var searchMatch, 2949 matches = [], 2950 keyHistory; 2951 2952 e.preventDefault(); 2953 2954 that.selectpicker.keydown.keyHistory += keyCodeMap[e.which]; 2955 2956 if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel); 2957 that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start(); 2958 2959 keyHistory = that.selectpicker.keydown.keyHistory; 2960 2961 // if all letters are the same, set keyHistory to just the first character when searching 2962 if (/^(.)\1+$/.test(keyHistory)) { 2963 keyHistory = keyHistory.charAt(0); 2964 } 2965 2966 // find matches 2967 for (var i = 0; i < that.selectpicker.current.data.length; i++) { 2968 var li = that.selectpicker.current.data[i], 2969 hasMatch; 2970 2971 hasMatch = stringSearch(li, keyHistory, 'startsWith', true); 2972 2973 if (hasMatch && that.selectpicker.view.canHighlight[i]) { 2974 matches.push(li.index); 2975 } 2976 } 2977 2978 if (matches.length) { 2979 var matchIndex = 0; 2980 2981 $items.removeClass('active').find('a').removeClass('active'); 2982 2983 // either only one key has been pressed or they are all the same key 2984 if (keyHistory.length === 1) { 2985 matchIndex = matches.indexOf(that.activeIndex); 2986 2987 if (matchIndex === -1 || matchIndex === matches.length - 1) { 2988 matchIndex = 0; 2989 } else { 2990 matchIndex++; 2991 } 2992 } 2993 2994 searchMatch = matches[matchIndex]; 2995 2996 activeLi = that.selectpicker.main.data[searchMatch]; 2997 2998 if (scrollTop - activeLi.position > 0) { 2999 offset = activeLi.position - activeLi.height; 3000 updateScroll = true; 3001 } else { 3002 offset = activeLi.position - that.sizeInfo.menuInnerHeight; 3003 // if the option is already visible at the current scroll position, just keep it the same 3004 updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight; 3005 } 3006 3007 liActive = that.selectpicker.main.elements[searchMatch]; 3008 3009 that.activeIndex = matches[matchIndex]; 3010 3011 that.focusItem(liActive); 3012 3013 if (liActive) liActive.firstChild.focus(); 3014 3015 if (updateScroll) that.$menuInner[0].scrollTop = offset; 3016 3017 $this.trigger('focus'); 3018 } 3019 } 3020 3021 // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. 3022 if ( 3023 isActive && 3024 ( 3025 (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) || 3026 e.which === keyCodes.ENTER || 3027 (e.which === keyCodes.TAB && that.options.selectOnTab) 3028 ) 3029 ) { 3030 if (e.which !== keyCodes.SPACE) e.preventDefault(); 3031 3032 if (!that.options.liveSearch || e.which !== keyCodes.SPACE) { 3033 that.$menuInner.find('.active a').trigger('click', true); // retain active class 3034 $this.trigger('focus'); 3035 3036 if (!that.options.liveSearch) { 3037 // Prevent screen from scrolling if the user hits the spacebar 3038 e.preventDefault(); 3039 // Fixes spacebar selection of dropdown items in FF & IE 3040 $(document).data('spaceSelect', true); 3041 } 3042 } 3043 } 3044 }, 3045 3046 mobile: function () { 3047 // ensure mobile is set to true if mobile function is called after init 3048 this.options.mobile = true; 3049 this.$element[0].classList.add('mobile-device'); 3050 }, 3051 3052 refresh: function () { 3053 // update options if data attributes have been changed 3054 var config = $.extend({}, this.options, this.$element.data()); 3055 this.options = config; 3056 3057 this.checkDisabled(); 3058 this.buildData(); 3059 this.setStyle(); 3060 this.render(); 3061 this.buildList(); 3062 this.setWidth(); 3063 3064 this.setSize(true); 3065 3066 this.$element.trigger('refreshed' + EVENT_KEY); 3067 }, 3068 3069 hide: function () { 3070 this.$newElement.hide(); 3071 }, 3072 3073 show: function () { 3074 this.$newElement.show(); 3075 }, 3076 3077 remove: function () { 3078 this.$newElement.remove(); 3079 this.$element.remove(); 3080 }, 3081 3082 destroy: function () { 3083 this.$newElement.before(this.$element).remove(); 3084 3085 if (this.$bsContainer) { 3086 this.$bsContainer.remove(); 3087 } else { 3088 this.$menu.remove(); 3089 } 3090 3091 if (this.selectpicker.view.titleOption && this.selectpicker.view.titleOption.parentNode) { 3092 this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption); 3093 } 3094 3095 this.$element 3096 .off(EVENT_KEY) 3097 .removeData('selectpicker') 3098 .removeClass('bs-select-hidden selectpicker'); 3099 3100 $(window).off(EVENT_KEY + '.' + this.selectId); 3101 } 3102 }; 3103 3104 // SELECTPICKER PLUGIN DEFINITION 3105 // ============================== 3106 function Plugin (option) { 3107 // get the args of the outer function.. 3108 var args = arguments; 3109 // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them 3110 // to get lost/corrupted in android 2.3 and IE9 #715 #775 3111 var _option = option; 3112 3113 [].shift.apply(args); 3114 3115 // if the version was not set successfully 3116 if (!version.success) { 3117 // try to retreive it again 3118 try { 3119 version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); 3120 } catch (err) { 3121 // fall back to use BootstrapVersion if set 3122 if (Selectpicker.BootstrapVersion) { 3123 version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.'); 3124 } else { 3125 version.full = [version.major, '0', '0']; 3126 3127 console.warn( 3128 'There was an issue retrieving Bootstrap\'s version. ' + 3129 'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' + 3130 'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.', 3131 err 3132 ); 3133 } 3134 } 3135 3136 version.major = version.full[0]; 3137 version.success = true; 3138 } 3139 3140 if (version.major === '4') { 3141 // some defaults need to be changed if using Bootstrap 4 3142 // check to see if they have already been manually changed before forcing them to update 3143 var toUpdate = []; 3144 3145 if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' }); 3146 if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' }); 3147 if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' }); 3148 3149 classNames.DIVIDER = 'dropdown-divider'; 3150 classNames.SHOW = 'show'; 3151 classNames.BUTTONCLASS = 'btn-light'; 3152 classNames.POPOVERHEADER = 'popover-header'; 3153 classNames.ICONBASE = ''; 3154 classNames.TICKICON = 'bs-ok-default'; 3155 3156 for (var i = 0; i < toUpdate.length; i++) { 3157 var option = toUpdate[i]; 3158 Selectpicker.DEFAULTS[option.name] = classNames[option.className]; 3159 } 3160 } 3161 3162 var value; 3163 var chain = this.each(function () { 3164 var $this = $(this); 3165 if ($this.is('select')) { 3166 var data = $this.data('selectpicker'), 3167 options = typeof _option == 'object' && _option; 3168 3169 if (!data) { 3170 var dataAttributes = $this.data(); 3171 3172 for (var dataAttr in dataAttributes) { 3173 if (Object.prototype.hasOwnProperty.call(dataAttributes, dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { 3174 delete dataAttributes[dataAttr]; 3175 } 3176 } 3177 3178 var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, dataAttributes, options); 3179 config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template); 3180 $this.data('selectpicker', (data = new Selectpicker(this, config))); 3181 } else if (options) { 3182 for (var i in options) { 3183 if (Object.prototype.hasOwnProperty.call(options, i)) { 3184 data.options[i] = options[i]; 3185 } 3186 } 3187 } 3188 3189 if (typeof _option == 'string') { 3190 if (data[_option] instanceof Function) { 3191 value = data[_option].apply(data, args); 3192 } else { 3193 value = data.options[_option]; 3194 } 3195 } 3196 } 3197 }); 3198 3199 if (typeof value !== 'undefined') { 3200 // noinspection JSUnusedAssignment 3201 return value; 3202 } else { 3203 return chain; 3204 } 3205 } 3206 3207 var old = $.fn.selectpicker; 3208 $.fn.selectpicker = Plugin; 3209 $.fn.selectpicker.Constructor = Selectpicker; 3210 3211 // SELECTPICKER NO CONFLICT 3212 // ======================== 3213 $.fn.selectpicker.noConflict = function () { 3214 $.fn.selectpicker = old; 3215 return this; 3216 }; 3217 3218 // get Bootstrap's keydown event handler for either Bootstrap 4 or Bootstrap 3 3219 function keydownHandler () { 3220 if ($.fn.dropdown) { 3221 // wait to define until function is called in case Bootstrap isn't loaded yet 3222 var bootstrapKeydown = $.fn.dropdown.Constructor._dataApiKeydownHandler || $.fn.dropdown.Constructor.prototype.keydown; 3223 return bootstrapKeydown.apply(this, arguments); 3224 } 3225 } 3226 3227 $(document) 3228 .off('keydown.bs.dropdown.data-api') 3229 .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > [data-toggle="dropdown"]', keydownHandler) 3230 .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > .dropdown-menu', keydownHandler) 3231 .on('keydown' + EVENT_KEY, '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown) 3232 .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) { 3233 e.stopPropagation(); 3234 }); 3235 3236 // SELECTPICKER DATA-API 3237 // ===================== 3238 $(window).on('load' + EVENT_KEY + '.data-api', function () { 3239 $('.selectpicker').each(function () { 3240 var $selectpicker = $(this); 3241 Plugin.call($selectpicker, $selectpicker.data()); 3242 }) 3243 }); 3244})(jQuery); 3245 3246 3247})); 3248//# sourceMappingURL=bootstrap-select.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.