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