vendor: 10,647 bytes, lines 1-240
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 223 this.updateButtonText(); 224 this.updateSelectAll(true); 225 226 if (this.options.enableClickableOptGroups && this.options.multiple) { 227 this.updateOptGroups(); 228 } 229 230 this.options.wasDisabled = this.$select.prop('disabled'); 231 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 232 this.disable(); 233 } 234 235 this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container); 236 this.$select.prop('tabindex', '-1'); 237 238 if (this.options.widthSynchronizationMode !== 'never') { 239 this.synchronizeButtonAndPopupWidth(); 240 }
vendor: 4,833 bytes, lines 241-380
241 242 this.options.onInitialized(this.$select, this.$container); 243 } 244 245 Multiselect.prototype = { 246 247 defaults: { 248 /** 249 * Default text function will either print 'None selected' in case no 250 * option is selected or a list of the selected options up to a length 251 * of 3 selected options. 252 * 253 * @param {jQuery} options 254 * @param {jQuery} select 255 * @returns {String} 256 */ 257 buttonText: function (selectedOptions, select) { 258 if (this.disabledText.length > 0 && select.prop('disabled')) { 259 return this.disabledText; 260 } 261 else if (selectedOptions.length === 0) { 262 return this.nonSelectedText; 263 } 264 else if (this.allSelectedText 265 && selectedOptions.length === $('option', $(select)).length 266 && $('option', $(select)).length !== 1 267 && this.multiple) { 268 269 if (this.selectAllNumber) { 270 return this.allSelectedText + ' (' + selectedOptions.length + ')'; 271 } 272 else { 273 return this.allSelectedText; 274 } 275 } 276 else if (this.numberDisplayed != 0 && selectedOptions.length > this.numberDisplayed) { 277 return selectedOptions.length + ' ' + this.nSelectedText; 278 } 279 else { 280 var selected = ''; 281 var delimiter = this.delimiterText; 282 283 selectedOptions.each(function () { 284 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 285 selected += label + delimiter; 286 }); 287 288 return selected.substr(0, selected.length - this.delimiterText.length); 289 } 290 }, 291 /** 292 * Updates the title of the button similar to the buttonText function. 293 * 294 * @param {jQuery} options 295 * @param {jQuery} select 296 * @returns {@exp;selected@call;substr} 297 */ 298 buttonTitle: function (options, select) { 299 if (options.length === 0) { 300 return this.nonSelectedText; 301 } 302 else { 303 var selected = ''; 304 var delimiter = this.delimiterText; 305 306 options.each(function () { 307 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); 308 selected += label + delimiter; 309 }); 310 return selected.substr(0, selected.length - this.delimiterText.length); 311 } 312 }, 313 checkboxName: function (option) { 314 return false; // no checkbox name 315 }, 316 /** 317 * Create a label. 318 * 319 * @param {jQuery} element 320 * @returns {String} 321 */ 322 optionLabel: function (element) { 323 return $(element).attr('label') || $(element).text(); 324 }, 325 /** 326 * Create a class. 327 * 328 * @param {jQuery} element 329 * @returns {String} 330 */ 331 optionClass: function (element) { 332 return $(element).attr('class') || ''; 333 }, 334 /** 335 * Triggered on change of the multiselect. 336 * 337 * Not triggered when selecting/deselecting options manually. 338 * 339 * @param {jQuery} option 340 * @param {Boolean} checked 341 */ 342 onChange: function (option, checked) { 343 344 }, 345 /** 346 * Triggered when the dropdown is shown. 347 * 348 * @param {jQuery} event 349 */ 350 onDropdownShow: function (event) { 351 352 }, 353 /** 354 * Triggered when the dropdown is hidden. 355 * 356 * @param {jQuery} event 357 */ 358 onDropdownHide: function (event) { 359 360 }, 361 /** 362 * Triggered after the dropdown is shown. 363 * 364 * @param {jQuery} event 365 */ 366 onDropdownShown: function (event) { 367 368 }, 369 /** 370 * Triggered after the dropdown is hidden. 371 * 372 * @param {jQuery} event 373 */ 374 onDropdownHidden: function (event) { 375 376 }, 377 /** 378 * Triggered on select all. 379 */ 380 onSelectAll: function (
380) { 381 382 }, 383 /** 384 * Triggered on deselect all. 385 */ 386 onDeselectAll: function () { 387 388 }, 389 /** 390 * Triggered after initializing. 391 * 392 * @param {jQuery} $select 393 * @param {jQuery} $container 394 */ 395 onInitialized: function ($select, $container) { 396 397 }, 398 /** 399 * Triggered on filtering. 400 * 401 * @param {jQuery} $filter 402 */ 403 onFiltering: function ($filter) { 404 405 }, 406 enableHTML: false, 407 buttonClass: 'form-select', 408 inheritClass: false, 409 buttonWidth: 'auto', 410 buttonContainer: '<div class="btn-group" />', 411 dropRight: false, 412 dropUp: false, 413 selectedClass: 'active', 414 // Maximum height of the dropdown menu. 415 // If maximum height is exceeded a scrollbar will be displayed. 416 maxHeight: false, 417 includeSelectAllOption: false, 418 includeSelectAllIfMoreThan: 0, 419 selectAllText: ' Select all', 420 selectAllValue: 'multiselect-all', 421 selectAllName: false, 422 selectAllNumber: true, 423 selectAllJustVisible: true, 424 enableFiltering: false, 425 enableCaseInsensitiveFiltering: false, 426 enableFullValueFiltering: false, 427 enableClickableOptGroups: false, 428 enableCollapsibleOptGroups: false, 429 collapseOptGroupsByDefault: false, 430 filterPlaceholder: 'Search', 431 // possible options: 'text', 'value', 'both' 432 filterBehavior: 'text', 433 includeFilterClearBtn: true, 434 preventInputChangeEvent: false, 435 nonSelectedText: 'None selected', 436 nSelectedText: 'selected', 437 allSelectedText: 'All selected', 438 numberDisplayed: 3, 439 disableIfEmpty: false, 440 disabledText: '', 441 delimiterText: ', ', 442 includeResetOption: false, 443 includeResetDivider: false,
vendor: 17,563 bytes, lines 444-851
444 resetText: 'Reset', 445 indentGroupOptions: true, 446 // possible options: 'never', 'always', 'ifPopupIsSmaller', 'ifPopupIsWider' 447 widthSynchronizationMode: 'never', 448 buttonTextAlignment: 'center', 449 templates: { 450 button: '<button type="button" class="multiselect dropdown-toggle" data-bs-toggle="dropdown"><span class="multiselect-selected-text"></span></button>', 451 popupContainer: '<div class="multiselect-container dropdown-menu"></div>', 452 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>', 453 option: '<button type="button" class="multiselect-option dropdown-item"></button>', 454 divider: '<div class="dropdown-divider"></div>', 455 optionGroup: '<button type="button" class="multiselect-group dropdown-item"></button>', 456 resetButton: '<div class="multiselect-reset text-center p-2"><button type="button" class="btn btn-sm btn-block btn-outline-secondary"></button></div>' 457 } 458 }, 459 460 constructor: Multiselect, 461 462 /** 463 * Builds the container of the multiselect. 464 */ 465 buildContainer: function () { 466 this.$container = $(this.options.buttonContainer); 467 if (this.options.widthSynchronizationMode !== 'never') { 468 this.$container.on('show.bs.dropdown', $.proxy(function () { 469 // the width needs to be synchronized again in case the width of the button changed in between 470 this.synchronizeButtonAndPopupWidth(); 471 this.options.onDropdownShow(); 472 }, this)); 473 } 474 else { 475 this.$container.on('show.bs.dropdown', this.options.onDropdownShow); 476 } 477 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); 478 this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); 479 this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); 480 }, 481 482 /** 483 * Builds the button of the multiselect. 484 */ 485 buildButton: function () { 486 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); 487 if (this.$select.attr('class') && this.options.inheritClass) { 488 this.$button.addClass(this.$select.attr('class')); 489 } 490 // Adopt active state. 491 if (this.$select.prop('disabled')) { 492 this.disable(); 493 } 494 else { 495 this.enable(); 496 } 497 498 // Manually add button width if set. 499 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { 500 this.$button.css({ 501 'width': '100%' //this.options.buttonWidth, 502 }); 503 this.$container.css({ 504 'width': this.options.buttonWidth 505 }); 506 } 507 508 if (this.options.buttonTextAlignment) { 509 switch (this.options.buttonTextAlignment) { 510 case 'left': 511 this.$button.addClass('text-left'); 512 break; 513 case 'center': 514 this.$button.addClass('text-center'); 515 break; 516 case 'right': 517 this.$button.addClass('text-right'); 518 break; 519 } 520 } 521 522 // Keep the tab index from the select. 523 var tabindex = this.$select.attr('tabindex'); 524 if (tabindex) { 525 this.$button.attr('tabindex', tabindex); 526 } 527 528 this.$container.prepend(this.$button); 529 }, 530 531 /** 532 * Builds the popup container representing the dropdown menu. 533 */ 534 buildDropdown: function () { 535 536 // Build popup container. 537 this.$popupContainer = $(this.options.templates.popupContainer); 538 539 if (this.options.dropRight) { 540 this.$container.addClass('dropright'); 541 } 542 else if (this.options.dropUp) { 543 this.$container.addClass("dropup"); 544 } 545 546 // Set max height of dropdown menu to activate auto scrollbar. 547 if (this.options.maxHeight) { 548 // TODO: Add a class for this option to move the css declarations. 549 this.$popupContainer.css({ 550 'max-height': this.options.maxHeight + 'px', 551 'overflow-y': 'auto', 552 'overflow-x': 'hidden' 553 }); 554 } 555 556 if (this.options.widthSynchronizationMode !== 'never') { 557 this.$popupContainer.css('overflow-x', 'hidden'); 558 } 559 560 this.$popupContainer.on("touchstart click", function (e) { 561 e.stopPropagation(); 562 }); 563 564 this.$container.append(this.$popupContainer); 565 }, 566 567 synchronizeButtonAndPopupWidth: function () { 568 if (!this.$popupContainer || this.options.widthSynchronizationMode === 'never') { 569 return; 570 } 571 572 var buttonWidth = this.$button.outerWidth(); 573 switch (this.options.widthSynchronizationMode) { 574 case 'always': 575 this.$popupContainer.css('min-width', buttonWidth); 576 this.$popupContainer.css('max-width', buttonWidth); 577 break; 578 case 'ifPopupIsSmaller': 579 this.$popupContainer.css('min-width', buttonWidth); 580 break; 581 case 'ifPopupIsWider': 582 this.$popupContainer.css('max-width', buttonWidth); 583 break; 584 } 585 }, 586 587 /** 588 * Build the dropdown options and binds all necessary events. 589 * 590 * Uses createDivider and createOptionValue to create the necessary options. 591 */ 592 buildDropdownOptions: function () { 593 594 this.$select.children().each($.proxy(function (index, element) { 595 596 var $element = $(element); 597 // Support optgroups and options without a group simultaneously. 598 var tag = $element.prop('tagName') 599 .toLowerCase(); 600 601 if ($element.prop('value') === this.options.selectAllValue) { 602 return; 603 } 604 605 if (tag === 'optgroup') { 606 this.createOptgroup(element); 607 } 608 else if (tag === 'option') { 609 610 if ($element.data('role') === 'divider') { 611 this.createDivider(); 612 } 613 else { 614 this.createOptionValue(element, false); 615 } 616 617 } 618 619 // Other illegal tags will be ignored. 620 }, this)); 621 622 // Bind the change event on the dropdown elements. 623 $(this.$popupContainer).off('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]'); 624 $(this.$popupContainer).on('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]', $.proxy(function (event) { 625 var $target = $(event.target); 626 627 var checked = $target.prop('checked') || false; 628 var isSelectAllOption = $target.val() === this.options.selectAllValue; 629 630 // Apply or unapply the configured selected class. 631 if (this.options.selectedClass) { 632 if (checked) { 633 $target.closest('.multiselect-option') 634 .addClass(this.options.selectedClass); 635 } 636 else { 637 $target.closest('.multiselect-option') 638 .removeClass(this.options.selectedClass); 639 } 640 } 641 642 // Get the corresponding option. 643 var value = $target.val(); 644 var $option = this.getOptionByValue(value); 645 646 var $optionsNotThis = $('option', this.$select).not($option); 647 var $checkboxesNotThis = $('input', this.$container).not($target); 648 649 if (isSelectAllOption) { 650 651 if (checked) { 652 this.selectAll(this.options.selectAllJustVisible, true); 653 } 654 else { 655 this.deselectAll(this.options.selectAllJustVisible, true); 656 } 657 } 658 else { 659 if (checked) { 660 $option.prop('selected', true); 661 662 if (this.options.multiple) { 663 // Simply select additional option. 664 $option.prop('selected', true); 665 } 666 else { 667 // Unselect all other options and corresponding checkboxes. 668 if (this.options.selectedClass) { 669 $($checkboxesNotThis).closest('.dropdown-item').removeClass(this.options.selectedClass); 670 } 671 672 $($checkboxesNotThis).prop('checked', false); 673 $optionsNotThis.prop('selected', false); 674 675 // It's a single selection, so close. 676 this.$button.click(); 677 } 678 679 if (this.options.selectedClass === "active") { 680 $optionsNotThis.closest(".dropdown-item").css("outline", ""); 681 } 682 } 683 else { 684 // Unselect option. 685 $option.prop('selected', false); 686 } 687 688 // To prevent select all from firing onChange: #575 689 this.options.onChange($option, checked); 690 691 // Do not update select all or optgroups on select all change! 692 this.updateSelectAll(); 693 694 if (this.options.enableClickableOptGroups && this.options.multiple) { 695 this.updateOptGroups(); 696 } 697 } 698 699 this.$select.change(); 700 this.updateButtonText(); 701 702 if (this.options.preventInputChangeEvent) { 703 return false; 704 } 705 }, this)); 706 707 $('.multiselect-option', this.$popupContainer).off('mousedown'); 708 $('.multiselect-option', this.$popupContainer).on('mousedown', function (e) { 709 if (e.shiftKey) { 710 // Prevent selecting text by Shift+click 711 return false; 712 } 713 }); 714 715 $(this.$popupContainer).off('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group'); 716 $(this.$popupContainer).on('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group', $.proxy(function (event) { 717 event.stopPropagation(); 718 719 var $target = $(event.target); 720 721 if (event.shiftKey && this.options.multiple) { 722 if (!$target.is("input")) { // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431) 723 event.preventDefault(); 724 $target = $target.closest(".multiselect-option").find("input"); 725 $target.prop("checked", !$target.prop("checked")); 726 } 727 var checked = $target.prop('checked') || false; 728 729 if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range 730 var from = this.$popupContainer.find(".multiselect-option:visible").index($target.closest(".multiselect-option")); 731 var to = this.$popupContainer.find(".multiselect-option:visible").index(this.lastToggledInput.closest(".multiselect-option")); 732 733 if (from > to) { // Swap the indices 734 var tmp = to; 735 to = from; 736 from = tmp; 737 } 738 739 // Make sure we grab all elements since slice excludes the last index 740 ++to; 741 742 // Change the checkboxes and underlying options 743 var range = this.$popupContainer.find(".multiselect-option:not(.multiselect-filter-hidden)").slice(from, to).find("input"); 744 745 range.prop('checked', checked); 746 747 if (this.options.selectedClass) { 748 range.closest('.multiselect-option') 749 .toggleClass(this.options.selectedClass, checked); 750 } 751 752 for (var i = 0, j = range.length; i < j; i++) { 753 var $checkbox = $(range[i]); 754 755 var $option = this.getOptionByValue($checkbox.val()); 756 757 $option.prop('selected', checked); 758 } 759 } 760 761 // Trigger the select "change" event 762 $target.trigger("change"); 763 } 764 else if (!$target.is('input')) { 765 var $checkbox = $target.closest('.multiselect-option, .multiselect-all').find('.form-check-input'); 766 if ($checkbox.length > 0) { 767 if (this.options.multiple || !$checkbox.prop('checked')) { 768 $checkbox.prop('checked', !$checkbox.prop('checked')); 769 $checkbox.change(); 770 } 771 } 772 else if (this.options.enableClickableOptGroups && this.options.multiple && !$target.hasClass("caret-container")) { 773 var groupItem = $target; 774 if (!groupItem.hasClass("multiselect-group")) { 775 groupItem = $target.closest('.multiselect-group'); 776 } 777 $checkbox = groupItem.find(".form-check-input"); 778 if ($checkbox.length > 0) { 779 $checkbox.prop('checked', !$checkbox.prop('checked')); 780 $checkbox.change(); 781 } 782 } 783 784 event.preventDefault(); 785 } 786 787 // Remembers last clicked option 788 var $input = $target.closest(".multiselect-option").find("input[type='checkbox'], input[type='radio']"); 789 if ($input.length > 0) { 790 this.lastToggledInput = $target; 791 } 792 else { 793 this.lastToggledInput = null; 794 } 795 796 $target.blur(); 797 }, this)); 798 799 //Keyboard support. 800 this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function (event) { 801 if ($('input.multiselect-search', this.$container).is(':focus')) { 802 return; 803 } 804 805 // keyCode 9 == Tab 806 if (event.keyCode === 9 && this.$container.hasClass('show')) { 807 this.$button.click(); 808 } 809 else { 810 var $items = $(this.$container).find(".multiselect-option:not(.disabled), .multiselect-group:not(.disabled), .multiselect-all").filter(":visible"); 811 812 if (!$items.length) { 813 return; 814 } 815 816 var index = $items.index($items.filter(':focus')); 817 818 var $current = $items.eq(index); 819 820 // keyCode 32 = Space 821 if (event.keyCode === 32) { 822 var $checkbox = $current.find('input'); 823 824 $checkbox.prop("checked", !$checkbox.prop("checked")); 825 $checkbox.change(); 826 827 event.preventDefault(); 828 } 829 830 // keyCode 13 = Enter 831 if (event.keyCode === 13) { 832 setTimeout(function () { 833 $current.focus(); 834 }, 0); 835 } 836 } 837 }, this)); 838 839 if (this.options.enableClickableOptGroups && this.options.multiple) { 840 $(".multiselect-group input", this.$popupContainer).off("change"); 841 $(".multiselect-group input", this.$popupContainer).on("change", $.proxy(function (event) { 842 event.stopPropagation(); 843 844 var $target = $(event.target); 845 var checked = $target.prop('checked') || false; 846 847 var $item = $(event.target).closest('.dropdown-item'); 848 var $group = $item.nextUntil(".multiselect-group") 849 .not('.multiselect-filter-hidden') 850 .not('.disabled'); 851
852 var $inputs = $group.find("input"); 853 854 var $options = []; 855 856 if (this.options.selectedClass) { 857 if (checked) { 858 $item.addClass(this.options.selectedClass); 859 } 860 else { 861 $item.removeClass(this.options.selectedClass); 862 } 863 } 864 865 $.each($inputs, $.proxy(function (index, input) { 866 var $input = $(input); 867 var value = $input.val(); 868 var $option = this.getOptionByValue(value); 869 870 if (checked) { 871 $input.prop('checked', true); 872 $input.closest('.dropdown-item') 873 .addClass(this.options.selectedClass); 874 875 $option.prop('selected', true); 876 } 877 else { 878 $input.prop('checked', false); 879 $input.closest('.dropdown-item') 880 .removeClass(this.options.selectedClass); 881 882 $option.prop('selected', false); 883 } 884 885 $options.push(this.getOptionByValue(value)); 886 }, this)) 887 888 // Cannot use select or deselect here because it would call updateOptGroups again. 889 890 this.options.onChange($options, checked); 891 892 this.$select.change(); 893 this.updateButtonText(); 894 this.updateSelectAll(); 895 }, this)); 896 } 897 898 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 899 $(".multiselect-group .caret-container", this.$popupContainer).off("click"); 900 $(".multiselect-group .caret-container", this.$popupContainer).on("click", $.proxy(function (event) { 901 var $group = $(event.target).closest('.multiselect-group'); 902 var $inputs = $group.nextUntil(".multiselect-group") 903 .not('.multiselect-filter-hidden'); 904 905 var visible = true; 906 $inputs.each(function () { 907 visible = visible && !$(this).hasClass('multiselect-collapsible-hidden'); 908 }); 909 910 if (visible) { 911 $inputs.hide() 912 .addClass('multiselect-collapsible-hidden'); 913 } 914 else { 915 $inputs.show() 916 .removeClass('multiselect-collapsible-hidden'); 917 } 918 }, this)); 919 } 920 }, 921 922 /** 923 * Create a checkbox container with input and label based on given values 924 * @param {JQuery} $item 925 * @param {String} label 926 * @param {String} name 927 * @param {String} value 928 * @param {String} inputType 929 * @returns {JQuery} 930 */ 931 createCheckbox: function ($item, label, name, value, title, inputType) { 932 var $wrapper = $('<span />'); 933 $wrapper.addClass("form-check"); 934 935 if (this.options.enableHTML && $(label).length > 0) { 936 var $checkboxLabel = $('<label class="form-check-label" />'); 937 $checkboxLabel.html(label); 938 $wrapper.append($checkboxLabel); 939 } 940 else { 941 var $checkboxLabel = $('<label class="form-check-label" />'); 942 $checkboxLabel.text(label); 943 $wrapper.append($checkboxLabel); 944 } 945 946 var $checkbox = $('<input class="form-check-input"/>').attr('type', inputType); 947 $checkbox.val(value); 948 $wrapper.prepend($checkbox); 949 950 if (name) { 951 $checkbox.attr('name', name); 952 } 953 954 $item.prepend($wrapper); 955 $item.attr("title", title || label); 956 957 return $checkbox; 958 }, 959 960 /** 961 * Create an option using the given select option. 962 * 963 * @param {jQuery} element 964 */ 965 createOptionValue: function (element, isGroupOption) { 966 var $element = $(element); 967 if ($element.is(':selected')) { 968 $element.prop('selected', true); 969 } 970
vendor: 32,195 bytes, lines 971-1782
971 // Support the label attribute on options. 972 var label = this.options.optionLabel(element); 973 var classes = this.options.optionClass(element); 974 var value = $element.val(); 975 var inputType = this.options.multiple ? "checkbox" : "radio"; 976 var title = $element.attr('title'); 977 978 var $option = $(this.options.templates.option); 979 $option.addClass(classes); 980 981 if (isGroupOption && this.options.indentGroupOptions) { 982 $option.addClass("multiselect-group-option-indented") 983 } 984 985 // Hide all children items when collapseOptGroupsByDefault is true 986 if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") { 987 $option.addClass("multiselect-collapsible-hidden"); 988 $option.hide(); 989 } 990 991 var name = this.options.checkboxName($element); 992 var $checkbox = this.createCheckbox($option, label, name, value, title, inputType); 993 994 var selected = $element.prop('selected') || false; 995 996 if (value === this.options.selectAllValue) { 997 $option.addClass("multiselect-all"); 998 $option.removeClass("multiselect-option"); 999 $checkbox.parent().parent() 1000 .addClass('multiselect-all'); 1001 } 1002 1003 this.$popupContainer.append($option); 1004 1005 if ($element.is(':disabled')) { 1006 $checkbox.attr('disabled', 'disabled') 1007 .prop('disabled', true) 1008 .closest('.dropdown-item') 1009 .addClass('disabled'); 1010 } 1011 1012 $checkbox.prop('checked', selected); 1013 1014 if (selected && this.options.selectedClass) { 1015 $checkbox.closest('.dropdown-item') 1016 .addClass(this.options.selectedClass); 1017 } 1018 }, 1019 1020 /** 1021 * Creates a divider using the given select option. 1022 * 1023 * @param {jQuery} element 1024 */ 1025 createDivider: function (element) { 1026 var $divider = $(this.options.templates.divider); 1027 this.$popupContainer.append($divider); 1028 }, 1029 1030 /** 1031 * Creates an optgroup. 1032 * 1033 * @param {jQuery} group 1034 */ 1035 createOptgroup: function (group) { 1036 var $group = $(group); 1037 var label = $group.attr("label"); 1038 var value = $group.attr("value"); 1039 var title = $group.attr('title'); 1040 1041 var $groupOption = $("<span class='multiselect-group dropdown-item-text'></span>"); 1042 1043 if (this.options.enableClickableOptGroups && this.options.multiple) { 1044 $groupOption = $(this.options.templates.optionGroup); 1045 var $checkbox = this.createCheckbox($groupOption, label, null, value, title, "checkbox"); 1046 } 1047 else { 1048 if (this.options.enableHTML) { 1049 $groupOption.html(" " + label); 1050 } 1051 else { 1052 $groupOption.text(" " + label); 1053 } 1054 } 1055 1056 var classes = this.options.optionClass(group); 1057 $groupOption.addClass(classes); 1058 1059 if (this.options.enableCollapsibleOptGroups && this.options.multiple) { 1060 $groupOption.find('.form-check').addClass('d-inline-block'); 1061 $groupOption.append('<span class="caret-container dropdown-toggle pl-1"></span>'); 1062 } 1063 1064 if ($group.is(':disabled')) { 1065 $groupOption.addClass('disabled'); 1066 } 1067 1068 this.$popupContainer.append($groupOption); 1069 1070 $("option", group).each($.proxy(function ($, group) { 1071 this.createOptionValue(group, true); 1072 }, this)); 1073 }, 1074 1075 /** 1076 * Build the reset. 1077 * 1078 */ 1079 buildReset: function () { 1080 if (this.options.includeResetOption) { 1081 1082 // Check whether to add a divider after the reset. 1083 if (this.options.includeResetDivider) { 1084 var divider = $(this.options.templates.divider); 1085 divider.addClass("mt-0"); 1086 this.$popupContainer.prepend(divider); 1087 } 1088 1089 var $resetButton = $(this.options.templates.resetButton); 1090 1091 if (this.options.enableHTML) { 1092 $('button', $resetButton).html(this.options.resetText); 1093 } 1094 else { 1095 $('button', $resetButton).text(this.options.resetText); 1096 } 1097 1098 $('button', $resetButton).click($.proxy(function () { 1099 this.clearSelection(); 1100 }, this)); 1101 1102 this.$popupContainer.prepend($resetButton); 1103 } 1104 }, 1105 1106 /** 1107 * Build the select all. 1108 * 1109 * Checks if a select all has already been created. 1110 */ 1111 buildSelectAll: function () { 1112 if (typeof this.options.selectAllValue === 'number') { 1113 this.options.selectAllValue = this.options.selectAllValue.toString(); 1114 } 1115 1116 var alreadyHasSelectAll = this.hasSelectAll(); 1117 1118 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple 1119 && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { 1120 1121 // Check whether to add a divider after the select all. 1122 if (this.options.includeSelectAllDivider) { 1123 this.$popupContainer.prepend($(this.options.templates.divider)); 1124 } 1125 1126 var $option = $(this.options.templates.li || this.options.templates.option); 1127 var $checkbox = this.createCheckbox($option, this.options.selectAllText, this.options.selectAllName, this.options.selectAllValue, this.options.selectAllText, "checkbox"); 1128 1129 $option.addClass("multiselect-all"); 1130 $option.removeClass("multiselect-option"); 1131 $option.find(".form-check-label").addClass("font-weight-bold"); 1132 1133 this.$popupContainer.prepend($option); 1134 1135 $checkbox.prop('checked', false); 1136 } 1137 }, 1138 1139 /** 1140 * Builds the filter. 1141 */ 1142 buildFilter: function () { 1143 1144 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. 1145 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { 1146 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); 1147 1148 if (this.$select.find('option').length >= enableFilterLength) { 1149 1150 this.$filter = $(this.options.templates.filter); 1151 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); 1152 1153 // Handles optional filter clear button 1154 if (!this.options.includeFilterClearBtn) { 1155 this.$filter.find(".multiselect-search").attr("type", "text"); 1156 1157 // Remove clear button if the old design of the filter with input groups and separated clear button is used 1158 this.$filter.find(".multiselect-clear-filter").remove(); 1159 } 1160 else { 1161 // Firefox does not support a clear button in search inputs right now therefore it must be added manually 1162 if (this.isFirefox() && this.$filter.find(".multiselect-clear-filter").length === 0) { 1163 this.$filter.append("<i class='fas fa-times text-muted multiselect-clear-filter multiselect-moz-clear-filter'></i>"); 1164 } 1165 1166 this.$filter.find(".multiselect-clear-filter").on('click', $.proxy(function (event) { 1167 clearTimeout(this.searchTimeout); 1168 1169 this.query = ''; 1170 this.$filter.find('.multiselect-search').val(''); 1171 $('.dropdown-item', this.$popupContainer).show().removeClass('multiselect-filter-hidden'); 1172 1173 this.updateSelectAll(); 1174 1175 if (this.options.enableClickableOptGroups && this.options.multiple) { 1176 this.updateOptGroups(); 1177 } 1178 1179 }, this)); 1180 } 1181 1182 this.$popupContainer.prepend(this.$filter); 1183 1184 this.$filter.val(this.query).on('click', function (event) { 1185 event.stopPropagation(); 1186 }).on('input keydown', $.proxy(function (event) { 1187 // Cancel enter key default behaviour 1188 if (event.which === 13) { 1189 event.preventDefault(); 1190 } 1191 1192 if (this.isFirefox() && this.options.includeFilterClearBtn) { 1193 if (event.target.value) { 1194 this.$filter.find(".multiselect-moz-clear-filter").show(); 1195 } 1196 else { 1197 this.$filter.find(".multiselect-moz-clear-filter").hide(); 1198 } 1199 } 1200 1201 // This is useful to catch "keydown" events after the browser has updated the control. 1202 clearTimeout(this.searchTimeout); 1203 1204 this.searchTimeout = this.asyncFunction($.proxy(function () { 1205 1206 if (this.query !== event.target.value) { 1207 this.query = event.target.value; 1208 1209 var currentGroup, currentGroupVisible; 1210 $.each($('.multiselect-option, .multiselect-group', this.$popupContainer), $.proxy(function (index, element) { 1211 var value = $('input', element).length > 0 ? $('input', element).val() : ""; 1212 var text = $('.form-check-label', element).text(); 1213 1214 var filterCandidate = ''; 1215 if ((this.options.filterBehavior === 'text')) { 1216 filterCandidate = text; 1217 } 1218 else if ((this.options.filterBehavior === 'value')) { 1219 filterCandidate = value; 1220 } 1221 else if (this.options.filterBehavior === 'both') { 1222 filterCandidate = text + '\n' + value; 1223 } 1224 1225 if (value !== this.options.selectAllValue && text) { 1226 1227 // By default lets assume that element is not 1228 // interesting for this search. 1229 var showElement = false; 1230 1231 if (this.options.enableCaseInsensitiveFiltering) { 1232 filterCandidate = filterCandidate.toLowerCase(); 1233 this.query = this.query.toLowerCase(); 1234 } 1235 1236 if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') { 1237 var valueToMatch = filterCandidate.trim().substring(0, this.query.length); 1238 if (this.query.indexOf(valueToMatch) > -1) { 1239 showElement = true; 1240 } 1241 } 1242 else if (filterCandidate.indexOf(this.query) > -1) { 1243 showElement = true; 1244 } 1245 1246 // Toggle current element (group or group item) according to showElement boolean. 1247 if (!showElement) { 1248 $(element).css('display', 'none'); 1249 $(element).addClass('multiselect-filter-hidden'); 1250 } 1251 if (showElement) { 1252 $(element).css('display', 'block'); 1253 $(element).removeClass('multiselect-filter-hidden'); 1254 } 1255 1256 // Differentiate groups and group items. 1257 if ($(element).hasClass('multiselect-group')) { 1258 // Remember group status. 1259 currentGroup = element; 1260 currentGroupVisible = showElement; 1261 } 1262 else { 1263 // Show group name when at least one of its items is visible. 1264 if (showElement) { 1265 $(currentGroup).show() 1266 .removeClass('multiselect-filter-hidden'); 1267 } 1268 1269 // Show all group items when group name satisfies filter. 1270 if (!showElement && currentGroupVisible) { 1271 $(element).show() 1272 .removeClass('multiselect-filter-hidden'); 1273 } 1274 } 1275 } 1276 }, this)); 1277 } 1278 1279 this.updateSelectAll(); 1280 1281 if (this.options.enableClickableOptGroups && this.options.multiple) { 1282 this.updateOptGroups(); 1283 } 1284 1285 this.options.onFiltering(event.target); 1286 1287 }, this), 300, this); 1288 }, this)); 1289 } 1290 } 1291 }, 1292 1293 /** 1294 * Unbinds the whole plugin. 1295 */ 1296 destroy: function () { 1297 this.$container.remove(); 1298 this.$select.show(); 1299 1300 // reset original state 1301 this.$select.prop('disabled', this.options.wasDisabled); 1302 1303 this.$select.data('multiselect', null); 1304 }, 1305 1306 /** 1307 * Refreshs the multiselect based on the selected options of the select. 1308 */ 1309 refresh: function () { 1310 var inputs = {}; 1311 $('.multiselect-option input', this.$popupContainer).each(function () { 1312 inputs[$(this).val()] = $(this); 1313 }); 1314 1315 $('option', this.$select).each($.proxy(function (index, element) { 1316 var $elem = $(element); 1317 var $input = inputs[$(element).val()]; 1318 1319 if ($elem.is(':selected')) { 1320 $input.prop('checked', true); 1321 1322 if (this.options.selectedClass) { 1323 $input.closest('.multiselect-option') 1324 .addClass(this.options.selectedClass); 1325 } 1326 } 1327 else { 1328 $input.prop('checked', false); 1329 1330 if (this.options.selectedClass) { 1331 $input.closest('.multiselect-option') 1332 .removeClass(this.options.selectedClass); 1333 } 1334 } 1335 1336 if ($elem.is(":disabled")) { 1337 $input.attr('disabled', 'disabled') 1338 .prop('disabled', true) 1339 .closest('.multiselect-option') 1340 .addClass('disabled'); 1341 } 1342 else { 1343 $input.prop('disabled', false) 1344 .closest('.multiselect-option') 1345 .removeClass('disabled'); 1346 } 1347 }, this)); 1348 1349 this.updateButtonText(); 1350 this.updateSelectAll(); 1351 1352 if (this.options.enableClickableOptGroups && this.options.multiple) { 1353 this.updateOptGroups(); 1354 } 1355 }, 1356 1357 /** 1358 * Select all options of the given values. 1359 * 1360 * If triggerOnChange is set to true, the on change event is triggered if 1361 * and only if one value is passed. 1362 * 1363 * @param {Array} selectValues 1364 * @param {Boolean} triggerOnChange 1365 */ 1366 select: function (selectValues, triggerOnChange) { 1367 if (!$.isArray(selectValues)) { 1368 selectValues = [selectValues]; 1369 } 1370 1371 for (var i = 0; i < selectValues.length; i++) { 1372 var value = selectValues[i]; 1373 1374 if (value === null || value === undefined) { 1375 continue; 1376 } 1377 1378 var $option = this.getOptionByValue(value); 1379 var $checkbox = this.getInputByValue(value); 1380 1381 if ($option === undefined || $checkbox === undefined) { 1382 continue; 1383 } 1384 1385 if (!this.options.multiple) { 1386 this.deselectAll(false); 1387 } 1388 1389 if (this.options.selectedClass) { 1390 $checkbox.closest('.dropdown-item') 1391 .addClass(this.options.selectedClass); 1392 } 1393 1394 $checkbox.prop('checked', true); 1395 $option.prop('selected', true); 1396 1397 if (triggerOnChange) { 1398 this.options.onChange($option, true); 1399 } 1400 } 1401 1402 this.updateButtonText(); 1403 this.updateSelectAll(); 1404 1405 if (this.options.enableClickableOptGroups && this.options.multiple) { 1406 this.updateOptGroups(); 1407 } 1408 }, 1409 1410 /** 1411 * Clears all selected items. 1412 */ 1413 clearSelection: function () { 1414 this.deselectAll(false); 1415 this.updateButtonText(); 1416 this.updateSelectAll(); 1417 1418 if (this.options.enableClickableOptGroups && this.options.multiple) { 1419 this.updateOptGroups(); 1420 } 1421 }, 1422 1423 /** 1424 * Deselects all options of the given values. 1425 * 1426 * If triggerOnChange is set to true, the on change event is triggered, if 1427 * and only if one value is passed. 1428 * 1429 * @param {Array} deselectValues 1430 * @param {Boolean} triggerOnChange 1431 */ 1432 deselect: function (deselectValues, triggerOnChange) { 1433 if (!$.isArray(deselectValues)) { 1434 deselectValues = [deselectValues]; 1435 } 1436 1437 for (var i = 0; i < deselectValues.length; i++) { 1438 var value = deselectValues[i]; 1439 1440 if (value === null || value === undefined) { 1441 continue; 1442 } 1443 1444 var $option = this.getOptionByValue(value); 1445 var $checkbox = this.getInputByValue(value); 1446 1447 if ($option === undefined || $checkbox === undefined) { 1448 continue; 1449 } 1450 1451 if (this.options.selectedClass) { 1452 $checkbox.closest('.dropdown-item') 1453 .removeClass(this.options.selectedClass); 1454 } 1455 1456 $checkbox.prop('checked', false); 1457 $option.prop('selected', false); 1458 1459 if (triggerOnChange) { 1460 this.options.onChange($option, false); 1461 } 1462 } 1463 1464 this.updateButtonText(); 1465 this.updateSelectAll(); 1466 1467 if (this.options.enableClickableOptGroups && this.options.multiple) { 1468 this.updateOptGroups(); 1469 } 1470 }, 1471 1472 /** 1473 * Selects all enabled & visible options. 1474 * 1475 * If justVisible is true or not specified, only visible options are selected. 1476 * 1477 * @param {Boolean} justVisible 1478 * @param {Boolean} triggerOnSelectAll 1479 */ 1480 selectAll: function (justVisible, triggerOnSelectAll) { 1481 1482 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1483 1484 if (justVisible) { 1485 var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer); 1486 $('input:enabled', visibleOptions).prop('checked', true); 1487 visibleOptions.addClass(this.options.selectedClass); 1488 1489 $('input:enabled', visibleOptions).each($.proxy(function (index, element) { 1490 var value = $(element).val(); 1491 var option = this.getOptionByValue(value); 1492 $(option).prop('selected', true); 1493 }, this)); 1494 } 1495 else { 1496 var allOptions = $(".multiselect-option:not(.disabled)", this.$popupContainer); 1497 $('input:enabled', allOptions).prop('checked', true); 1498 allOptions.addClass(this.options.selectedClass); 1499 1500 $('input:enabled', allOptions).each($.proxy(function (index, element) { 1501 var value = $(element).val(); 1502 var option = this.getOptionByValue(value); 1503 $(option).prop('selected', true); 1504 }, this)); 1505 } 1506 1507 $('.multiselect-option input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', true); 1508 1509 if (this.options.enableClickableOptGroups && this.options.multiple) { 1510 this.updateOptGroups(); 1511 } 1512 1513 this.updateButtonText(); 1514 this.updateSelectAll(); 1515 1516 if (triggerOnSelectAll) { 1517 this.options.onSelectAll(); 1518 } 1519 }, 1520 1521 /** 1522 * Deselects all options. 1523 * 1524 * If justVisible is true or not specified, only visible options are deselected. 1525 * 1526 * @param {Boolean} justVisible 1527 */ 1528 deselectAll: function (justVisible, triggerOnDeselectAll) { 1529 1530 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1531 1532 if (justVisible) { 1533 var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer); 1534 $('input[type="checkbox"]:enabled', visibleOptions).prop('checked', false); 1535 visibleOptions.removeClass(this.options.selectedClass); 1536 1537 $('input[type="checkbox"]:enabled', visibleOptions).each($.proxy(function (index, element) { 1538 var value = $(element).val(); 1539 var option = this.getOptionByValue(value); 1540 $(option).prop('selected', false); 1541 }, this)); 1542 } 1543 else { 1544 var allOptions = $(".multiselect-option:not(.disabled):not(.multiselect-group)", this.$popupContainer); 1545 $('input[type="checkbox"]:enabled', allOptions).prop('checked', false); 1546 allOptions.removeClass(this.options.selectedClass); 1547 1548 $('input[type="checkbox"]:enabled', allOptions).each($.proxy(function (index, element) { 1549 var value = $(element).val(); 1550 var option = this.getOptionByValue(value); 1551 $(option).prop('selected', false); 1552 }, this)); 1553 } 1554 1555 $('.multiselect-all input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', false); 1556 1557 if (this.options.enableClickableOptGroups && this.options.multiple) { 1558 this.updateOptGroups(); 1559 } 1560 1561 this.updateButtonText(); 1562 this.updateSelectAll(); 1563 1564 if (triggerOnDeselectAll) { 1565 this.options.onDeselectAll(); 1566 } 1567 }, 1568 1569 /** 1570 * Rebuild the plugin. 1571 * 1572 * Rebuilds the dropdown, the filter and the select all option. 1573 */ 1574 rebuild: function () { 1575 this.$popupContainer.html(''); 1576 1577 // Important to distinguish between radios and checkboxes. 1578 this.options.multiple = this.$select.attr('multiple') === "multiple"; 1579 1580 this.buildSelectAll(); 1581 this.buildDropdownOptions(); 1582 this.buildFilter(); 1583 1584 this.updateButtonText(); 1585 this.updateSelectAll(true); 1586 1587 if (this.options.enableClickableOptGroups && this.options.multiple) { 1588 this.updateOptGroups(); 1589 } 1590 1591 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 1592 this.disable(); 1593 } 1594 else { 1595 this.enable(); 1596 } 1597 1598 if (this.options.dropRight) { 1599 this.$container.addClass('dropright'); 1600 } 1601 else if (this.options.dropUp) { 1602 this.$container.addClass('dropup'); 1603 } 1604 1605 if (this.options.widthSynchronizationMode !== 'never') { 1606 this.synchronizeButtonAndPopupWidth(); 1607 } 1608 }, 1609 1610 /** 1611 * The provided data will be used to build the dropdown. 1612 */ 1613 dataprovider: function (dataprovider) { 1614 1615 var groupCounter = 0; 1616 var $select = this.$select.empty(); 1617 1618 $.each(dataprovider, function (index, option) { 1619 var $tag; 1620 1621 if ($.isArray(option.children)) { // create optiongroup tag 1622 groupCounter++; 1623 1624 $tag = $('<optgroup/>').attr({ 1625 label: option.label || 'Group ' + groupCounter, 1626 disabled: !!option.disabled, 1627 value: option.value 1628 }); 1629 1630 forEach(option.children, function (subOption) { // add children option tags 1631 var attributes = { 1632 value: subOption.value, 1633 label: subOption.label || subOption.value, 1634 title: subOption.title, 1635 selected: !!subOption.selected, 1636 disabled: !!subOption.disabled 1637 }; 1638 1639 //Loop through attributes object and add key-value for each attribute 1640 for (var key in subOption.attributes) { 1641 attributes['data-' + key] = subOption.attributes[key]; 1642 } 1643 //Append original attributes + new data attributes to option 1644 $tag.append($('<option/>').attr(attributes)); 1645 }); 1646 } 1647 else { 1648 1649 var attributes = { 1650 'value': option.value, 1651 'label': option.label || option.value, 1652 'title': option.title, 1653 'class': option['class'], 1654 'selected': !!option['selected'], 1655 'disabled': !!option['disabled'] 1656 }; 1657 //Loop through attributes object and add key-value for each attribute 1658 for (var key in option.attributes) { 1659 attributes['data-' + key] = option.attributes[key]; 1660 } 1661 //Append original attributes + new data attributes to option 1662 $tag = $('<option/>').attr(attributes); 1663 1664 $tag.text(option.label || option.value); 1665 } 1666 1667 $select.append($tag); 1668 }); 1669 1670 this.rebuild(); 1671 }, 1672 1673 /** 1674 * Enable the multiselect. 1675 */ 1676 enable: function () { 1677 this.$select.prop('disabled', false); 1678 this.$button.prop('disabled', false) 1679 .removeClass('disabled'); 1680 1681 this.updateButtonText(); 1682 }, 1683 1684 /** 1685 * Disable the multiselect. 1686 */ 1687 disable: function () { 1688 this.$select.prop('disabled', true); 1689 this.$button.prop('disabled', true) 1690 .addClass('disabled'); 1691 1692 this.updateButtonText(); 1693 }, 1694 1695 /** 1696 * Set the options. 1697 * 1698 * @param {Array} options 1699 */ 1700 setOptions: function (options) { 1701 this.options = this.mergeOptions(options); 1702 }, 1703 1704 /** 1705 * Merges the given options with the default options. 1706 * 1707 * @param {Array} options 1708 * @returns {Array} 1709 */ 1710 mergeOptions: function (options) { 1711 return $.extend(true, {}, this.defaults, this.options, options); 1712 }, 1713 1714 /** 1715 * Checks whether a select all checkbox is present. 1716 * 1717 * @returns {Boolean} 1718 */ 1719 hasSelectAll: function () { 1720 return $('.multiselect-all', this.$popupContainer).length > 0; 1721 }, 1722 1723 /** 1724 * Update opt groups. 1725 */ 1726 updateOptGroups: function () { 1727 var $groups = $('.multiselect-group', this.$popupContainer) 1728 var selectedClass = this.options.selectedClass; 1729 1730 $groups.each(function () { 1731 var $options = $(this).nextUntil('.multiselect-group') 1732 .not('.multiselect-filter-hidden') 1733 .not('.disabled'); 1734 1735 var checked = true; 1736 $options.each(function () { 1737 var $input = $('input', this); 1738 1739 if (!$input.prop('checked')) { 1740 checked = false; 1741 } 1742 }); 1743 1744 if (selectedClass) { 1745 if (checked) { 1746 $(this).addClass(selectedClass); 1747 } 1748 else { 1749 $(this).removeClass(selectedClass); 1750 } 1751 } 1752 1753 $('input', this).prop('checked', checked); 1754 }); 1755 }, 1756 1757 /** 1758 * Updates the select all checkbox based on the currently displayed and selected checkboxes. 1759 */ 1760 updateSelectAll: function (notTriggerOnSelectAll) { 1761 if (this.hasSelectAll()) { 1762 var allBoxes = $(".multiselect-option:not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$popupContainer); 1763 var allBoxesLength = allBoxes.length; 1764 var checkedBoxesLength = allBoxes.filter(":checked").length; 1765 var selectAllItem = $(".multiselect-all", this.$popupContainer); 1766 var selectAllInput = selectAllItem.find("input"); 1767 1768 if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) { 1769 selectAllInput.prop("checked", true); 1770 selectAllItem.addClass(this.options.selectedClass); 1771 } 1772 else { 1773 selectAllInput.prop("checked", false); 1774 selectAllItem.removeClass(this.options.selectedClass); 1775 } 1776 } 1777 }, 1778 1779 /** 1780 * Update the button text and its title based on the currently selected options. 1781 */ 1782 updateButtonText: function () {
1783 var options = this.getSelected(); 1784 1785 // First update the displayed button text. 1786 if (this.options.enableHTML) { 1787 $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select)); 1788 } 1789 else { 1790 $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select)); 1791 } 1792 1793 // Now update the title attribute of the button. 1794 $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); 1795 this.$button.trigger('change'); 1796 }, 1797 1798 /** 1799 * Get all selected options. 1800 * 1801 * @returns {jQUery} 1802 */ 1803 getSelected: function () { 1804 return $('option', this.$select).filter(":selected"); 1805 }, 1806 1807 /** 1808 * Gets a select option by its value. 1809 * 1810 * @param {String} value 1811 * @returns {jQuery} 1812 */ 1813 getOptionByValue: function (value) { 1814 1815 var options = $('option', this.$select); 1816 var valueToCompare = value.toString(); 1817 1818 for (var i = 0; i < options.length; i = i + 1) { 1819 var option = options[i]; 1820 if (option.value === valueToCompare) { 1821 return $(option); 1822 } 1823 } 1824 }, 1825 1826 /** 1827 * Get the input (radio/checkbox) by its value. 1828 * 1829 * @param {String} value 1830 * @returns {jQuery} 1831 */ 1832 getInputByValue: function (value) { 1833 1834 var checkboxes = $('.multiselect-option input:not(.multiselect-search)', this.$popupContainer); 1835 var valueToCompare = value.toString(); 1836 1837 for (var i = 0; i < checkboxes.length; i = i + 1) { 1838 var checkbox = checkboxes[i]; 1839 if (checkbox.value === valueToCompare) { 1840 return $(checkbox); 1841 } 1842 } 1843 }, 1844 1845 /** 1846 * Used for knockout integration. 1847 */ 1848 updateOriginalOptions: function () { 1849 this.originalOptions = this.$select.clone()[0].options; 1850 }, 1851 1852 asyncFunction: function (callback, timeout, self) { 1853 var args = Array.prototype.slice.call(arguments, 3); 1854 return setTimeout(function () { 1855 callback.apply(self || window, args); 1856 }, timeout); 1857 }, 1858 1859 setAllSelectedText: function (allSelectedText) { 1860 this.options.allSelectedText = allSelectedText; 1861 this.updateButtonText(); 1862 }, 1863 1864 isFirefox: function () { 1865 var firefoxIdentifier = 'firefox'; 1866 var valueNotFoundIndex = -1; 1867 1868 if (navigator && navigator.userAgent) { 1869 return navigator.userAgent.toLocaleLowerCase().indexOf(firefoxIdentifier) > valueNotFoundIndex; 1870 } 1871 1872 return false; 1873 } 1874 }; 1875 1876 $.fn.multiselect = function (option, parameter, extraOptions) { 1877 return this.each(function () { 1878 var data = $(this).data('multiselect'); 1879 var options = typeof option === 'object' && option; 1880 1881 // Initialize the multiselect. 1882 if (!data) { 1883 data = new Multiselect(this, options); 1884 $(this).data('multiselect', data); 1885 } 1886 1887 // Call multiselect method. 1888 if (typeof option === 'string') { 1889 data[option](parameter, extraOptions); 1890 1891 if (option === 'destroy') { 1892 $(this).data('multiselect', false); 1893 } 1894 } 1895 }); 1896 }; 1897 1898 $.fn.multiselect.Constructor = Multiselect; 1899 1900 $(function () { 1901 $("select[data-role=multiselect]").multiselect(); 1902 }); 1903 1904});
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.