vendor: 16,535 bytes, lines 1-406
1/** 2 * Bootstrap Multiselect (http://davidstutz.de/bootstrap-multiselect/) 3 * 4 * Apache License, Version 2.0: 5 * Copyright (c) 2012 - 2021 David Stutz 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 8 * use this file except in compliance with the License. You may obtain a 9 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 * License for the specific language governing permissions and limitations 15 * under the License. 16 * 17 * BSD 3-Clause License: 18 * Copyright (c) 2012 - 2021 David Stutz 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or without 22 * modification, are permitted provided that the following conditions are met: 23 * - Redistributions of source code must retain the above copyright notice, 24 * this list of conditions and the following disclaimer. 25 * - Redistributions in binary form must reproduce the above copyright notice, 26 * this list of conditions and the following disclaimer in the documentation 27 * and/or other materials provided with the distribution. 28 * - Neither the name of David Stutz nor the names of its contributors may be 29 * used to endorse or promote products derived from this software without 30 * specific prior written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 33 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 34 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 35 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 37 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 38 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 39 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 40 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 41 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 */ 44(function (root, factory) { 45 // check to see if 'knockout' AMD module is specified if using requirejs 46 if (typeof define === 'function' && define.amd && 47 typeof require === 'function' && typeof require.specified === 'function' && require.specified('knockout')) { 48 49 // AMD. Register as an anonymous module. 50 define(['jquery', 'knockout'], factory); 51 } else { 52 // Browser globals 53 factory(root.jQuery, root.ko); 54 } 55})(this, function ($, ko) { 56 "use strict";// jshint ;_; 57 58 if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { 59 ko.bindingHandlers.multiselect = { 60 after: ['options', 'value', 'selectedOptions', 'enable', 'disable'], 61 62 init: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 63 var $element = $(element); 64 var config = ko.toJS(valueAccessor()); 65 66 $element.multiselect(config); 67 68 if (allBindings.has('options')) { 69 var options = allBindings.get('options'); 70 if (ko.isObservable(options)) { 71 ko.computed({ 72 read: function () { 73 options(); 74 setTimeout(function () { 75 var ms = $element.data('multiselect'); 76 if (ms) 77 ms.updateOriginalOptions();//Not sure how beneficial this is. 78 $element.multiselect('rebuild'); 79 }, 1); 80 }, 81 disposeWhenNodeIsRemoved: element 82 }); 83 } 84 } 85 86 //value and selectedOptions are two-way, so these will be triggered even by our own actions. 87 //It needs some way to tell if they are triggered because of us or because of outside change. 88 //It doesn't loop but it's a waste of processing. 89 if (allBindings.has('value')) { 90 var value = allBindings.get('value'); 91 if (ko.isObservable(value)) { 92 ko.computed({ 93 read: function () { 94 value(); 95 setTimeout(function () { 96 $element.multiselect('refresh'); 97 }, 1); 98 }, 99 disposeWhenNodeIsRemoved: element 100 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 101 } 102 } 103 104 //Switched from arrayChange subscription to general subscription using 'refresh'. 105 //Not sure performance is any better using 'select' and 'deselect'. 106 if (allBindings.has('selectedOptions')) { 107 var selectedOptions = allBindings.get('selectedOptions'); 108 if (ko.isObservable(selectedOptions)) { 109 ko.computed({ 110 read: function () { 111 selectedOptions(); 112 setTimeout(function () { 113 $element.multiselect('refresh'); 114 }, 1); 115 }, 116 disposeWhenNodeIsRemoved: element 117 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 118 } 119 } 120 121 var setEnabled = function (enable) { 122 setTimeout(function () { 123 if (enable) 124 $element.multiselect('enable'); 125 else 126 $element.multiselect('disable'); 127 }); 128 }; 129 130 if (allBindings.has('enable')) { 131 var enable = allBindings.get('enable'); 132 if (ko.isObservable(enable)) { 133 ko.computed({ 134 read: function () { 135 setEnabled(enable()); 136 }, 137 disposeWhenNodeIsRemoved: element 138 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 139 } else { 140 setEnabled(enable); 141 } 142 } 143 144 if (allBindings.has('disable')) { 145 var disable = allBindings.get('disable'); 146 if (ko.isObservable(disable)) { 147 ko.computed({ 148 read: function () { 149 setEnabled(!disable()); 150 }, 151 disposeWhenNodeIsRemoved: element 152 }).extend({ rateLimit: 100, notifyWhenChangesStop: true }); 153 } else { 154 setEnabled(!disable); 155 } 156 } 157 158 ko.utils.domNodeDisposal.addDisposeCallback(element, function () { 159 $element.multiselect('destroy'); 160 }); 161 }, 162 163 update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { 164 var $element = $(element); 165 var config = ko.toJS(valueAccessor()); 166 167 $element.multiselect('setOptions', config); 168 $element.multiselect('rebuild'); 169 } 170 }; 171 } 172 173 function forEach(array, callback) { 174 for (var index = 0; index < array.length; ++index) { 175 callback(array[index], index); 176 } 177 } 178 179 /** 180 * Constructor to create a new multiselect using the given select. 181 * 182 * @param {jQuery} select 183 * @param {Object} options 184 * @returns {Multiselect} 185 */ 186 function Multiselect(select, options) { 187 188 this.$select = $(select); 189 this.options = this.mergeOptions($.extend({}, options, this.$select.data())); 190 191 // Placeholder via data attributes 192 if (this.$select.attr("data-placeholder")) { 193 this.options.nonSelectedText = this.$select.data("placeholder"); 194 } 195 196 // Initialization. 197 // We have to clone to create a new reference. 198 this.originalOptions = this.$select.clone()[0].options; 199 this.query = ''; 200 this.searchTimeout = null; 201 this.lastToggledInput = null; 202 203 this.options.multiple = this.$select.attr('multiple') === "multiple"; 204 this.options.onChange = $.proxy(this.options.onChange, this); 205 this.options.onSelectAll = $.proxy(this.options.onSelectAll, this); 206 this.options.onDeselectAll = $.proxy(this.options.onDeselectAll, this); 207 this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); 208 this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); 209 this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); 210 this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); 211 this.options.onInitialized = $.proxy(this.options.onInitialized, this); 212 this.options.onFiltering = $.proxy(this.options.onFiltering, this); 213 214 // Build select all if enabled. 215 this.buildContainer(); 216 this.buildButton(); 217 this.buildDropdown(); 218 this.buildReset(); 219 this.buildSelectAll(); 220 this.buildDropdownOptions(); 221 this.buildFilter(); 222 this.buildButtons(); 223 224 this.updateButtonText(); 225 this.updateSelectAll(true); 226 227 if (this.options.enableClickableOptGroups && this.options.multiple) { 228 this.updateOptGroups(); 229 } 230 231 this.options.wasDisabled = this.$select.prop('disabled'); 232 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0 && !this.options.wasDisabled) { 233 this.disable(true); 234 } 235 236 this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container); 237 this.$select.prop('tabindex', '-1'); 238 239 if (this.options.widthSynchronizationMode !== 'never') { 240 this.synchronizeButtonAndPopupWidth(); 241 } 242 243 this.options.onInitialized(this.$select, this.$container); 244 } 245 246 Multiselect.prototype = { 247 248 defaults: { 249 /** 250 * Default text function will either print 'None selected' in case no 251 * option is selected or a list of the selected options up to a length 252 * of 3 selected options. 253 * 254 * @param {jQuery} options 255 * @param {jQuery} select 256 * @returns {String} 257 */ 258 buttonText: function (selectedOptions, select) { 259 if (this.disabledText.length > 0 && select.prop('disabled')) { 260 return this.disabledText; 261 } 262 else if (selectedOptions.length === 0) { 263 return this.nonSelectedText; 264 } 265 else if (this.allSelectedText 266 && selectedOptions.length === $('option', $(select)).length 267 && $('option', $(select)).length !== 1 268 && this.multiple) { 269 270 if (this.selectAllNumber) { 271 return this.allSelectedText + ' (' + selectedOptions.length + ')'; 272 } 273 else { 274 return this.allSelectedText; 275 } 276 } 277 else if (this.numberDisplayed != 0 && selectedOptions.length > this.numberDisplayed) { 278 return selectedOptions.length + ' ' + this.nSelectedText; 279 } 280 else { 281 var selected = ''; 282 var delimiter = this.delimiterText; 283 284 selectedOptions.each(function () { 285 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 286 selected += label + delimiter; 287 }); 288 289 return selected.substr(0, selected.length - this.delimiterText.length); 290 } 291 }, 292 /** 293 * Updates the title of the button similar to the buttonText function. 294 * 295 * @param {jQuery} options 296 * @param {jQuery} select 297 * @returns {@exp;selected@call;substr} 298 */ 299 buttonTitle: function (options, select) { 300 if (options.length === 0) { 301 return this.nonSelectedText; 302 } 303 else { 304 var selected = ''; 305 var delimiter = this.delimiterText; 306 307 options.each(function () { 308 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 309 selected += label + delimiter; 310 }); 311 return selected.substr(0, selected.length - this.delimiterText.length); 312 } 313 }, 314 checkboxName: function (option) { 315 return false; // no checkbox name 316 }, 317 /** 318 * Create a label. 319 * 320 * @param {jQuery} element 321 * @returns {String} 322 */ 323 optionLabel: function (element) { 324 return $(element).attr('label') || $(element).text(); 325 }, 326 /** 327 * Create a class. 328 * 329 * @param {jQuery} element 330 * @returns {String} 331 */ 332 optionClass: function (element) { 333 return $(element).attr('class') || ''; 334 }, 335 /** 336 * Triggered on change of the multiselect. 337 * 338 * Not triggered when selecting/deselecting options manually. 339 * 340 * @param {jQuery} option 341 * @param {Boolean} checked 342 */ 343 onChange: function (option, checked) { 344 345 }, 346 /** 347 * Triggered when the dropdown is shown. 348 * 349 * @param {jQuery} event 350 */ 351 onDropdownShow: function (event) { 352 353 }, 354 /** 355 * Triggered when the dropdown is hidden. 356 * 357 * @param {jQuery} event 358 */ 359 onDropdownHide: function (event) { 360 361 }, 362 /** 363 * Triggered after the dropdown is shown. 364 * 365 * @param {jQuery} event 366 */ 367 onDropdownShown: function (event) { 368 369 }, 370 /** 371 * Triggered after the dropdown is hidden. 372 * 373 * @param {jQuery} event 374 */ 375 onDropdownHidden: function (event) { 376 377 }, 378 /** 379 * Triggered on select all. 380 */ 381 onSelectAll: function () { 382 383 }, 384 /** 385 * Triggered on deselect all. 386 */ 387 onDeselectAll: function () { 388 389 }, 390 /** 391 * Triggered after initializing. 392 * 393 * @param {jQuery} $select 394 * @param {jQuery} $container 395 */ 396 onInitialized: function ($select, $container) { 397 398 }, 399 /** 400 * Triggered on filtering. 401 * 402 * @param {jQuery} $filter 403 */ 404 onFiltering: function ($filter) { 405 406 },
407 enableHTML: false, 408 buttonClass: 'custom-select', 409 inheritClass: false, 410 buttonWidth: 'auto', 411 buttonContainer: '<div class="btn-group" />', 412 dropRight: false, 413 dropUp: false, 414 selectedClass: 'active', 415 // Maximum height of the dropdown menu. 416 // If maximum height is exceeded a scrollbar will be displayed. 417 maxHeight: false, 418 includeSelectAllOption: false, 419 includeSelectAllIfMoreThan: 0, 420 selectAllText: ' Select all', 421 selectAllValue: 'multiselect-all', 422 selectAllName: false, 423 selectAllNumber: true, 424 selectAllJustVisible: true, 425 enableFiltering: false, 426 enableCaseInsensitiveFiltering: false, 427 enableFullValueFiltering: false, 428 enableClickableOptGroups: false, 429 enableCollapsibleOptGroups: false, 430 collapseOptGroupsByDefault: false, 431 filterPlaceholder: 'Search', 432 // possible options: 'text', 'value', 'both' 433 filterBehavior: 'text', 434 includeFilterClearBtn: true, 435 preventInputChangeEvent: false, 436 nonSelectedText: 'None selected', 437 nSelectedText: 'selected', 438 allSelectedText: 'All selected', 439 resetButtonText: 'Reset', 440 numberDisplayed: 3, 441 disableIfEmpty: false, 442 disabledText: '', 443 delimiterText: ', ', 444 includeResetOption: false, 445 includeResetDivider: false,
vendor: 18,581 bytes, lines 446-858
446 resetText: 'Reset', 447 indentGroupOptions: true, 448 // possible options: 'never', 'always', 'ifPopupIsSmaller', 'ifPopupIsWider' 449 widthSynchronizationMode: 'never', 450 buttonTextAlignment: 'center', 451 enableResetButton: false, 452 templates: { 453 button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span></button>', 454 popupContainer: '<div class="multiselect-container dropdown-menu"></div>', 455 filter: '<div class="multiselect-filter d-flex align-items-center"><i class="fas fa-sm fa-search text-muted"></i><input type="search" class="multiselect-search form-control" /></div>', 456 buttonGroup: '<div class="multiselect-buttons btn-group" style="display:flex;"></div>', 457 buttonGroupReset: '<button type="button" class="multiselect-reset btn btn-secondary btn-block"></button>', 458 option: '<button type="button" class="multiselect-option dropdown-item"></button>', 459 divider: '<div class="dropdown-divider"></div>', 460 optionGroup: '<button type="button" class="multiselect-group dropdown-item"></button>', 461 resetButton: '<div class="multiselect-reset text-center p-2"><button type="button" class="btn btn-sm btn-block btn-outline-secondary"></button></div>' 462 } 463 }, 464 465 constructor: Multiselect, 466 467 /** 468 * Builds the container of the multiselect. 469 */ 470 buildContainer: function () { 471 this.$container = $(this.options.buttonContainer); 472 if (this.options.widthSynchronizationMode !== 'never') { 473 this.$container.on('show.bs.dropdown', $.proxy(function () { 474 // the width needs to be synchronized again in case the width of the button changed in between 475 this.synchronizeButtonAndPopupWidth(); 476 this.options.onDropdownShow(); 477 }, this)); 478 } 479 else { 480 this.$container.on('show.bs.dropdown', this.options.onDropdownShow); 481 } 482 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); 483 this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); 484 this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); 485 }, 486 487 /** 488 * Builds the button of the multiselect. 489 */ 490 buildButton: function () { 491 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); 492 if (this.$select.attr('class') && this.options.inheritClass) { 493 this.$button.addClass(this.$select.attr('class')); 494 } 495 // Adopt active state. 496 if (this.$select.prop('disabled')) { 497 this.disable(); 498 } 499 else { 500 this.enable(); 501 } 502 503 // Manually add button width if set. 504 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { 505 this.$button.css({ 506 'width': '100%' //this.options.buttonWidth, 507 }); 508 this.$container.css({ 509 'width': this.options.buttonWidth 510 }); 511 } 512 513 if (this.options.buttonTextAlignment) { 514 switch (this.options.buttonTextAlignment) { 515 case 'left': 516 this.$button.addClass('text-left'); 517 break; 518 case 'center': 519 this.$button.addClass('text-center'); 520 break; 521 case 'right': 522 this.$button.addClass('text-right'); 523 break; 524 } 525 } 526 527 // Keep the tab index from the select. 528 var tabindex = this.$select.attr('tabindex'); 529 if (tabindex) { 530 this.$button.attr('tabindex', tabindex); 531 } 532 533 this.$container.prepend(this.$button); 534 }, 535 536 /** 537 * Builds the popup container representing the dropdown menu. 538 */ 539 buildDropdown: function () { 540 541 // Build popup container. 542 this.$popupContainer = $(this.options.templates.popupContainer); 543 544 if (this.options.dropRight) { 545 this.$container.addClass('dropright'); 546 } 547 else if (this.options.dropUp) { 548 this.$container.addClass("dropup"); 549 } 550 551 // Set max height of dropdown menu to activate auto scrollbar. 552 if (this.options.maxHeight) { 553 // TODO: Add a class for this option to move the css declarations. 554 this.$popupContainer.css({ 555 'max-height': this.options.maxHeight + 'px', 556 'overflow-y': 'auto', 557 'overflow-x': 'hidden' 558 }); 559 } 560 561 if (this.options.widthSynchronizationMode !== 'never') { 562 this.$popupContainer.css('overflow-x', 'hidden'); 563 } 564 565 this.$popupContainer.on("touchstart click", function (e) { 566 e.stopPropagation(); 567 }); 568 569 this.$container.append(this.$popupContainer); 570 }, 571 572 synchronizeButtonAndPopupWidth: function () { 573 if (!this.$popupContainer || this.options.widthSynchronizationMode === 'never') { 574 return; 575 } 576 577 var buttonWidth = this.$button.outerWidth(); 578 switch (this.options.widthSynchronizationMode) { 579 case 'always': 580 this.$popupContainer.css('min-width', buttonWidth); 581 this.$popupContainer.css('max-width', buttonWidth); 582 break; 583 case 'ifPopupIsSmaller': 584 this.$popupContainer.css('min-width', buttonWidth); 585 break; 586 case 'ifPopupIsWider': 587 this.$popupContainer.css('max-width', buttonWidth); 588 break; 589 } 590 }, 591 592 /** 593 * Build the dropdown options and binds all necessary events. 594 * 595 * Uses createDivider and createOptionValue to create the necessary options. 596 */ 597 buildDropdownOptions: function () { 598 599 this.$select.children().each($.proxy(function (index, element) { 600 601 var $element = $(element); 602 // Support optgroups and options without a group simultaneously. 603 var tag = $element.prop('tagName') 604 .toLowerCase(); 605 606 if ($element.prop('value') === this.options.selectAllValue) { 607 return; 608 } 609 610 if (tag === 'optgroup') { 611 this.createOptgroup(element); 612 } 613 else if (tag === 'option') { 614 615 if ($element.data('role') === 'divider') { 616 this.createDivider(); 617 } 618 else { 619 this.createOptionValue(element, false); 620 } 621 622 } 623 624 // Other illegal tags will be ignored. 625 }, this)); 626 627 // Bind the change event on the dropdown elements. 628 $(this.$popupContainer).off('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]'); 629 $(this.$popupContainer).on('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]', $.proxy(function (event) { 630 var $target = $(event.target); 631 632 var checked = $target.prop('checked') || false; 633 var isSelectAllOption = $target.val() === this.options.selectAllValue; 634 635 // Apply or unapply the configured selected class. 636 if (this.options.selectedClass) { 637 if (checked) { 638 $target.closest('.multiselect-option') 639 .addClass(this.options.selectedClass); 640 } 641 else { 642 $target.closest('.multiselect-option') 643 .removeClass(this.options.selectedClass); 644 } 645 } 646 647 // Get the corresponding option. 648 var value = $target.val(); 649 var $option = this.getOptionByValue(value); 650 651 var $optionsNotThis = $('option', this.$select).not($option); 652 var $checkboxesNotThis = $('input', this.$container).not($target); 653 654 if (isSelectAllOption) { 655 656 if (checked) { 657 this.selectAll(this.options.selectAllJustVisible, true); 658 } 659 else { 660 this.deselectAll(this.options.selectAllJustVisible, true); 661 } 662 } 663 else { 664 if (checked) { 665 $option.prop('selected', true); 666 667 if (this.options.multiple) { 668 // Simply select additional option. 669 $option.prop('selected', true); 670 } 671 else { 672 // Unselect all other options and corresponding checkboxes. 673 if (this.options.selectedClass) { 674 $($checkboxesNotThis).closest('.dropdown-item').removeClass(this.options.selectedClass); 675 } 676 677 $($checkboxesNotThis).prop('checked', false); 678 $optionsNotThis.prop('selected', false); 679 680 // It's a single selection, so close. 681 this.$button.click(); 682 } 683 684 if (this.options.selectedClass === "active") { 685 $optionsNotThis.closest(".dropdown-item").css("outline", ""); 686 } 687 } 688 else { 689 // Unselect option. 690 $option.prop('selected', false); 691 } 692 693 // To prevent select all from firing onChange: #575 694 this.options.onChange($option, checked); 695 696 // Do not update select all or optgroups on select all change! 697 this.updateSelectAll(); 698 699 if (this.options.enableClickableOptGroups && this.options.multiple) { 700 this.updateOptGroups(); 701 } 702 } 703 704 this.$select.change(); 705 this.updateButtonText(); 706 707 if (this.options.preventInputChangeEvent) { 708 return false; 709 } 710 }, this)); 711 712 $('.multiselect-option', this.$popupContainer).off('mousedown'); 713 $('.multiselect-option', this.$popupContainer).on('mousedown', function (e) { 714 if (e.shiftKey) { 715 // Prevent selecting text by Shift+click 716 return false; 717 } 718 }); 719 720 $(this.$popupContainer).off('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group'); 721 $(this.$popupContainer).on('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group', $.proxy(function (event) { 722 event.stopPropagation(); 723 724 var $target = $(event.target); 725 726 if (event.shiftKey && this.options.multiple) { 727 if (!$target.is("input")) { // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431) 728 event.preventDefault(); 729 $target = $target.closest(".multiselect-option").find("input"); 730 $target.prop("checked", !$target.prop("checked")); 731 } 732 var checked = $target.prop('checked') || false; 733 734 if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range 735 var from = this.$popupContainer.find(".multiselect-option:visible").index($target.closest(".multiselect-option")); 736 var to = this.$popupContainer.find(".multiselect-option:visible").index(this.lastToggledInput.closest(".multiselect-option")); 737 738 if (from > to) { // Swap the indices 739 var tmp = to; 740 to = from; 741 from = tmp; 742 } 743 744 // Make sure we grab all elements since slice excludes the last index 745 ++to; 746 747 // Change the checkboxes and underlying options 748 var range = this.$popupContainer.find(".multiselect-option:not(.multiselect-filter-hidden)").slice(from, to).find("input"); 749 750 range.prop('checked', checked); 751 752 if (this.options.selectedClass) { 753 range.closest('.multiselect-option') 754 .toggleClass(this.options.selectedClass, checked); 755 } 756 757 for (var i = 0, j = range.length; i < j; i++) { 758 var $checkbox = $(range[i]); 759 760 var $option = this.getOptionByValue($checkbox.val()); 761 762 $option.prop('selected', checked); 763 } 764 } 765 766 // Trigger the select "change" event 767 $target.trigger("change"); 768 } 769 else if (!$target.is('input')) { 770 var $checkbox = $target.closest('.multiselect-option, .multiselect-all').find('.form-check-input'); 771 if ($checkbox.length > 0) { 772 if (this.options.multiple || !$checkbox.prop('checked')) { 773 $checkbox.prop('checked', !$checkbox.prop('checked')); 774 $checkbox.change(); 775 } 776 } 777 else if (this.options.enableClickableOptGroups && this.options.multiple && !$target.hasClass("caret-container")) { 778 var groupItem = $target; 779 if (!groupItem.hasClass("multiselect-group")) { 780 groupItem = $target.closest('.multiselect-group'); 781 } 782 $checkbox = groupItem.find(".form-check-input"); 783 if ($checkbox.length > 0) { 784 $checkbox.prop('checked', !$checkbox.prop('checked')); 785 $checkbox.change(); 786 } 787 } 788 789 event.preventDefault(); 790 } 791 792 // Remembers last clicked option 793 var $input = $target.closest(".multiselect-option").find("input[type='checkbox'], input[type='radio']"); 794 if ($input.length > 0) { 795 this.lastToggledInput = $target; 796 } 797 else { 798 this.lastToggledInput = null; 799 } 800 801 $target.blur(); 802 }, this)); 803 804 //Keyboard support. 805 this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function (event) { 806 var $items = $(this.$container).find(".multiselect-option:not(.disabled), .multiselect-group:not(.disabled), .multiselect-all").filter(":visible"); 807 var index = $items.index($items.filter(':focus')); 808 var $search = $('.multiselect-search', this.$container); 809 810 // keyCode 9 == Tab 811 if (event.keyCode === 9 && this.$container.hasClass('show')) { 812 this.$button.click(); 813 } 814 // keyCode 13 = Enter 815 else if (event.keyCode == 13) { 816 var $current = $items.eq(index); 817 setTimeout(function () { 818 $current.focus(); 819 }, 1); 820 } 821 // keyCode 38 = Arrow Up 822 else if (event.keyCode == 38) { 823 if (index == 0 && !$search.is(':focus')) { 824 setTimeout(function () { 825 $search.focus(); 826 }, 1); 827 } 828 } 829 // keyCode 40 = Arrow Down 830 else if (event.keyCode == 40) { 831 if ($search.is(':focus')) { 832 var $first = $items.eq(0); 833 setTimeout(function () { 834 $search.blur(); 835 $first.focus(); 836 }, 1); 837 } 838 else if (index == -1) { 839 setTimeout(function () { 840 $search.focus(); 841 }, 1); 842 } 843 } 844 }, this)); 845 846 if (this.options.enableClickableOptGroups && this.options.multiple) { 847 $(".multiselect-group input", this.$popupContainer).off("change"); 848 $(".multiselect-group input", this.$popupContainer).on("change", $.proxy(function (event) { 849 event.stopPropagation(); 850 851 var $target = $(event.target); 852 var checked = $target.prop('checked') || false; 853 854 var $item = $(event.target).closest('.dropdown-item'); 855 var $group = $item.nextUntil(".multiselect-group") 856 .not('.multiselect-filter-hidden') 857 .not('.disabled'); 858
859 var $inputs = $group.find("input"); 860 861 var $options = []; 862 863 if (this.options.selectedClass) { 864 if (checked) { 865 $item.addClass(this.options.selectedClass); 866 } 867 else { 868 $item.removeClass(this.options.selectedClass); 869 } 870 } 871 872 $.each($inputs, $.proxy(function (index, input) { 873 var $input = $(input); 874 var value = $input.val(); 875 var $option = this.getOptionByValue(value); 876 877 if (checked) { 878 $input.prop('checked', true); 879 $input.closest('.dropdown-item') 880 .addClass(this.options.selectedClass); 881 882 $option.prop('selected', true); 883 } 884 else { 885 $input.prop('checked', false); 886 $input.closest('.dropdown-item') 887 .removeClass(this.options.selectedClass); 888 889 $option.prop('selected', false); 890 } 891 892 $options.push(this.getOptionByValue(value)); 893 }, this)) 894 895 // Cannot use select or deselect here because it would call updateOptGroups again. 896 897 this.options.onChange($options, checked); 898 899 this.$select.change(); 900 this.updateButtonText(); 901 this.updateSelectAll(); 902 }, this)); 903 } 904 905 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 906 $(".multiselect-group .caret-container", this.$popupContainer).off("click"); 907 $(".multiselect-group .caret-container", this.$popupContainer).on("click", $.proxy(function (event) { 908 var $group = $(event.target).closest('.multiselect-group'); 909 var $inputs = $group.nextUntil(".multiselect-group") 910 .not('.multiselect-filter-hidden'); 911 912 var visible = true; 913 $inputs.each(function () { 914 visible = visible && !$(this).hasClass('multiselect-collapsible-hidden'); 915 }); 916 917 if (visible) { 918 $inputs.hide() 919 .addClass('multiselect-collapsible-hidden'); 920 } 921 else { 922 $inputs.show() 923 .removeClass('multiselect-collapsible-hidden'); 924 } 925 }, this)); 926 } 927 }, 928 929 /** 930 * Create a checkbox container with input and label based on given values 931 * @param {JQuery} $item 932 * @param {String} label 933 * @param {String} name 934 * @param {String} value 935 * @param {String} inputType 936 * @returns {JQuery} 937 */ 938 createCheckbox: function ($item, labelContent, name, value, title, inputType) { 939 var $wrapper = $('<span />'); 940 $wrapper.addClass("form-check"); 941 942 if (this.options.enableHTML && $(labelContent).length > 0) { 943 var $checkboxLabel = $('<label class="form-check-label" />'); 944 $checkboxLabel.html(labelContent); 945 $wrapper.append($checkboxLabel); 946 } 947 else { 948 var $checkboxLabel = $('<label class="form-check-label" />'); 949 $checkboxLabel.text(labelContent); 950 $wrapper.append($checkboxLabel); 951 } 952 953 var $checkbox = $('<input class="form-check-input"/>').attr('type', inputType); 954 $checkbox.val(value); 955 $wrapper.prepend($checkbox); 956 957 if (name) { 958 $checkbox.attr('name', name); 959 } 960 961 $item.prepend($wrapper); 962 $item.attr("title", title || labelContent); 963 964 return $checkbox; 965 }, 966 967 /** 968 * Create an option using the given select option. 969 * 970 * @param {jQuery} element 971 */ 972 createOptionValue: function (element, isGroupOption) { 973 var $element = $(element); 974 if ($element.is(':selected')) { 975 $element.prop('selected', true); 976 } 977
vendor: 37,895 bytes, lines 978-1893
978 // Support the label attribute on options. 979 var label = this.options.optionLabel(element); 980 var classes = this.options.optionClass(element); 981 var value = $element.val(); 982 var inputType = this.options.multiple ? "checkbox" : "radio"; 983 var title = $element.attr('title'); 984 985 var $option = $(this.options.templates.option); 986 $option.addClass(classes); 987 988 if (isGroupOption && this.options.indentGroupOptions) { 989 $option.addClass("multiselect-group-option-indented") 990 } 991 992 // Hide all children items when collapseOptGroupsByDefault is true 993 if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") { 994 $option.addClass("multiselect-collapsible-hidden"); 995 $option.hide(); 996 } 997 998 var name = this.options.checkboxName($element); 999 var $checkbox = this.createCheckbox($option, label, name, value, title, inputType); 1000 1001 var selected = $element.prop('selected') || false; 1002 1003 if (value === this.options.selectAllValue) { 1004 $option.addClass("multiselect-all"); 1005 $option.removeClass("multiselect-option"); 1006 $checkbox.parent().parent() 1007 .addClass('multiselect-all'); 1008 } 1009 1010 this.$popupContainer.append($option); 1011 1012 if ($element.is(':disabled')) { 1013 $checkbox.attr('disabled', 'disabled') 1014 .prop('disabled', true) 1015 .closest('.dropdown-item') 1016 .addClass('disabled'); 1017 } 1018 1019 $checkbox.prop('checked', selected); 1020 1021 if (selected && this.options.selectedClass) { 1022 $checkbox.closest('.dropdown-item') 1023 .addClass(this.options.selectedClass); 1024 } 1025 }, 1026 1027 /** 1028 * Creates a divider using the given select option. 1029 * 1030 * @param {jQuery} element 1031 */ 1032 createDivider: function (element) { 1033 var $divider = $(this.options.templates.divider); 1034 this.$popupContainer.append($divider); 1035 }, 1036 1037 /** 1038 * Creates an optgroup. 1039 * 1040 * @param {jQuery} group 1041 */ 1042 createOptgroup: function (group) { 1043 var $group = $(group); 1044 var label = $group.attr("label"); 1045 var value = $group.attr("value"); 1046 var title = $group.attr('title'); 1047 1048 var $groupOption = $("<span class='multiselect-group dropdown-item-text'></span>"); 1049 1050 if (this.options.enableClickableOptGroups && this.options.multiple) { 1051 $groupOption = $(this.options.templates.optionGroup); 1052 var $checkbox = this.createCheckbox($groupOption, label, null, value, title, "checkbox"); 1053 } 1054 else { 1055 if (this.options.enableHTML) { 1056 $groupOption.html(" " + label); 1057 } 1058 else { 1059 $groupOption.text(" " + label); 1060 } 1061 } 1062 1063 var classes = this.options.optionClass(group); 1064 $groupOption.addClass(classes); 1065 1066 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 1067 $groupOption.find('.form-check').addClass('d-inline-block'); 1068 $groupOption.append('<span class="caret-container dropdown-toggle pl-1"></span>'); 1069 } 1070 1071 if ($group.is(':disabled')) { 1072 $groupOption.addClass('disabled'); 1073 } 1074 1075 this.$popupContainer.append($groupOption); 1076 1077 $("option", group).each($.proxy(function ($, group) { 1078 this.createOptionValue(group, true); 1079 }, this)); 1080 }, 1081 1082 /** 1083 * Build the reset. 1084 * 1085 */ 1086 buildReset: function () { 1087 if (this.options.includeResetOption) { 1088 1089 // Check whether to add a divider after the reset. 1090 if (this.options.includeResetDivider) { 1091 var divider = $(this.options.templates.divider); 1092 divider.addClass("mt-0"); 1093 this.$popupContainer.prepend(divider); 1094 } 1095 1096 var $resetButton = $(this.options.templates.resetButton); 1097 1098 if (this.options.enableHTML) { 1099 $('button', $resetButton).html(this.options.resetText); 1100 } 1101 else { 1102 $('button', $resetButton).text(this.options.resetText); 1103 } 1104 1105 $('button', $resetButton).click($.proxy(function () { 1106 this.clearSelection(); 1107 }, this)); 1108 1109 this.$popupContainer.prepend($resetButton); 1110 } 1111 }, 1112 1113 /** 1114 * Build the select all. 1115 * 1116 * Checks if a select all has already been created. 1117 */ 1118 buildSelectAll: function () { 1119 if (typeof this.options.selectAllValue === 'number') { 1120 this.options.selectAllValue = this.options.selectAllValue.toString(); 1121 } 1122 1123 var alreadyHasSelectAll = this.hasSelectAll(); 1124 1125 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple 1126 && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { 1127 1128 // Check whether to add a divider after the select all. 1129 if (this.options.includeSelectAllDivider) { 1130 this.$popupContainer.prepend($(this.options.templates.divider)); 1131 } 1132 1133 var $option = $(this.options.templates.li || this.options.templates.option); 1134 var $checkbox = this.createCheckbox($option, this.options.selectAllText, this.options.selectAllName, this.options.selectAllValue, this.options.selectAllText, "checkbox"); 1135 1136 $option.addClass("multiselect-all"); 1137 $option.removeClass("multiselect-option"); 1138 $option.find(".form-check-label").addClass("font-weight-bold"); 1139 1140 this.$popupContainer.prepend($option); 1141 1142 $checkbox.prop('checked', false); 1143 } 1144 }, 1145 1146 /** 1147 * Builds the filter. 1148 */ 1149 buildFilter: function () { 1150 1151 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. 1152 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { 1153 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); 1154 1155 if (this.$select.find('option').length >= enableFilterLength) { 1156 1157 this.$filter = $(this.options.templates.filter); 1158 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); 1159 1160 // Handles optional filter clear button 1161 if (!this.options.includeFilterClearBtn) { 1162 this.$filter.find(".multiselect-search").attr("type", "text"); 1163 1164 // Remove clear button if the old design of the filter with input groups and separated clear button is used 1165 this.$filter.find(".multiselect-clear-filter").remove(); 1166 } 1167 else { 1168 // Firefox does not support a clear button in search inputs right now therefore it must be added manually 1169 if (this.isFirefox() && this.$filter.find(".multiselect-clear-filter").length === 0) { 1170 this.$filter.append("<i class='fas fa-times text-muted multiselect-clear-filter multiselect-moz-clear-filter'></i>"); 1171 } 1172 1173 this.$filter.find(".multiselect-clear-filter").on('click', $.proxy(function (event) { 1174 clearTimeout(this.searchTimeout); 1175 1176 this.query = ''; 1177 this.$filter.find('.multiselect-search').val(''); 1178 $('.dropdown-item', this.$popupContainer).show().removeClass('multiselect-filter-hidden'); 1179 1180 this.updateSelectAll(); 1181 1182 if (this.options.enableClickableOptGroups && this.options.multiple) { 1183 this.updateOptGroups(); 1184 } 1185 1186 }, this)); 1187 } 1188 1189 this.$popupContainer.prepend(this.$filter); 1190 1191 this.$filter.val(this.query).on('click', function (event) { 1192 event.stopPropagation(); 1193 }).on('input keydown', $.proxy(function (event) { 1194 // Cancel enter key default behaviour 1195 if (event.which === 13) { 1196 event.preventDefault(); 1197 } 1198 1199 if (this.isFirefox() && this.options.includeFilterClearBtn) { 1200 if (event.target.value) { 1201 this.$filter.find(".multiselect-moz-clear-filter").show(); 1202 } 1203 else { 1204 this.$filter.find(".multiselect-moz-clear-filter").hide(); 1205 } 1206 } 1207 1208 // This is useful to catch "keydown" events after the browser has updated the control. 1209 clearTimeout(this.searchTimeout); 1210 1211 this.searchTimeout = this.asyncFunction($.proxy(function () { 1212 1213 if (this.query !== event.target.value) { 1214 this.query = event.target.value; 1215 1216 var currentGroup, currentGroupVisible; 1217 $.each($('.multiselect-option, .multiselect-group', this.$popupContainer), $.proxy(function (index, element) { 1218 var value = $('input', element).length > 0 ? $('input', element).val() : ""; 1219 var text = $('.form-check-label', element).text(); 1220 1221 var filterCandidate = ''; 1222 if ((this.options.filterBehavior === 'text')) { 1223 filterCandidate = text; 1224 } 1225 else if ((this.options.filterBehavior === 'value')) { 1226 filterCandidate = value; 1227 } 1228 else if (this.options.filterBehavior === 'both') { 1229 filterCandidate = text + '\n' + value; 1230 } 1231 1232 if (value !== this.options.selectAllValue && text) { 1233 1234 // By default lets assume that element is not 1235 // interesting for this search. 1236 var showElement = false; 1237 1238 if (this.options.enableCaseInsensitiveFiltering) { 1239 filterCandidate = filterCandidate.toLowerCase(); 1240 this.query = this.query.toLowerCase(); 1241 } 1242 1243 if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') { 1244 var valueToMatch = filterCandidate.trim().substring(0, this.query.length); 1245 if (this.query.indexOf(valueToMatch) > -1) { 1246 showElement = true; 1247 } 1248 } 1249 else if (filterCandidate.indexOf(this.query) > -1) { 1250 showElement = true; 1251 } 1252 1253 // Toggle current element (group or group item) according to showElement boolean. 1254 if (!showElement) { 1255 $(element).css('display', 'none'); 1256 $(element).addClass('multiselect-filter-hidden'); 1257 } 1258 if (showElement) { 1259 $(element).css('display', 'block'); 1260 $(element).removeClass('multiselect-filter-hidden'); 1261 } 1262 1263 // Differentiate groups and group items. 1264 if ($(element).hasClass('multiselect-group')) { 1265 // Remember group status. 1266 currentGroup = element; 1267 currentGroupVisible = showElement; 1268 } 1269 else { 1270 // Show group name when at least one of its items is visible. 1271 if (showElement) { 1272 $(currentGroup).show() 1273 .removeClass('multiselect-filter-hidden'); 1274 } 1275 1276 // Show all group items when group name satisfies filter. 1277 if (!showElement && currentGroupVisible) { 1278 $(element).show() 1279 .removeClass('multiselect-filter-hidden'); 1280 } 1281 } 1282 } 1283 }, this)); 1284 } 1285 1286 this.updateSelectAll(); 1287 1288 if (this.options.enableClickableOptGroups && this.options.multiple) { 1289 this.updateOptGroups(); 1290 } 1291 1292 this.updatePopupPosition(); 1293 1294 this.options.onFiltering(event.target); 1295 1296 }, this), 300, this); 1297 }, this)); 1298 } 1299 } 1300 }, 1301 1302 /** 1303 * Builds the filter. 1304 */ 1305 buildButtons: function () { 1306 if (this.options.enableResetButton) { 1307 var $buttonGroup = $(this.options.templates.buttonGroup); 1308 this.$buttonGroupReset = $(this.options.templates.buttonGroupReset).text(this.options.resetButtonText); 1309 $buttonGroup.append(this.$buttonGroupReset); 1310 this.$popupContainer.prepend($buttonGroup); 1311 1312 // We save all options that were previously selected. 1313 this.defaultSelection = {}; 1314 $('option', this.$select).each($.proxy(function(index, element) { 1315 var $option = $(element); 1316 this.defaultSelection[$option.val()] = $option.prop('selected'); 1317 }, this)); 1318 1319 this.$buttonGroupReset.on('click', $.proxy(function(event) { 1320 $('option', this.$select).each($.proxy(function(index, element) { 1321 var $option = $(element); 1322 $option.prop('selected', this.defaultSelection[$option.val()]); 1323 }, this)); 1324 this.refresh(); 1325 1326 if (this.options.enableFiltering) { 1327 this.$filter.trigger('keydown'); 1328 $('input', this.$filter).val(''); 1329 } 1330 }, this)); 1331 } 1332 }, 1333 1334 updatePopupPosition: function() { 1335 // prevent gaps between popup and select when filter is used (#1199) 1336 var transformMatrix = this.$popupContainer.css("transform"); 1337 var matrixType = transformMatrix.substring(0, transformMatrix.indexOf('(')); 1338 var values = transformMatrix.substring(transformMatrix.indexOf('(') + 1, transformMatrix.length - 1); 1339 var valuesArray = values.split(','); 1340 1341 var valueIndex = 5; 1342 if(matrixType === "matrix3d") { 1343 valueIndex = 13; 1344 } 1345 1346 var yTransformation = valuesArray[valueIndex]; 1347 // Need to check to avoid errors when testing and in some other situations. 1348 yTransformation = typeof yTransformation === 'undefined' ? 0 : yTransformation.trim(); 1349 if (yTransformation < 0) { 1350 yTransformation = this.$popupContainer.css("height").replace('px', '') * -1; 1351 valuesArray[valueIndex] = yTransformation; 1352 transformMatrix = matrixType + '(' + valuesArray.join(',') + ')'; 1353 this.$popupContainer.css("transform", transformMatrix); 1354 } 1355 }, 1356 1357 /** 1358 * Unbinds the whole plugin. 1359 */ 1360 destroy: function () { 1361 this.$container.remove(); 1362 this.$select.unwrap(); 1363 this.$select.show(); 1364 1365 // reset original state 1366 this.$select.prop('disabled', this.options.wasDisabled); 1367 1368 this.$select.data('multiselect', null); 1369 }, 1370 1371 /** 1372 * Refreshs the multiselect based on the selected options of the select. 1373 */ 1374 refresh: function () { 1375 var inputs = {}; 1376 $('.multiselect-option input', this.$popupContainer).each(function () { 1377 inputs[$(this).val()] = $(this); 1378 }); 1379 1380 $('option', this.$select).each($.proxy(function (index, element) { 1381 var $elem = $(element); 1382 var $input = inputs[$(element).val()]; 1383 1384 if ($elem.is(':selected')) { 1385 $input.prop('checked', true); 1386 1387 if (this.options.selectedClass) { 1388 $input.closest('.multiselect-option') 1389 .addClass(this.options.selectedClass); 1390 } 1391 } 1392 else { 1393 $input.prop('checked', false); 1394 1395 if (this.options.selectedClass) { 1396 $input.closest('.multiselect-option') 1397 .removeClass(this.options.selectedClass); 1398 } 1399 } 1400 1401 if ($elem.is(":disabled")) { 1402 $input.attr('disabled', 'disabled') 1403 .prop('disabled', true) 1404 .closest('.multiselect-option') 1405 .addClass('disabled'); 1406 } 1407 else { 1408 $input.prop('disabled', false) 1409 .closest('.multiselect-option') 1410 .removeClass('disabled'); 1411 } 1412 }, this)); 1413 1414 this.updateButtonText(); 1415 this.updateSelectAll(); 1416 1417 if (this.options.enableClickableOptGroups && this.options.multiple) { 1418 this.updateOptGroups(); 1419 } 1420 }, 1421 1422 /** 1423 * Select all options of the given values. 1424 * 1425 * If triggerOnChange is set to true, the on change event is triggered if 1426 * and only if one value is passed. 1427 * 1428 * @param {Array} selectValues 1429 * @param {Boolean} triggerOnChange 1430 */ 1431 select: function (selectValues, triggerOnChange) { 1432 if (!$.isArray(selectValues)) { 1433 selectValues = [selectValues]; 1434 } 1435 1436 for (var i = 0; i < selectValues.length; i++) { 1437 var value = selectValues[i]; 1438 1439 if (value === null || value === undefined) { 1440 continue; 1441 } 1442 1443 var $option = this.getOptionByValue(value); 1444 var $checkbox = this.getInputByValue(value); 1445 1446 if ($option === undefined || $checkbox === undefined) { 1447 continue; 1448 } 1449 1450 if (this.options.selectedClass) { 1451 $checkbox.closest('.dropdown-item') 1452 .addClass(this.options.selectedClass); 1453 } 1454 1455 $checkbox.prop('checked', true); 1456 $option.prop('selected', true); 1457 1458 if (!this.options.multiple) { 1459 var $checkboxesNotThis = $('input', this.$container).not($checkbox); 1460 $($checkboxesNotThis).prop('checked', false); 1461 $($checkboxesNotThis).closest('.multiselect-option').removeClass("active") 1462 1463 var $optionsNotThis = $('option', this.$select).not($option); 1464 $optionsNotThis.prop('selected', false); 1465 } 1466 1467 if (triggerOnChange) { 1468 this.options.onChange($option, true); 1469 } 1470 } 1471 1472 this.updateButtonText(); 1473 this.updateSelectAll(); 1474 1475 if (this.options.enableClickableOptGroups && this.options.multiple) { 1476 this.updateOptGroups(); 1477 } 1478 }, 1479 1480 /** 1481 * Clears all selected items. 1482 */ 1483 clearSelection: function () { 1484 this.deselectAll(false); 1485 this.updateButtonText(); 1486 this.updateSelectAll(); 1487 1488 if (this.options.enableClickableOptGroups && this.options.multiple) { 1489 this.updateOptGroups(); 1490 } 1491 }, 1492 1493 /** 1494 * Deselects all options of the given values. 1495 * 1496 * If triggerOnChange is set to true, the on change event is triggered, if 1497 * and only if one value is passed. 1498 * 1499 * @param {Array} deselectValues 1500 * @param {Boolean} triggerOnChange 1501 */ 1502 deselect: function (deselectValues, triggerOnChange) { 1503 if (!this.options.multiple) { 1504 // In single selection mode at least on option needs to be selected 1505 return; 1506 } 1507 1508 if (!$.isArray(deselectValues)) { 1509 deselectValues = [deselectValues]; 1510 } 1511 1512 for (var i = 0; i < deselectValues.length; i++) { 1513 var value = deselectValues[i]; 1514 1515 if (value === null || value === undefined) { 1516 continue; 1517 } 1518 1519 var $option = this.getOptionByValue(value); 1520 var $checkbox = this.getInputByValue(value); 1521 1522 if ($option === undefined || $checkbox === undefined) { 1523 continue; 1524 } 1525 1526 if (this.options.selectedClass) { 1527 $checkbox.closest('.dropdown-item') 1528 .removeClass(this.options.selectedClass); 1529 } 1530 1531 $checkbox.prop('checked', false); 1532 $option.prop('selected', false); 1533 1534 if (triggerOnChange) { 1535 this.options.onChange($option, false); 1536 } 1537 } 1538 1539 this.updateButtonText(); 1540 this.updateSelectAll(); 1541 1542 if (this.options.enableClickableOptGroups && this.options.multiple) { 1543 this.updateOptGroups(); 1544 } 1545 }, 1546 1547 /** 1548 * Selects all enabled & visible options. 1549 * 1550 * If justVisible is true or not specified, only visible options are selected. 1551 * 1552 * @param {Boolean} justVisible 1553 * @param {Boolean} triggerOnSelectAll 1554 */ 1555 selectAll: function (justVisible, triggerOnSelectAll) { 1556 if (!this.options.multiple) { 1557 // In single selection mode only one option can be selected at a time 1558 return; 1559 } 1560 1561 // Record all changes, i.e., options selected that were not selected before. 1562 var selected = []; 1563 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1564 1565 if (justVisible) { 1566 var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer); 1567 $('input:enabled', visibleOptions).prop('checked', true); 1568 visibleOptions.addClass(this.options.selectedClass); 1569 1570 $('input:enabled', visibleOptions).each($.proxy(function (index, element) { 1571 var value = $(element).val(); 1572 var option = this.getOptionByValue(value); 1573 if (!$(option).prop('selected')) { 1574 selected.push(option); 1575 } 1576 $(option).prop('selected', true); 1577 }, this)); 1578 } 1579 else { 1580 var allOptions = $(".multiselect-option:not(.disabled)", this.$popupContainer); 1581 $('input:enabled', allOptions).prop('checked', true); 1582 allOptions.addClass(this.options.selectedClass); 1583 1584 $('input:enabled', allOptions).each($.proxy(function (index, element) { 1585 var value = $(element).val(); 1586 var option = this.getOptionByValue(value); 1587 if (!$(option).prop('selected')) { 1588 selected.push(option); 1589 } 1590 $(option).prop('selected', true); 1591 }, this)); 1592 } 1593 1594 $('.multiselect-option input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', true); 1595 1596 if (this.options.enableClickableOptGroups && this.options.multiple) { 1597 this.updateOptGroups(); 1598 } 1599 1600 this.updateButtonText(); 1601 this.updateSelectAll(); 1602 1603 if (triggerOnSelectAll) { 1604 this.options.onSelectAll(selected); 1605 } 1606 }, 1607 1608 /** 1609 * Deselects all options. 1610 * 1611 * If justVisible is true or not specified, only visible options are deselected. 1612 * 1613 * @param {Boolean} justVisible 1614 */ 1615 deselectAll: function (justVisible, triggerOnDeselectAll) { 1616 if (!this.options.multiple) { 1617 // In single selection mode at least on option needs to be selected 1618 return; 1619 } 1620 1621 // Record changes, i.e., those options that are deselected but were not deselected before. 1622 var deselected = []; 1623 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1624 1625 if (justVisible) { 1626 var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer); 1627 $('input[type="checkbox"]:enabled', visibleOptions).prop('checked', false); 1628 visibleOptions.removeClass(this.options.selectedClass); 1629 1630 $('input[type="checkbox"]:enabled', visibleOptions).each($.proxy(function (index, element) { 1631 var value = $(element).val(); 1632 var option = this.getOptionByValue(value); 1633 if ($(option).prop('selected')) { 1634 deselected.push(option); 1635 } 1636 $(option).prop('selected', false); 1637 }, this)); 1638 } 1639 else { 1640 var allOptions = $(".multiselect-option:not(.disabled):not(.multiselect-group)", this.$popupContainer); 1641 $('input[type="checkbox"]:enabled', allOptions).prop('checked', false); 1642 allOptions.removeClass(this.options.selectedClass); 1643 1644 $('input[type="checkbox"]:enabled', allOptions).each($.proxy(function (index, element) { 1645 var value = $(element).val(); 1646 var option = this.getOptionByValue(value); 1647 if ($(option).prop('selected')) { 1648 deselected.push(option); 1649 } 1650 $(option).prop('selected', false); 1651 }, this)); 1652 } 1653 1654 $('.multiselect-all input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', false); 1655 1656 if (this.options.enableClickableOptGroups && this.options.multiple) { 1657 this.updateOptGroups(); 1658 } 1659 1660 this.updateButtonText(); 1661 this.updateSelectAll(); 1662 1663 if (triggerOnDeselectAll) { 1664 this.options.onDeselectAll(deselected); 1665 } 1666 }, 1667 1668 /** 1669 * Rebuild the plugin. 1670 * 1671 * Rebuilds the dropdown, the filter and the select all option. 1672 */ 1673 rebuild: function () { 1674 this.$popupContainer.html(''); 1675 1676 // Important to distinguish between radios and checkboxes. 1677 this.options.multiple = this.$select.attr('multiple') === "multiple"; 1678 1679 this.buildSelectAll(); 1680 this.buildDropdownOptions(); 1681 this.buildFilter(); 1682 this.buildButtons(); 1683 1684 this.updateButtonText(); 1685 this.updateSelectAll(true); 1686 1687 if (this.options.enableClickableOptGroups && this.options.multiple) { 1688 this.updateOptGroups(); 1689 } 1690 1691 if (this.options.disableIfEmpty) { 1692 if ($('option', this.$select).length <= 0) { 1693 if (!this.$select.prop('disabled')) { 1694 this.disable(true); 1695 } 1696 } 1697 else if (this.$select.data("disabled-by-option")) { 1698 this.enable(); 1699 } 1700 } 1701 1702 if (this.options.dropRight) { 1703 this.$container.addClass('dropright'); 1704 } 1705 else if (this.options.dropUp) { 1706 this.$container.addClass('dropup'); 1707 } 1708 1709 if (this.options.widthSynchronizationMode !== 'never') { 1710 this.synchronizeButtonAndPopupWidth(); 1711 } 1712 }, 1713 1714 /** 1715 * The provided data will be used to build the dropdown. 1716 */ 1717 dataprovider: function (dataprovider) { 1718 1719 var groupCounter = 0; 1720 var $select = this.$select.empty(); 1721 1722 $.each(dataprovider, function (index, option) { 1723 var $tag; 1724 1725 if ($.isArray(option.children)) { // create optiongroup tag 1726 groupCounter++; 1727 1728 $tag = $('<optgroup/>').attr({ 1729 label: option.label || 'Group ' + groupCounter, 1730 disabled: !!option.disabled, 1731 value: option.value 1732 }); 1733 1734 forEach(option.children, function (subOption) { // add children option tags 1735 var attributes = { 1736 value: subOption.value, 1737 label: subOption.label || subOption.value, 1738 title: subOption.title, 1739 selected: !!subOption.selected, 1740 disabled: !!subOption.disabled 1741 }; 1742 1743 //Loop through attributes object and add key-value for each attribute 1744 for (var key in subOption.attributes) { 1745 attributes['data-' + key] = subOption.attributes[key]; 1746 } 1747 //Append original attributes + new data attributes to option 1748 $tag.append($('<option/>').attr(attributes)); 1749 }); 1750 } 1751 else { 1752 1753 var attributes = { 1754 'value': option.value, 1755 'label': option.label || option.value, 1756 'title': option.title, 1757 'class': option['class'], 1758 'selected': !!option['selected'], 1759 'disabled': !!option['disabled'] 1760 }; 1761 //Loop through attributes object and add key-value for each attribute 1762 for (var key in option.attributes) { 1763 attributes['data-' + key] = option.attributes[key]; 1764 } 1765 //Append original attributes + new data attributes to option 1766 $tag = $('<option/>').attr(attributes); 1767 1768 $tag.text(option.label || option.value); 1769 } 1770 1771 $select.append($tag); 1772 }); 1773 1774 this.rebuild(); 1775 }, 1776 1777 /** 1778 * Enable the multiselect. 1779 */ 1780 enable: function () { 1781 this.$select.prop('disabled', false); 1782 this.$button.prop('disabled', false) 1783 .removeClass('disabled'); 1784 1785 this.updateButtonText(); 1786 }, 1787 1788 /** 1789 * Disable the multiselect. 1790 */ 1791 disable: function (disableByOption) { 1792 this.$select.prop('disabled', true); 1793 this.$button.prop('disabled', true) 1794 .addClass('disabled'); 1795 1796 if (disableByOption) { 1797 this.$select.data("disabled-by-option", true); 1798 } 1799 else { 1800 this.$select.data("disabled-by-option", null); 1801 } 1802 1803 this.updateButtonText(); 1804 }, 1805 1806 /** 1807 * Set the options. 1808 * 1809 * @param {Array} options 1810 */ 1811 setOptions: function (options) { 1812 this.options = this.mergeOptions(options); 1813 }, 1814 1815 /** 1816 * Merges the given options with the default options. 1817 * 1818 * @param {Array} options 1819 * @returns {Array} 1820 */ 1821 mergeOptions: function (options) { 1822 return $.extend(true, {}, this.defaults, this.options, options); 1823 }, 1824 1825 /** 1826 * Checks whether a select all checkbox is present. 1827 * 1828 * @returns {Boolean} 1829 */ 1830 hasSelectAll: function () { 1831 return $('.multiselect-all', this.$popupContainer).length > 0; 1832 }, 1833 1834 /** 1835 * Update opt groups. 1836 */ 1837 updateOptGroups: function () { 1838 var $groups = $('.multiselect-group', this.$popupContainer) 1839 var selectedClass = this.options.selectedClass; 1840 1841 $groups.each(function () { 1842 var $options = $(this).nextUntil('.multiselect-group') 1843 .not('.multiselect-filter-hidden') 1844 .not('.disabled'); 1845 1846 var checked = true; 1847 $options.each(function () { 1848 var $input = $('input', this); 1849 1850 if (!$input.prop('checked')) { 1851 checked = false; 1852 } 1853 }); 1854 1855 if (selectedClass) { 1856 if (checked) { 1857 $(this).addClass(selectedClass); 1858 } 1859 else { 1860 $(this).removeClass(selectedClass); 1861 } 1862 } 1863 1864 $('input', this).prop('checked', checked); 1865 }); 1866 }, 1867 1868 /** 1869 * Updates the select all checkbox based on the currently displayed and selected checkboxes. 1870 */ 1871 updateSelectAll: function (notTriggerOnSelectAll) { 1872 if (this.hasSelectAll()) { 1873 var allBoxes = $(".multiselect-option:not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$popupContainer); 1874 var allBoxesLength = allBoxes.length; 1875 var checkedBoxesLength = allBoxes.filter(":checked").length; 1876 var selectAllItem = $(".multiselect-all", this.$popupContainer); 1877 var selectAllInput = selectAllItem.find("input"); 1878 1879 if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) { 1880 selectAllInput.prop("checked", true); 1881 selectAllItem.addClass(this.options.selectedClass); 1882 } 1883 else { 1884 selectAllInput.prop("checked", false); 1885 selectAllItem.removeClass(this.options.selectedClass); 1886 } 1887 } 1888 }, 1889 1890 /** 1891 * Update the button text and its title based on the currently selected options. 1892 */ 1893 updateButtonText: function () {
1894 var options = this.getSelected(); 1895 1896 // First update the displayed button text. 1897 if (this.options.enableHTML) { 1898 $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select)); 1899 } 1900 else { 1901 $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select)); 1902 } 1903 1904 // Now update the title attribute of the button. 1905 $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); 1906 this.$button.trigger('change'); 1907 }, 1908 1909 /** 1910 * Get all selected options. 1911 * 1912 * @returns {jQUery} 1913 */ 1914 getSelected: function () { 1915 return $('option', this.$select).filter(":selected"); 1916 }, 1917 1918 /** 1919 * Gets a select option by its value. 1920 * 1921 * @param {String} value 1922 * @returns {jQuery} 1923 */ 1924 getOptionByValue: function (value) { 1925 1926 var options = $('option', this.$select); 1927 var valueToCompare = value.toString(); 1928 1929 for (var i = 0; i < options.length; i = i + 1) { 1930 var option = options[i]; 1931 if (option.value === valueToCompare) { 1932 return $(option); 1933 } 1934 } 1935 }, 1936 1937 /** 1938 * Get the input (radio/checkbox) by its value. 1939 * 1940 * @param {String} value 1941 * @returns {jQuery} 1942 */ 1943 getInputByValue: function (value) { 1944 1945 var checkboxes = $('.multiselect-option input:not(.multiselect-search)', this.$popupContainer); 1946 var valueToCompare = value.toString(); 1947 1948 for (var i = 0; i < checkboxes.length; i = i + 1) { 1949 var checkbox = checkboxes[i]; 1950 if (checkbox.value === valueToCompare) { 1951 return $(checkbox); 1952 } 1953 } 1954 }, 1955 1956 /** 1957 * Used for knockout integration. 1958 */ 1959 updateOriginalOptions: function () { 1960 this.originalOptions = this.$select.clone()[0].options; 1961 }, 1962 1963 asyncFunction: function (callback, timeout, self) { 1964 var args = Array.prototype.slice.call(arguments, 3); 1965 return setTimeout(function () { 1966 callback.apply(self || window, args); 1967 }, timeout); 1968 }, 1969 1970 setAllSelectedText: function (allSelectedText) { 1971 this.options.allSelectedText = allSelectedText; 1972 this.updateButtonText(); 1973 }, 1974 1975 isFirefox: function () { 1976 var firefoxIdentifier = 'firefox'; 1977 var valueNotFoundIndex = -1; 1978 1979 if (navigator && navigator.userAgent) { 1980 return navigator.userAgent.toLocaleLowerCase().indexOf(firefoxIdentifier) > valueNotFoundIndex; 1981 } 1982 1983 return false; 1984 } 1985 }; 1986 1987 $.fn.multiselect = function (option, parameter, extraOptions) { 1988 return this.each(function () { 1989 var data = $(this).data('multiselect'); 1990 var options = typeof option === 'object' && option; 1991 1992 // Initialize the multiselect. 1993 if (!data) { 1994 data = new Multiselect(this, options); 1995 $(this).data('multiselect', data); 1996 } 1997 1998 // Call multiselect method. 1999 if (typeof option === 'string') { 2000 data[option](parameter, extraOptions); 2001 2002 if (option === 'destroy') { 2003 $(this).data('multiselect', false); 2004 } 2005 } 2006 }); 2007 }; 2008 2009 $.fn.multiselect.Constructor = Multiselect; 2010 2011 $(function () { 2012 $("select[data-role=multiselect]").multiselect(); 2013 }); 2014 2015});
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.