1/*! 2 * Bootstrap-select v1.6.2 (http://silviomoreto.github.io/bootstrap-select/) 3 * 4 * Copyright 2013-2014 bootstrap-select 5 * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) 6 */ 7(function ($) { 8 'use strict'; 9 10 // Case insensitive search 11 $.expr[':'].icontains = function (obj, index, meta) { 12 return icontains($(obj).text(), meta[3]); 13 }; 14 15 // Case and accent insensitive search 16 $.expr[':'].aicontains = function (obj, index, meta) { 17 return icontains($(obj).data('normalizedText') || $(obj).text(), meta[3]); 18 }; 19 20 /** 21 * Actual implementation of the case insensitive search. 22 * @access private 23 * @param {String} haystack 24 * @param {String} needle 25 * @returns {boolean} 26 */ 27 function icontains(haystack, needle) { 28 return haystack.toUpperCase().indexOf(needle.toUpperCase()) > -1; 29 } 30 31 /** 32 * Remove all diatrics from the given text. 33 * @access private 34 * @param {String} text 35 * @returns {String} 36 */ 37 function normalizeToBase(text) { 38 var rExps = [ 39 {re: /[\xC0-\xC6]/g, ch: "A"}, 40 {re: /[\xE0-\xE6]/g, ch: "a"}, 41 {re: /[\xC8-\xCB]/g, ch: "E"}, 42 {re: /[\xE8-\xEB]/g, ch: "e"}, 43 {re: /[\xCC-\xCF]/g, ch: "I"}, 44 {re: /[\xEC-\xEF]/g, ch: "i"}, 45 {re: /[\xD2-\xD6]/g, ch: "O"}, 46 {re: /[\xF2-\xF6]/g, ch: "o"}, 47 {re: /[\xD9-\xDC]/g, ch: "U"}, 48 {re: /[\xF9-\xFC]/g, ch: "u"}, 49 {re: /[\xC7-\xE7]/g, ch: "c"}, 50 {re: /[\xD1]/g, ch: "N"}, 51 {re: /[\xF1]/g, ch: "n"} 52 ]; 53 $.each(rExps, function () { 54 text = text.replace(this.re, this.ch); 55 }); 56 return text; 57 } 58 59 var Selectpicker = function (element, options, e) { 60 if (e) { 61 e.stopPropagation(); 62 e.preventDefault(); 63 } 64 65 this.$element = $(element); 66 this.$newElement = null; 67 this.$button = null; 68 this.$menu = null; 69 this.$lis = null; 70 this.options = options; 71 72 // 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 73 // data-attribute) 74 if (this.options.title === null) { 75 this.options.title = this.$element.attr('title'); 76 } 77 78 //Expose public methods 79 this.val = Selectpicker.prototype.val; 80 this.render = Selectpicker.prototype.render; 81 this.refresh = Selectpicker.prototype.refresh; 82 this.setStyle = Selectpicker.prototype.setStyle; 83 this.selectAll = Selectpicker.prototype.selectAll; 84 this.deselectAll = Selectpicker.prototype.deselectAll; 85 this.destroy = Selectpicker.prototype.remove; 86 this.remove = Selectpicker.prototype.remove; 87 this.show = Selectpicker.prototype.show; 88 this.hide = Selectpicker.prototype.hide; 89 90 this.init(); 91 }; 92 93 Selectpicker.VERSION = '1.6.2'; 94 95 // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. 96 Selectpicker.DEFAULTS = { 97 noneSelectedText: 'Nothing selected', 98 noneResultsText: 'No results match', 99 countSelectedText: function (numSelected, numTotal) { 100 return (numSelected == 1) ? "{0} item selected" : "{0} items selected"; 101 }, 102 maxOptionsText: function (numAll, numGroup) { 103 var arr = []; 104 105 arr[0] = (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)'; 106 arr[1] = (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'; 107 108 return arr; 109 }, 110 selectAllText: 'Select All', 111 deselectAllText: 'Deselect All', 112 multipleSeparator: ', ', 113 style: 'btn-default', 114 size: 'auto', 115 title: null, 116 selectedTextFormat: 'values', 117 width: false, 118 container: false, 119 hideDisabled: false, 120 showSubtext: false, 121 showIcon: true, 122 showContent: true, 123 dropupAuto: true, 124 header: false, 125 liveSearch: false, 126 actionsBox: false, 127 iconBase: 'glyphicon', 128 tickIcon: 'glyphicon-ok', 129 maxOptions: false, 130 mobile: false, 131 selectOnTab: false, 132 dropdownAlignRight: false, 133 searchAccentInsensitive: false 134 }; 135 136 Selectpicker.prototype = { 137 138 constructor: Selectpicker, 139 140 init: function () { 141 var that = this, 142 id = this.$element.attr('id'); 143 144 this.$element.hide(); 145 this.multiple = this.$element.prop('multiple'); 146 this.autofocus = this.$element.prop('autofocus'); 147 this.$newElement = this.createView(); 148 this.$element.after(this.$newElement); 149 this.$menu = this.$newElement.find('> .dropdown-menu'); 150 this.$button = this.$newElement.find('> button'); 151 this.$searchbox = this.$newElement.find('input'); 152 153 if (this.options.dropdownAlignRight) 154 this.$menu.addClass('dropdown-menu-right'); 155 156 if (typeof id !== 'undefined') { 157 this.$button.attr('data-id', id); 158 $('label[for="' + id + '"]').click(function (e) { 159 e.preventDefault(); 160 that.$button.focus(); 161 }); 162 } 163 164 this.checkDisabled(); 165 this.clickListener(); 166 if (this.options.liveSearch) this.liveSearchListener(); 167 this.render(); 168 this.liHeight(); 169 this.setStyle(); 170 this.setWidth(); 171 if (this.options.container) this.selectPosition(); 172 this.$menu.data('this', this); 173 this.$newElement.data('this', this); 174 if (this.options.mobile) this.mobile(); 175 }, 176 177 createDropdown: function () { 178 // Options 179 // If we are multiple, then add the show-tick class by default 180 var multiple = this.multiple ? ' show-tick' : '', 181 inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '', 182 autofocus = this.autofocus ? ' autofocus' : '', 183 btnSize = this.$element.parents().hasClass('form-group-lg') ? ' btn-lg' : (this.$element.parents().hasClass('form-group-sm') ? ' btn-sm' : ''); 184 // Elements 185 var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">×</button>' + this.options.header + '</div>' : ''; 186 var searchbox = this.options.liveSearch ? '<div class="bs-searchbox"><input type="text" class="input-block-level form-control" autocomplete="off" /></div>' : ''; 187 var actionsbox = this.options.actionsBox ? '<div class="bs-actionsbox">' + 188 '<div class="btn-group btn-block">' + 189 '<button class="actions-btn bs-select-all btn btn-sm btn-default">' + 190 this.options.selectAllText + 191 '</button>' + 192 '<button class="actions-btn bs-deselect-all btn btn-sm btn-default">' + 193 this.options.deselectAllText + 194 '</button>' + 195 '</div>' + 196 '</div>' : ''; 197 var drop = 198 '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' + 199 '<button type="button" class="btn dropdown-toggle selectpicker' + btnSize + '" data-toggle="dropdown"' + autofocus + '>' + 200 '<span class="filter-option pull-left"></span> ' + 201 '<span class="caret"></span>' + 202 '</button>' + 203 '<div class="dropdown-menu open">' + 204 header + 205 searchbox + 206 actionsbox + 207 '<ul class="dropdown-menu inner selectpicker" role="menu">' + 208 '</ul>' + 209 '</div>' + 210 '</div>'; 211 212 return $(drop); 213 }, 214 215 createView: function () { 216 var $drop = this.createDropdown(); 217 var $li = this.createLi(); 218 $drop.find('ul').append($li); 219 return $drop; 220 }, 221 222 reloadLi: function () { 223 //Remove all children. 224 this.destroyLi(); 225 //Re build 226 var $li = this.createLi(); 227 this.$menu.find('ul').append($li); 228 }, 229 230 destroyLi: function () { 231 this.$menu.find('li').remove(); 232 }, 233 234 createLi: function () { 235 var that = this, 236 _li = [], 237 optID = 0; 238 239 // Helper functions 240 /** 241 * @param content 242 * @param [index] 243 * @param [classes] 244 * @returns {string} 245 */ 246 var generateLI = function (content, index, classes) { 247 return '<li' + 248 (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + 249 (typeof index !== 'undefined' | null === index ? ' data-original-index="' + index + '"' : '') + 250 '>' + content + '</li>'; 251 }; 252 253 /** 254 * @param text 255 * @param [classes] 256 * @param [inline] 257 * @param [optgroup] 258 * @returns {string} 259 */ 260 var generateA = function (text, classes, inline, optgroup) { 261 var normText = normalizeToBase($.trim($("<div/>").html(text).text()).replace(/\s\s+/g, ' ')); 262 return '<a tabindex="0"' + 263 (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + 264 (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') + 265 (typeof optgroup !== 'undefined' ? 'data-optgroup="' + optgroup + '"' : '') + 266 ' data-normalized-text="' + normText + '"' + 267 '>
267' + text + 268 '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' + 269 '</a>'; 270 }; 271 272 this.$element.find('option').each(function () { 273 var $this = $(this); 274 275 // Get the class and text for the option 276 var optionClass = $this.attr('class') || '', 277 inline = $this.attr('style'), 278 text = $this.data('content') ? $this.data('content') : $this.html(), 279 subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : '', 280 icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '', 281 isDisabled = $this.is(':disabled') || $this.parent().is(':disabled'), 282 index = $this[0].index; 283 if (icon !== '' && isDisabled) { 284 icon = '<span>' + icon + '</span>'; 285 } 286 287 if (!$this.data('content')) { 288 // Prepend any icon and append any subtext to the main text. 289 text = icon + '<span class="text">' + text + subtext + '</span>'; 290 } 291 292 if (that.options.hideDisabled && isDisabled) { 293 return; 294 } 295 296 if ($this.parent().is('optgroup') && $this.data('divider') !== true) { 297 if ($this.index() === 0) { // Is it the first option of the optgroup? 298 optID += 1; 299 300 // Get the opt group label 301 var label = $this.parent().attr('label'); 302 var labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.parent().data('subtext') + '</small>' : ''; 303 var labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : ''; 304 label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>'; 305 306 if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? 307 _li.push(generateLI('', null, 'divider')); 308 } 309 310 _li.push(generateLI(label, null, 'dropdown-header')); 311 } 312 313 _li.push(generateLI(generateA(text, 'opt ' + optionClass, inline, optID), index)); 314 } else if ($this.data('divider') === true) { 315 _li.push(generateLI('', index, 'divider')); 316 } else if ($this.data('hidden') === true) { 317 _li.push(generateLI(generateA(text, optionClass, inline), index, 'hide is-hidden')); 318 } else { 319 _li.push(generateLI(generateA(text, optionClass, inline), index)); 320 } 321 }); 322 323 //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 324 if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) { 325 this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); 326 } 327 328 return $(_li.join('')); 329 }, 330 331 findLis: function () { 332 if (this.$lis == null) this.$lis = this.$menu.find('li'); 333 return this.$lis; 334 }, 335 336 /** 337 * @param [updateLi] defaults to true 338 */ 339 render: function (updateLi) { 340 var that = this; 341 342 //Update the LI to match the SELECT 343 if (updateLi !== false) { 344 this.$element.find('option').each(function (index) { 345 that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled')); 346 that.setSelected(index, $(this).is(':selected')); 347 }); 348 } 349 350 this.tabIndex(); 351 var notDisabled = this.options.hideDisabled ? ':not([disabled])' : ''; 352 var selectedItems = this.$element.find('option:selected' + notDisabled).map(function () { 353 var $this = $(this); 354 var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : ''; 355 var subtext; 356 if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) { 357 subtext = ' <small class="muted text-muted">' + $this.data('subtext') + '</small>'; 358 } else { 359 subtext = ''; 360 } 361 if ($this.data('content') && that.options.showContent) { 362 return $this.data('content'); 363 } else if (typeof $this.attr('title') !== 'undefined') { 364 return $this.attr('title'); 365 } else { 366 return icon + $this.html() + subtext; 367 } 368 }).toArray(); 369 370 //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled 371 //Convert all the values into a comma delimited string 372 var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator); 373 374 //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc.. 375 if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) { 376 var max = this.options.selectedTextFormat.split('>'); 377 if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) { 378 notDisabled = this.options.hideDisabled ? ', [disabled]' : ''; 379 var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, 380 tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText; 381 title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString()); 382 } 383 } 384 385 this.options.title = this.$element.attr('title'); 386 387 if (this.options.selectedTextFormat == 'static') { 388 title = this.options.title; 389 } 390 391 //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text 392 if (!title) { 393 title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; 394 } 395 396 this.$button.attr('title', $.trim($("<div/>").html(title).text()).replace(/\s\s+/g, ' ')); 397 this.$newElement.find('.filter-option').html(title); 398 }, 399 400 /** 401 * @param [style] 402 * @param [status] 403 */ 404 setStyle: function (style, status) { 405 if (this.$element.attr('class')) { 406 this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, '')); 407 } 408 409 var buttonClass = style ? style : this.options.style; 410 411 if (status == 'add') { 412 this.$button.addClass(buttonClass); 413 } else if (status == 'remove') { 414 this.$button.removeClass(buttonClass); 415 } else { 416 this.$button.removeClass(this.options.style); 417 this.$button.addClass(buttonClass); 418 } 419 }, 420 421 liHeight: function () { 422 if (this.options.size === false) return; 423 424 var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'), 425 $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'), 426 liHeight = $menuClone.find('li').not('.divider').not('.dropdown-header').filter(':visible').children('a').outerHeight(), 427 headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0, 428 searchHeight = this.options.liveSearch ? $menuClone.find('.bs-searchbox').outerHeight() : 0, 429 actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0; 430 431 $selectClone.remove(); 432 433 this.$newElement 434 .data('liHeight', liHeight) 435 .data('headerHeight', headerHeight) 436 .data('searchHeight', searchHeight) 437 .data('actionsHeight', actionsHeight); 438 }, 439 440 setSize: function () { 441 this.findLis(); 442 var that = this, 443 menu = this.$menu, 444 menuInner = menu.find('.inner'), 445 selectHeight = this.$newElement.outerHeight(), 446 liHeight = this.$newElement.data('liHeight'), 447 headerHeight = this.$newElement.data('headerHeight'), 448 searchHeight = this.$newElement.data('searchHeight'), 449 actionsHeight = this.$newElement.data('actionsHeight'), 450 divHeight = this.$lis.filter('.divider').outerHeight(true), 451 menuPadding = parseInt(menu.css('padding-top')) + 452 parseInt(menu.css('padding-bottom')) + 453 parseInt(menu.css('border-top-width')) + 454 parseInt(menu.css('border-bottom-width')), 455 notDisabled = this.options.hideDisabled ? ', .disabled' : '', 456 $window = $(window), 457 menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2, 458 menuHeight, 459 selectOffsetTop, 460 selectOffsetBot, 461 posVert = function () { 462 // JQuery defines a scrollTop function, but in pure JS it's a property 463 //noinspection JSValidateTypes 464 selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); 465 selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; 466 }; 467 posVert(); 468 if (this.options.header) menu.css('padding-top', 0); 469 470 if (this.options.size == 'auto') { 471 var getSize = function () { 472 var minHeight, 473 lisVis = that.$lis.not('.hide'); 474 475 posVert(); 476 menuHeight = selectOffsetBot - menuExtras; 477 478 if (that.options.dropupAuto) { 479 that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height())); 480 } 481 if (that.$newElement.hasClass('dropup')) { 482 menuHeight = selectOffsetTop - menuExtras; 483 } 484 485 if ((lisVis.length + lisVis.filter('.dropdown-header').length) > 3) { 486 minHeight = liHeight * 3 + menuExtras - 2; 487 } else { 488 minHeight = 0; 489 } 490 491 menu.css({'max-height': menuHeight + 'px', 'overflow': 'hidden', 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + 'px'}); 492 menuInner.css({'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', 'overflow-y': 'auto', 'min-height': Math.max(minHeight - menuPadding, 0) + 'px'}); 493 }; 494 getSize(); 495 this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); 496 $(window).off('resize.getSize').on('resize.getSize', getSize); 497 $(window).off('scroll.getSize').on('scroll.getSize', getSize); 498 } else if (this.options.size && this.options.size != 'auto' && menu.find('li' + notDisabled).length > this.options.size) { 499 var optIndex = this.$lis.not('.divider' + notDisabled).find(' > *').slice(0, this.options.size).last().parent().index(); 500 var divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; 501 menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding; 502 if (that.options.dropupAuto) { 503 //noinspection JSUnusedAssignment 504 this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height())); 505 } 506 menu.css({'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow': 'hidden'}); 507 menuInner.css({'max-height': menuHeight - menuPadding + 'px', 'overflow-y': 'auto'}); 508 } 509 }, 510 511 setWidth: function () { 512 if (this.options.width == 'auto') { 513 this.$menu.css('min-width', '0'); 514 515 // Get correct width if element hidden 516 var selectClone = this.$newElement.clone().appendTo('body'); 517 var ulWidth = selectClone.find('> .dropdown-menu').css('width'); 518 var btnWidth = selectClone.css('width', 'auto').find('> button').css('width'); 519 selectClone.remove(); 520 521 // Set width to whatever's larger, button title or longest option 522 this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px'); 523 } else if (this.options.width == 'fit') { 524 // Remove inline min-width so width can be changed from 'auto' 525 this.$menu.css('min-width', ''); 526 this.$newElement.css('width', '').addClass('fit-width'); 527 } else if (this.options.width) { 528 // Remove inline min-width so width can be changed from 'auto' 529 this.$menu.css('min-width', ''); 530 this.$newElement.css('width', this.options.width); 531 } else { 532 // Remove inline min-width/width so width can be changed 533 this.$menu.css('min-width', ''); 534 this.$newElement.css('width', ''); 535 } 536 // Remove fit-width class if width is changed programmatically 537 if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { 538 this.$newElement.removeClass('fit-width'); 539 } 540 }, 541 542 selectPosition: function () { 543 var that = this, 544 drop = '<div />', 545 $drop = $(drop), 546 pos, 547 actualHeight, 548 getPlacement = function ($element) { 549 $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); 550 pos = $element.offset(); 551 actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; 552 $drop.css({'top': pos.top + actualHeight, 'left': pos.left, 'width': $element[0].offsetWidth, 'position': 'absolute'}); 553 }; 554 this.$newElement.on('click', function () { 555 if (that.isDisabled()) { 556 return; 557 } 558 getPlacement($(this)); 559 $drop.appendTo(that.options.container); 560 $drop.toggleClass('open', !$(this).hasClass('open')); 561 $drop.append(that.$menu); 562 }); 563 $(window).resize(function () { 564 getPlacement(that.$newElement); 565 }); 566 $(window).on('scroll', function () { 567 getPlacement(that.$newElement); 568 }); 569 $('html').on('click', function (e) { 570 if ($(e.target).closest(that.$newElement).length < 1) { 571 $drop.removeClass('open'); 572 } 573 }); 574 }, 575 576 setSelected: function (index, selected) { 577 this.findLis(); 578 this.$lis.filter('[data-original-index="' + index + '"]').toggleClass('selected', selected); 579 }, 580 581 setDisabled: function (index, disabled) { 582 this.findLis(); 583 if (disabled) { 584 this.$lis.filter('[data-original-index="' + index + '"]').addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1); 585 } else { 586 this.$lis.filter('[data-original-index="' + index + '"]').removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0); 587 } 588 }, 589 590 isDisabled: function () { 591 return this.$element.is(':disabled'); 592 }, 593 594 checkDisabled: function () { 595 var that = this; 596 597 if (this.isDisabled()) { 598 this.$button.addClass('disabled').attr('tabindex', -1); 599 } else { 600 if (this.$button.hasClass('disabled')) { 601 this.$button.removeClass('disabled'); 602 } 603 604 if (this.$button.attr('tabindex') == -1) { 605 if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex'); 606 } 607 } 608 609 this.$button.click(function () { 610 return !that.isDisabled(); 611 }); 612 }, 613 614 tabIndex: function () { 615 if (this.$element.is('[tabindex]')) { 616 this.$element.data('tabindex', this.$element.attr('tabindex')); 617 this.$button.attr('tabindex', this.$element.data('tabindex')); 618 } 619 }, 620 621 clickListener: function () { 622 var that = this; 623 624 this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) { 625 e.stopPropagation(); 626 }); 627 628 this.$newElement.on('click', function () { 629 that.setSize(); 630 if (!that.options.liveSearch && !that.multiple) { 631 setTimeout(function () { 632 that.$menu.find('.selected a').focus(); 633 }, 10); 634 } 635 }); 636 637 this.$menu.on('click', 'li a', function (e) { 638 var $this = $(this), 639 clickedIndex = $this.parent().data('originalIndex'),
640 prevValue = that.$element.val(), 641 prevIndex = that.$element.prop('selectedIndex'); 642 643 // Don't close on multi choice menu 644 if (that.multiple) { 645 e.stopPropagation(); 646 } 647 648 e.preventDefault(); 649 650 //Don't run if we have been disabled 651 if (!that.isDisabled() && !$this.parent().hasClass('disabled')) { 652 var $options = that.$element.find('option'), 653 $option = $options.eq(clickedIndex), 654 state = $option.prop('selected'), 655 $optgroup = $option.parent('optgroup'), 656 maxOptions = that.options.maxOptions, 657 maxOptionsGrp = $optgroup.data('maxOptions') || false; 658 659 if (!that.multiple) { // Deselect all others if not multi select box 660 $options.prop('selected', false); 661 $option.prop('selected', true); 662 that.$menu.find('.selected').removeClass('selected'); 663 that.setSelected(clickedIndex, true); 664 } else { // Toggle the one we have chosen if we are multi select. 665 $option.prop('selected', !state); 666 that.setSelected(clickedIndex, !state); 667 $this.blur(); 668 669 if ((maxOptions !== false) || (maxOptionsGrp !== false)) { 670 var maxReached = maxOptions < $options.filter(':selected').length, 671 maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; 672 673 if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { 674 if (maxOptions && maxOptions == 1) { 675 $options.prop('selected', false); 676 $option.prop('selected', true); 677 that.$menu.find('.selected').removeClass('selected'); 678 that.setSelected(clickedIndex, true); 679 } else if (maxOptionsGrp && maxOptionsGrp == 1) { 680 $optgroup.find('option:selected').prop('selected', false); 681 $option.prop('selected', true); 682 var optgroupID = $this.data('optgroup'); 683 684 that.$menu.find('.selected').has('a[data-optgroup="' + optgroupID + '"]').removeClass('selected'); 685 686 that.setSelected(clickedIndex, true); 687 } else { 688 var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ? 689 that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText, 690 maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), 691 maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), 692 $notify = $('<div class="notify"></div>'); 693 // If {var} is set in array, replace it 694 /** @deprecated */ 695 if (maxOptionsArr[2]) { 696 maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); 697 maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); 698 } 699 700 $option.prop('selected', false); 701 702 that.$menu.append($notify); 703 704 if (maxOptions && maxReached) { 705 $notify.append($('<div>' + maxTxt + '</div>')); 706 that.$element.trigger('maxReached.bs.select'); 707 } 708 709 if (maxOptionsGrp && maxReachedGrp) { 710 $notify.append($('<div>' + maxTxtGrp + '</div>')); 711 that.$element.trigger('maxReachedGrp.bs.select'); 712 } 713 714 setTimeout(function () { 715 that.setSelected(clickedIndex, false); 716 }, 10); 717 718 $notify.delay(750).fadeOut(300, function () { 719 $(this).remove(); 720 }); 721 } 722 } 723 } 724 } 725 726 if (!that.multiple) { 727 that.$button.focus(); 728 } else if (that.options.liveSearch) { 729 that.$searchbox.focus(); 730 } 731 732 // Trigger select 'change' 733 if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { 734 that.$element.change(); 735 } 736 } 737 }); 738 739 this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) { 740 if (e.target == this) { 741 e.preventDefault(); 742 e.stopPropagation(); 743 if (!that.options.liveSearch) { 744 that.$button.focus(); 745 } else { 746 that.$searchbox.focus(); 747 } 748 } 749 }); 750 751 this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) { 752 e.preventDefault(); 753 e.stopPropagation(); 754 if (!that.options.liveSearch) { 755 that.$button.focus(); 756 } else { 757 that.$searchbox.focus(); 758 } 759 }); 760 761 this.$menu.on('click', '.popover-title .close', function () { 762 that.$button.focus(); 763 }); 764 765 this.$searchbox.on('click', function (e) { 766 e.stopPropagation(); 767 }); 768 769 770 this.$menu.on('click', '.actions-btn', function (e) { 771 if (that.options.liveSearch) { 772 that.$searchbox.focus(); 773 } else { 774 that.$button.focus(); 775 } 776 777 e.preventDefault(); 778 e.stopPropagation(); 779 780 if ($(this).is('.bs-select-all')) { 781 that.selectAll(); 782 } else { 783 that.deselectAll(); 784 } 785 that.$element.change(); 786 }); 787 788 this.$element.change(function () { 789 that.render(false); 790 }); 791 }, 792 793 liveSearchListener: function () { 794 var that = this, 795 no_results = $('<li class="no-results"></li>'); 796 797 this.$newElement.on('click.dropdown.data-api', function () { 798 that.$menu.find('.active').removeClass('active'); 799 if (!!that.$searchbox.val()) { 800 that.$searchbox.val(''); 801 that.$lis.not('.is-hidden').removeClass('hide'); 802 if (!!no_results.parent().length) no_results.remove(); 803 } 804 if (!that.multiple) that.$menu.find('.selected').addClass('active'); 805 setTimeout(function () { 806 that.$searchbox.focus(); 807 }, 10); 808 }); 809 810 this.$searchbox.on('input propertychange', function () { 811 if (that.$searchbox.val()) { 812
813 if (that.options.searchAccentInsensitive) { 814 that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':aicontains(' + normalizeToBase(that.$searchbox.val()) + ')').parent().addClass('hide'); 815 } else { 816 that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide'); 817 } 818 819 if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) { 820 if (!!no_results.parent().length) no_results.remove(); 821 no_results.html(that.options.noneResultsText + ' "' + that.$searchbox.val() + '"').show(); 822 that.$menu.find('li').last().after(no_results); 823 } else if (!!no_results.parent().length) { 824 no_results.remove(); 825 } 826 827 } else { 828 that.$lis.not('.is-hidden').removeClass('hide'); 829 if (!!no_results.parent().length) no_results.remove(); 830 } 831 832 that.$menu.find('li.active').removeClass('active'); 833 that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus(); 834 $(this).focus(); 835 }); 836 }, 837 838 val: function (value) { 839 if (typeof value !== 'undefined') { 840 this.$element.val(value); 841 this.render(); 842 843 return this.$element; 844 } else { 845 return this.$element.val(); 846 } 847 }, 848 849 selectAll: function () { 850 this.findLis(); 851 this.$lis.not('.divider').not('.disabled').not('.selected').filter(':visible').find('a').click(); 852 }, 853 854 deselectAll: function () { 855 this.findLis(); 856 this.$lis.not('.divider').not('.disabled').filter('.selected').filter(':visible').find('a').click(); 857 }, 858 859 keydown: function (e) { 860 var $this = $(this), 861 $parent = ($this.is('input')) ? $this.parent().parent() : $this.parent(), 862 $items, 863 that = $parent.data('this'), 864 index, 865 next, 866 first, 867 last, 868 prev, 869 nextPrev, 870 prevIndex, 871 isActive, 872 keyCodeMap = { 873 32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 874 65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k', 76: 'l', 875 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 876 89: 'y', 90: 'z', 96: '0', 97: '1', 98: '2', 99: '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9' 877 }; 878 879 if (that.options.liveSearch) $parent = $this.parent().parent(); 880 881 if (that.options.container) $parent = that.$menu; 882 883 $items = $('[role=menu] li a', $parent); 884 885 isActive = that.$menu.parent().hasClass('open'); 886 887 if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) { 888 if (!that.options.container) { 889 that.setSize(); 890 that.$menu.parent().addClass('open'); 891 isActive = true; 892 } else { 893 that.$newElement.trigger('click'); 894 } 895 that.$searchbox.focus(); 896 } 897 898 if (that.options.liveSearch) { 899 if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) { 900 e.preventDefault(); 901 that.$menu.parent().removeClass('open'); 902 that.$button.focus(); 903 } 904 $items = $('[role=menu] li:not(.divider):not(.dropdown-header):visible', $parent); 905 if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) { 906 if ($items.filter('.active').length === 0) { 907 if (that.options.searchAccentInsensitive) { 908 $items = that.$newElement.find('li').filter(':aicontains(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')'); 909 } else { 910 $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')'); 911 } 912 } 913 } 914 } 915 916 if (!$items.length) return; 917 918 if (/(38|40)/.test(e.keyCode.toString(10))) { 919 index = $items.index($items.filter(':focus')); 920 first = $items.parent(':not(.disabled):visible').first().index(); 921 last = $items.parent(':not(.disabled):visible').last().index(); 922 next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index(); 923 prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index(); 924 nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index(); 925 926 if (that.options.liveSearch) { 927 $items.each(function (i) { 928 if ($(this).is(':not(.disabled)')) { 929 $(this).data('index', i); 930 } 931 }); 932 index = $items.index($items.filter('.active')); 933 first = $items.filter(':not(.disabled):visible').first().data('index'); 934 last = $items.filter(':not(.disabled):visible').last().data('index'); 935 next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index'); 936 prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index'); 937 nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index'); 938 } 939 940 prevIndex = $this.data('prevIndex'); 941 942 if (e.keyCode == 38) { 943 if (that.options.liveSearch) index -= 1; 944 if (index != nextPrev && index > prev) index = prev; 945 if (index < first) index = first; 946 if (index == prevIndex) index = last; 947 } 948 949 if (e.keyCode == 40) { 950 if (that.options.liveSearch) index += 1; 951 if (index == -1) index = 0; 952 if (index != nextPrev && index < next) index = next; 953 if (index > last) index = last; 954 if (index == prevIndex) index = first; 955 } 956 957 $this.data('prevIndex', index); 958 959 if (!that.options.liveSearch) { 960 $items.eq(index).focus(); 961 } else { 962 e.preventDefault(); 963 if (!$this.is('.dropdown-toggle')) { 964 $items.removeClass('active'); 965 $items.eq(index).addClass('active').find('a').focus(); 966 $this.focus(); 967 } 968 } 969 970 } else if (!$this.is('input')) { 971 var keyIndex = [], 972 count, 973 prevKey; 974 975 $items.each(function () { 976 if ($(this).parent().is(':not(.disabled)')) { 977 if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { 978 keyIndex.push($(this).parent().index()); 979 } 980 } 981 }); 982 983 count = $(document).data('keycount'); 984 count++; 985 $(document).data('keycount', count); 986 987 prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1); 988 989 if (prevKey != keyCodeMap[e.keyCode]) { 990 count = 1; 991 $(document).data('keycount', count); 992 } else if (count >= keyIndex.length) { 993 $(document).data('keycount', 0); 994 if (count > keyIndex.length) count = 1; 995 } 996 997 $items.eq(keyIndex[count - 1]).focus(); 998 } 999 1000 // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. 1001 if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) { 1002 if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault(); 1003 if (!that.options.liveSearch) { 1004 $(':focus').click(); 1005 } else if (!/(32)/.test(e.keyCode.toString(10))) { 1006 that.$menu.find('.active a').click(); 1007 $this.focus(); 1008 } 1009 $(document).data('keycount', 0); 1010 } 1011 1012 if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) { 1013 that.$menu.parent().removeClass('open'); 1014 that.$button.focus(); 1015 } 1016 }, 1017 1018 mobile: function () { 1019 this.$element.addClass('mobile-device').appendTo(this.$newElement); 1020 if (this.options.container) this.$menu.hide(); 1021 }, 1022 1023 refresh: function () { 1024 this.$lis = null; 1025 this.reloadLi(); 1026 this.render(); 1027 this.setWidth(); 1028 this.setStyle(); 1029 this.checkDisabled(); 1030 this.liHeight(); 1031 }, 1032 1033 update: function () { 1034 this.reloadLi(); 1035 this.setWidth(); 1036 this.setStyle(); 1037 this.checkDisabled(); 1038 this.liHeight(); 1039 }, 1040 1041 hide: function () { 1042 this.$newElement.hide(); 1043 }, 1044 1045 show: function () { 1046 this.$newElement.show(); 1047 }, 1048 1049 remove: function () { 1050 this.$newElement.remove(); 1051 this.$element.remove(); 1052 } 1053 }; 1054 1055 // SELECTPICKER PLUGIN DEFINITION 1056 // ============================== 1057 function Plugin(option, event) { 1058 // get the args of the outer function.. 1059 var args = arguments; 1060 // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
1061 // to get lost 1062 //noinspection JSDuplicatedDeclaration 1063 var _option = option, 1064 option = args[0], 1065 event = args[1]; 1066 [].shift.apply(args); 1067 1068 // This fixes a bug in the js implementation on android 2.3 #715 1069 if (typeof option == 'undefined') { 1070 option = _option; 1071 } 1072 1073 var value; 1074 var chain = this.each(function () { 1075 var $this = $(this); 1076 if ($this.is('select')) { 1077 var data = $this.data('selectpicker'), 1078 options = typeof option == 'object' && option; 1079 1080 if (!data) { 1081 var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options); 1082 $this.data('selectpicker', (data = new Selectpicker(this, config, event))); 1083 } else if (options) { 1084 for (var i in options) { 1085 if (options.hasOwnProperty(i)) { 1086 data.options[i] = options[i]; 1087 } 1088 } 1089 } 1090 1091 if (typeof option == 'string') { 1092 if (data[option] instanceof Function) { 1093 value = data[option].apply(data, args); 1094 } else { 1095 value = data.options[option]; 1096 } 1097 } 1098 } 1099 }); 1100 1101 if (typeof value !== 'undefined') { 1102 //noinspection JSUnusedAssignment 1103 return value; 1104 } else { 1105 return chain; 1106 } 1107 } 1108 1109 var old = $.fn.selectpicker; 1110 $.fn.selectpicker = Plugin; 1111 $.fn.selectpicker.Constructor = Selectpicker; 1112 1113 // SELECTPICKER NO CONFLICT 1114 // ======================== 1115 $.fn.selectpicker.noConflict = function () { 1116 $.fn.selectpicker = old; 1117 return this; 1118 }; 1119 1120 $(document) 1121 .data('keycount', 0) 1122 .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', Selectpicker.prototype.keydown) 1123 .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', function (e) { 1124 e.stopPropagation(); 1125 }); 1126 1127 // SELECTPICKER DATA-API 1128 // ===================== 1129 $(window).on('load.bs.select.data-api', function () { 1130 $('.selectpicker').each(function () { 1131 var $selectpicker = $(this); 1132 Plugin.call($selectpicker, $selectpicker.data()); 1133 }) 1134 }); 1135})(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.