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