1/** 2 * Bootstrap Multiselect v0.9.8 (https://github.com/davidstutz/bootstrap-multiselect) 3 * 4 * Copyright 2012 - 2014 David Stutz 5 * 6 * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0. 7 */ 8!function($) { 9 10 "use strict";// jshint ;_; 11 12 if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { 13 ko.bindingHandlers.multiselect = { 14 15 init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 16 17 var listOfSelectedItems = allBindingsAccessor().selectedOptions; 18 var config = ko.utils.unwrapObservable(valueAccessor()); 19 20 $(element).multiselect(config); 21 22 if (isObservableArray(listOfSelectedItems)) { 23 24 // Set the initial selection state on the multiselect list. 25 $(element).multiselect('select', ko.utils.unwrapObservable(listOfSelectedItems)); 26 27 // Subscribe to the selectedOptions: ko.observableArray 28 listOfSelectedItems.subscribe(function (changes) { 29 var addedArray = [], deletedArray = []; 30 forEach(changes, function (change) { 31 switch (change.status) { 32 case 'added': 33 addedArray.push(change.value); 34 break; 35 case 'deleted': 36 deletedArray.push(change.value); 37 break; 38 } 39 }); 40 41 if (addedArray.length > 0) { 42 $(element).multiselect('select', addedArray); 43 } 44 45 if (deletedArray.length > 0) { 46 $(element).multiselect('deselect', deletedArray); 47 } 48 }, null, "arrayChange"); 49 } 50 }, 51 52 update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 53 54 var listOfItems = allBindingsAccessor().options, 55 ms = $(element).data('multiselect'), 56 config = ko.utils.unwrapObservable(valueAccessor()); 57 58 if (isObservableArray(listOfItems)) { 59 // Subscribe to the options: ko.observableArray incase it changes later 60 listOfItems.subscribe(function (theArray) { 61 $(element).multiselect('rebuild'); 62 }); 63 } 64 65 if (!ms) { 66 $(element).multiselect(config); 67 } 68 else { 69 ms.updateOriginalOptions(); 70 } 71 } 72 }; 73 } 74 75 function isObservableArray(obj) { 76 return ko.isObservable(obj) && !(obj.destroyAll === undefined); 77 } 78 79 function forEach(array, callback) { 80 for (var index = 0; index < array.length; ++index) { 81 callback(array[index]); 82 } 83 } 84 85 /** 86 * Constructor to create a new multiselect using the given select. 87 * 88 * @param {jQuery} select 89 * @param {Object} options 90 * @returns {Multiselect} 91 */ 92 function Multiselect(select, options) { 93 94 this.$select = $(select); 95 this.options = this.mergeOptions($.extend({}, options, this.$select.data())); 96 97 // Initialization. 98 // We have to clone to create a new reference. 99 this.originalOptions = this.$select.clone()[0].options; 100 this.query = ''; 101 this.searchTimeout = null; 102 103 this.options.multiple = this.$select.attr('multiple') === "multiple"; 104 this.options.onChange = $.proxy(this.options.onChange, this); 105 this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); 106 this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); 107 this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this); 108 this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this); 109 110 // Build select all if enabled. 111 this.buildContainer(); 112 this.buildButton(); 113 this.buildDropdown(); 114 this.buildSelectAll(); 115 this.buildDropdownOptions(); 116 this.buildFilter(); 117 118 this.updateButtonText(); 119 this.updateSelectAll(); 120 121 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 122 this.disable(); 123 } 124 125 this.$select.hide().after(this.$container); 126 }; 127 128 Multiselect.prototype = { 129 130 defaults: { 131 /**
132 * Default text function will either print 'None selected' in case no 133 * option is selected or a list of the selected options up to a length 134 * of 3 selected options. 135 * 136 * @param {jQuery} options 137 * @param {jQuery} select 138 * @returns {String} 139 */ 140 buttonText: function(options, select) { 141 if (options.length === 0) { 142 return this.nonSelectedText + ' <b class="caret"></b>'; 143 } 144 else if (options.length == $('option', $(select)).length) { 145 return this.allSelectedText + ' <b class="caret"></b>'; 146 } 147 else if (options.length > this.numberDisplayed) { 148 return options.length + ' ' + this.nSelectedText + ' <b class="caret"></b>'; 149 } 150 else { 151 var selected = ''; 152 options.each(function() { 153 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html(); 154 155 selected += label + ', '; 156 }); 157 158 return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>'; 159 } 160 }, 161 /** 162 * Updates the title of the button similar to the buttonText function. 163 * 164 * @param {jQuery} options 165 * @param {jQuery} select 166 * @returns {@exp;selected@call;substr} 167 */ 168 buttonTitle: function(options, select) { 169 if (options.length === 0) { 170 return this.nonSelectedText; 171 } 172 else { 173 var selected = ''; 174 options.each(function () { 175 selected += $(this).text() + ', '; 176 }); 177 return selected.substr(0, selected.length - 2); 178 } 179 }, 180 /** 181 * Create a label. 182 * 183 * @param {jQuery} element 184 * @returns {String} 185 */ 186 label: function(element){ 187 return $(element).attr('label') || $(element).html(); 188 }, 189 /** 190 * Triggered on change of the multiselect. 191 * 192 * Not triggered when selecting/deselecting options manually. 193 * 194 * @param {jQuery} option 195 * @param {Boolean} checked 196 */ 197 onChange : function(option, checked) { 198 199 }, 200 /** 201 * Triggered when the dropdown is shown. 202 * 203 * @param {jQuery} event 204 */ 205 onDropdownShow: function(event) { 206 207 }, 208 /** 209 * Triggered when the dropdown is hidden. 210 * 211 * @param {jQuery} event 212 */ 213 onDropdownHide: function(event) { 214 215 }, 216 /** 217 * Triggered after the dropdown is shown. 218 * 219 * @param {jQuery} event 220 */ 221 onDropdownShown: function(event) { 222 223 }, 224 /** 225 * Triggered after the dropdown is hidden. 226 * 227 * @param {jQuery} event 228 */ 229 onDropdownHidden: function(event) { 230 231 }, 232 buttonClass: 'btn btn-default', 233 buttonWidth: 'auto', 234 buttonContainer: '<div class="btn-group" />', 235 dropRight: false, 236 selectedClass: 'active', 237 // Maximum height of the dropdown menu. 238 // If maximum height is exceeded a scrollbar will be displayed. 239 maxHeight: false, 240 checkboxName: false, 241 includeSelectAllOption: false, 242 includeSelectAllIfMoreThan: 0, 243 selectAllText: ' Select all', 244 selectAllValue: 'multiselect-all', 245 selectAllName: false, 246 enableFiltering: false, 247 enableCaseInsensitiveFiltering: false, 248 enableClickableOptGroups: false, 249 filterPlaceholder: 'Search', 250 // possible options: 'text', 'value', 'both' 251 filterBehavior: 'text', 252 includeFilterClearBtn: true, 253 preventInputChangeEvent: false, 254 nonSelectedText: 'None selected', 255 nSelectedText: 'selected', 256 allSelectedText: 'All selected',
vendor: 7,515 bytes, lines 257-450
257 numberDisplayed: 3, 258 disableIfEmpty: false, 259 templates: { 260 button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"></button>', 261 ul: '<ul class="multiselect-container dropdown-menu"></ul>', 262 filter: '<li class="multiselect-item filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text"></div></li>', 263 filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button"><i class="glyphicon glyphicon-remove-circle"></i></button></span>', 264 li: '<li><a href="javascript:void(0);"><label></label></a></li>', 265 divider: '<li class="multiselect-item divider"></li>', 266 liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>' 267 } 268 }, 269 270 constructor: Multiselect, 271 272 /** 273 * Builds the container of the multiselect. 274 */ 275 buildContainer: function() { 276 this.$container = $(this.options.buttonContainer); 277 this.$container.on('show.bs.dropdown', this.options.onDropdownShow); 278 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); 279 this.$container.on('shown.bs.dropdown', this.options.onDropdownShown); 280 this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden); 281 }, 282 283 /** 284 * Builds the button of the multiselect. 285 */ 286 buildButton: function() { 287 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); 288 289 // Adopt active state. 290 if (this.$select.prop('disabled')) { 291 this.disable(); 292 } 293 else { 294 this.enable(); 295 } 296 297 // Manually add button width if set. 298 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { 299 this.$button.css({ 300 'width' : this.options.buttonWidth 301 }); 302 this.$container.css({ 303 'width': this.options.buttonWidth 304 }); 305 } 306 307 // Keep the tab index from the select. 308 var tabindex = this.$select.attr('tabindex'); 309 if (tabindex) { 310 this.$button.attr('tabindex', tabindex); 311 } 312 313 this.$container.prepend(this.$button); 314 }, 315 316 /** 317 * Builds the ul representing the dropdown menu. 318 */ 319 buildDropdown: function() { 320 321 // Build ul. 322 this.$ul = $(this.options.templates.ul); 323 324 if (this.options.dropRight) { 325 this.$ul.addClass('pull-right'); 326 } 327 328 // Set max height of dropdown menu to activate auto scrollbar. 329 if (this.options.maxHeight) { 330 // TODO: Add a class for this option to move the css declarations. 331 this.$ul.css({ 332 'max-height': this.options.maxHeight + 'px', 333 'overflow-y': 'auto', 334 'overflow-x': 'hidden' 335 }); 336 } 337 338 this.$container.append(this.$ul); 339 }, 340 341 /** 342 * Build the dropdown options and binds all nessecary events. 343 * 344 * Uses createDivider and createOptionValue to create the necessary options. 345 */ 346 buildDropdownOptions: function() { 347 348 this.$select.children().each($.proxy(function(index, element) { 349 350 var $element = $(element); 351 // Support optgroups and options without a group simultaneously. 352 var tag = $element.prop('tagName') 353 .toLowerCase(); 354 355 if ($element.prop('value') === this.options.selectAllValue) { 356 return; 357 } 358 359 if (tag === 'optgroup') { 360 this.createOptgroup(element); 361 } 362 else if (tag === 'option') { 363 364 if ($element.data('role') === 'divider') { 365 this.createDivider(); 366 } 367 else { 368 this.createOptionValue(element); 369 } 370 371 } 372 373 // Other illegal tags will be ignored. 374 }, this)); 375 376 // Bind the change event on the dropdown elements. 377 $('li input', this.$ul).on('change', $.proxy(function(event) { 378 var $target = $(event.target); 379 380 var checked = $target.prop('checked') || false; 381 var isSelectAllOption = $target.val() === this.options.selectAllValue; 382 383 // Apply or unapply the configured selected class. 384 if (this.options.selectedClass) { 385 if (checked) { 386 $target.closest('li') 387 .addClass(this.options.selectedClass); 388 } 389 else { 390 $target.closest('li') 391 .removeClass(this.options.selectedClass); 392 } 393 } 394 395 // Get the corresponding option. 396 var value = $target.val(); 397 var $option = this.getOptionByValue(value); 398 399 var $optionsNotThis = $('option', this.$select).not($option); 400 var $checkboxesNotThis = $('input', this.$container).not($target); 401 402 if (isSelectAllOption) { 403 if (checked) { 404 this.selectAll(); 405 } 406 else { 407 this.deselectAll(); 408 } 409 } 410 411 if(!isSelectAllOption){ 412 if (checked) { 413 $option.prop('selected', true); 414 415 if (this.options.multiple) { 416 // Simply select additional option. 417 $option.prop('selected', true); 418 } 419 else { 420 // Unselect all other options and corresponding checkboxes. 421 if (this.options.selectedClass) { 422 $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); 423 } 424 425 $($checkboxesNotThis).prop('checked', false); 426 $optionsNotThis.prop('selected', false); 427 428 // It's a single selection, so close. 429 this.$button.click(); 430 } 431 432 if (this.options.selectedClass === "active") { 433 $optionsNotThis.closest("a").css("outline", ""); 434 } 435 } 436 else { 437 // Unselect option. 438 $option.prop('selected', false); 439 } 440 } 441 442 this.$select.change(); 443 444 this.updateButtonText(); 445 this.updateSelectAll(); 446 447 this.options.onChange($option, checked); 448 449 if(this.options.preventInputChangeEvent) { 450 return false;
451 } 452 }, this)); 453 454 $('li a', this.$ul).on('touchstart click', function(event) { 455 event.stopPropagation(); 456 457 var $target = $(event.target); 458 459 if (document.getSelection().type === 'Range') { 460 var $input = $(this).find("input:first"); 461 462 $input.prop("checked", !$input.prop("checked")) 463 .trigger("change"); 464 } 465 466 if (event.shiftKey) { 467 var checked = $target.prop('checked') || false; 468 469 if (checked) { 470 var prev = $target.closest('li') 471 .siblings('li[class="active"]:first'); 472 473 var currentIdx = $target.closest('li') 474 .index(); 475 var prevIdx = prev.index(); 476 477 if (currentIdx > prevIdx) { 478 $target.closest("li").prevUntil(prev).each( 479 function() { 480 $(this).find("input:first").prop("checked", true) 481 .trigger("change"); 482 } 483 ); 484 } 485 else { 486 $target.closest("li").nextUntil(prev).each( 487 function() { 488 $(this).find("input:first").prop("checked", true) 489 .trigger("change"); 490 } 491 ); 492 } 493 } 494 } 495 496 $target.blur(); 497 }); 498 499 // Keyboard support. 500 this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) { 501 if ($('input[type="text"]', this.$container).is(':focus')) { 502 return; 503 } 504 505 if (event.keyCode === 9 && this.$container.hasClass('open')) { 506 this.$button.click(); 507 } 508 else { 509 var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible"); 510 511 if (!$items.length) { 512 return; 513 } 514 515 var index = $items.index($items.filter(':focus')); 516 517 // Navigation up. 518 if (event.keyCode === 38 && index > 0) { 519 index--; 520 } 521 // Navigate down. 522 else if (event.keyCode === 40 && index < $items.length - 1) { 523 index++; 524 } 525 else if (!~index) { 526 index = 0; 527 } 528 529 var $current = $items.eq(index); 530 $current.focus(); 531 532 if (event.keyCode === 32 || event.keyCode === 13) { 533 var $checkbox = $current.find('input'); 534 535 $checkbox.prop("checked", !$checkbox.prop("checked")); 536 $checkbox.change(); 537 } 538 539 event.stopPropagation(); 540 event.preventDefault(); 541 } 542 }, this)); 543 544 if(this.options.enableClickableOptGroups && this.options.multiple) { 545 $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) { 546 event.stopPropagation(); 547 548 var group = $(event.target).parent(); 549 550 // Search all option in optgroup 551 var $options = group.nextUntil('li.multiselect-group'); 552 553 // check or uncheck items 554 var allChecked = true; 555 var optionInputs = $options.find('input'); 556 optionInputs.each(function() { 557 allChecked = allChecked && $(this).prop('checked'); 558 }); 559 560 optionInputs.prop('checked', !allChecked).trigger('change'); 561 }, this)); 562 } 563 }, 564 565 /** 566 * Create an option using the given select option. 567 * 568 * @param {jQuery} element 569 */ 570 createOptionValue: function(element) { 571 var $element = $(element); 572 if ($element.is(':selected')) { 573 $element.prop('selected', true); 574 } 575
576 // Support the label attribute on options. 577 var label = this.options.label(element); 578 var value = $element.val(); 579 var inputType = this.options.multiple ? "checkbox" : "radio"; 580 581 var $li = $(this.options.templates.li); 582 var $label = $('label', $li); 583 $label.addClass(inputType); 584 585 var $checkbox = $('<input/>').attr('type', inputType); 586 587 if (this.options.checkboxName) { 588 $checkbox.attr('name', this.options.checkboxName); 589 } 590 $label.append($checkbox); 591 592 var selected = $element.prop('selected') || false; 593 $checkbox.val(value); 594 595 if (value === this.options.selectAllValue) { 596 $li.addClass("multiselect-item multiselect-all"); 597 $checkbox.parent().parent() 598 .addClass('multiselect-all'); 599 } 600 601 $label.append(" " + label); 602 $label.attr('title', $element.attr('title')); 603 604 this.$ul.append($li); 605 606 if ($element.is(':disabled')) { 607 $checkbox.attr('disabled', 'disabled') 608 .prop('disabled', true) 609 .closest('a') 610 .attr("tabindex", "-1") 611 .closest('li') 612 .addClass('disabled'); 613 } 614 615 $checkbox.prop('checked', selected); 616 617 if (selected && this.options.selectedClass) { 618 $checkbox.closest('li') 619 .addClass(this.options.selectedClass); 620 } 621 }, 622 623 /** 624 * Creates a divider using the given select option. 625 * 626 * @param {jQuery} element 627 */ 628 createDivider: function(element) { 629 var $divider = $(this.options.templates.divider); 630 this.$ul.append($divider); 631 }, 632 633 /** 634 * Creates an optgroup. 635 * 636 * @param {jQuery} group 637 */ 638 createOptgroup: function(group) { 639 var groupName = $(group).prop('label'); 640 641 // Add a header for the group. 642 var $li = $(this.options.templates.liGroup); 643 $('label', $li).text(groupName); 644 645 if (this.options.enableClickableOptGroups) { 646 $li.addClass('multiselect-group-clickable'); 647 } 648 649 this.$ul.append($li); 650 651 if ($(group).is(':disabled')) { 652 $li.addClass('disabled'); 653 } 654 655 // Add the options of the group. 656 $('option', group).each($.proxy(function(index, element) { 657 this.createOptionValue(element); 658 }, this)); 659 }, 660 661 /** 662 * Build the selct all. 663 * 664 * Checks if a select all has already been created. 665 */ 666 buildSelectAll: function() { 667 if (typeof this.options.selectAllValue === 'number') { 668 this.options.selectAllValue = this.options.selectAllValue.toString(); 669 } 670 671 var alreadyHasSelectAll = this.hasSelectAll(); 672 673 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple 674 && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) { 675 676 // Check whether to add a divider after the select all. 677 if (this.options.includeSelectAllDivider) { 678 this.$ul.prepend($(this.options.templates.divider)); 679 } 680 681 var $li = $(this.options.templates.li); 682 $('label', $li).addClass("checkbox"); 683 684 if (this.options.selectAllName) { 685 $('label', $li).append('<input type="checkbox" name="' + this.options.selectAllName + '" />'); 686 } 687 else { 688 $('label', $li).append('<input type="checkbox" />'); 689 } 690 691 var $checkbox = $('input', $li); 692 $checkbox.val(this.options.selectAllValue); 693 694 $li.addClass("multiselect-item multiselect-all"); 695 $checkbox.parent().parent() 696 .addClass('multiselect-all'); 697 698 $('label', $li).append(" " + this.options.selectAllText); 699 700 this.$ul.prepend($li); 701 702 $checkbox.prop('checked', false); 703 } 704 }, 705 706 /** 707 * Builds the filter. 708 */ 709 buildFilter: function() { 710 711 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. 712 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { 713 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); 714 715 if (this.$select.find('option').length >= enableFilterLength) { 716 717 this.$filter = $(this.options.templates.filter); 718 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); 719 720 // Adds optional filter clear button 721 if(this.options.includeFilterClearBtn){ 722 var clearBtn = $(this.options.templates.filterClearBtn); 723 clearBtn.on('click', $.proxy(function(event){ 724 clearTimeout(this.searchTimeout); 725 this.$filter.find('.multiselect-search').val(''); 726 $('li', this.$ul).show().removeClass("filter-hidden"); 727 this.updateSelectAll(); 728 }, this)); 729 this.$filter.find('.input-group').append(clearBtn); 730 } 731 732 this.$ul.prepend(this.$filter); 733 734 this.$filter.val(this.query).on('click', function(event) { 735 event.stopPropagation(); 736 }).on('input keydown', $.proxy(function(event) { 737 // Cancel enter key default behaviour 738 if (event.which === 13) { 739 event.preventDefault(); 740 } 741 742 // This is useful to catch "keydown" events after the browser has updated the control. 743 clearTimeout(this.searchTimeout); 744 745 this.searchTimeout = this.asyncFunction($.proxy(function() { 746 747 if (this.query !== event.target.value) { 748 this.query = event.target.value; 749 750 var currentGroup, currentGroupVisible; 751 $.each($('li', this.$ul), $.proxy(function(index, element) { 752 var value = $('input', element).val(); 753 var text = $('label', element).text(); 754 755 var filterCandidate = ''; 756 if ((this.options.filterBehavior === 'text')) { 757 filterCandidate = text; 758 } 759 else if ((this.options.filterBehavior === 'value')) { 760 filterCandidate = value; 761 } 762 else if (this.options.filterBehavior === 'both') { 763 filterCandidate = text + '\n' + value; 764 } 765 766 if (value !== this.options.selectAllValue && text) { 767 // By default lets assume that element is not 768 // interesting for this search. 769 var showElement = false;
770 771 if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) { 772 showElement = true; 773 } 774 else if (filterCandidate.indexOf(this.query) > -1) { 775 showElement = true; 776 } 777 778 // Toggle current element (group or group item) according to showElement boolean. 779 $(element).toggle(showElement).toggleClass('filter-hidden', !showElement); 780 781 // Differentiate groups and group items. 782 if ($(element).hasClass('multiselect-group')) { 783 // Remember group status. 784 currentGroup = element; 785 currentGroupVisible = showElement; 786 } 787 else { 788 // Show group name when at least one of its items is visible. 789 if (showElement) { 790 $(currentGroup).show().removeClass('filter-hidden'); 791 } 792 793 // Show all group items when group name satisfies filter. 794 if (!showElement && currentGroupVisible) { 795 $(element).show().removeClass('filter-hidden'); 796 } 797 } 798 } 799 }, this)); 800 } 801 802 this.updateSelectAll(); 803 }, this), 300, this); 804 }, this)); 805 } 806 } 807 }, 808 809 /** 810 * Unbinds the whole plugin. 811 */ 812 destroy: function() { 813 this.$container.remove(); 814 this.$select.show(); 815 this.$select.data('multiselect', null); 816 }, 817 818 /** 819 * Refreshs the multiselect based on the selected options of the select. 820 */ 821 refresh: function() { 822 $('option', this.$select).each($.proxy(function(index, element) { 823 var $input = $('li input', this.$ul).filter(function() { 824 return $(this).val() === $(element).val(); 825 }); 826 827 if ($(element).is(':selected')) { 828 $input.prop('checked', true); 829 830 if (this.options.selectedClass) { 831 $input.closest('li') 832 .addClass(this.options.selectedClass); 833 } 834 } 835 else { 836 $input.prop('checked', false); 837 838 if (this.options.selectedClass) { 839 $input.closest('li') 840 .removeClass(this.options.selectedClass); 841 } 842 } 843 844 if ($(element).is(":disabled")) { 845 $input.attr('disabled', 'disabled') 846 .prop('disabled', true) 847 .closest('li') 848 .addClass('disabled'); 849 } 850 else { 851 $input.prop('disabled', false) 852 .closest('li') 853 .removeClass('disabled'); 854 } 855 }, this)); 856 857 this.updateButtonText(); 858 this.updateSelectAll(); 859 }, 860 861 /** 862 * Select all options of the given values. 863 * 864 * If triggerOnChange is set to true, the on change event is triggered if 865 * and only if one value is passed. 866 * 867 * @param {Array} selectValues 868 * @param {Boolean} triggerOnChange 869 */ 870 select: function(selectValues, triggerOnChange) { 871 if(!$.isArray(selectValues)) { 872 selectValues = [selectValues]; 873 } 874 875 for (var i = 0; i < selectValues.length; i++) { 876 var value = selectValues[i]; 877 878 if (value === null || value === undefined) { 879 continue; 880 } 881 882 var $option = this.getOptionByValue(value); 883 var $checkbox = this.getInputByValue(value); 884 885 if($option === undefined || $checkbox === undefined) { 886 continue; 887 } 888 889 if (!this.options.multiple) { 890 this.deselectAll(false); 891 } 892 893 if (this.options.selectedClass) { 894 $checkbox.closest('li') 895 .addClass(this.options.selectedClass); 896 } 897 898 $checkbox.prop('checked', true); 899 $option.prop('selected', true); 900 } 901 902 this.updateButtonText(); 903 this.updateSelectAll(); 904 905 if (triggerOnChange && selectValues.length === 1) { 906 this.options.onChange($option, true); 907 } 908 }, 909 910 /** 911 * Clears all selected items. 912 */ 913 clearSelection: function () { 914 this.deselectAll(false); 915 this.updateButtonText(); 916 this.updateSelectAll(); 917 }, 918 919 /** 920 * Deselects all options of the given values. 921 * 922 * If triggerOnChange is set to true, the on change event is triggered, if 923 * and only if one value is passed. 924 * 925 * @param {Array} deselectValues 926 * @param {Boolean} triggerOnChange 927 */ 928 deselect: function(deselectValues, triggerOnChange) { 929 if(!$.isArray(deselectValues)) { 930 deselectValues = [deselectValues]; 931 } 932 933 for (var i = 0; i < deselectValues.length; i++) { 934 var value = deselectValues[i]; 935 936 if (value === null || value === undefined) { 937 continue; 938 } 939 940 var $option = this.getOptionByValue(value); 941 var $checkbox = this.getInputByValue(value); 942 943 if($option === undefined || $checkbox === undefined) { 944 continue; 945 } 946 947 if (this.options.selectedClass) { 948 $checkbox.closest('li') 949 .removeClass(this.options.selectedClass); 950 } 951 952 $checkbox.prop('checked', false); 953 $option.prop('selected', false); 954 } 955 956 this.updateButtonText(); 957 this.updateSelectAll(); 958 959 if (triggerOnChange && deselectValues.length === 1) { 960 this.options.onChange($option, false); 961 } 962 }, 963 964 /** 965 * Selects all enabled & visible options. 966 * 967 * If justVisible is true or not specified, only visible options are selected. 968 * 969 * @param {Boolean} justVisible 970 */ 971 selectAll: function (justVisible) { 972 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 973 var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul); 974 var visibleCheckboxes = allCheckboxes.filter(":visible"); 975 var allCheckboxesCount = allCheckboxes.length; 976 var visibleCheckboxesCount = visibleCheckboxes.length; 977 978 if(justVisible) { 979 visibleCheckboxes.prop('checked', true); 980 $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass); 981 } 982 else { 983 allCheckboxes.prop('checked', true); 984 $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass); 985 } 986 987 if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) { 988 $("option:enabled", this.$select).prop('selected', true); 989 } 990 else { 991 var values = visibleCheckboxes.map(function() { 992 return $(this).val(); 993 }).get(); 994 995 $("option:enabled", this.$select).filter(function(index) { 996 return $.inArray($(this).val(), values) !== -1; 997 }).prop('selected', true); 998 } 999 }, 1000 1001 /** 1002 * Deselects all options. 1003 * 1004 * If justVisible is true or not specified, only visible options are deselected. 1005 * 1006 * @param {Boolean} justVisible 1007 */ 1008 deselectAll: function (justVisible) { 1009 var justVisible = typeof justVisible === 'undefined' ? true : justVisible; 1010 1011 if(justVisible) { 1012 var visibleCheckboxes = $("li input[type='checkbox']:enabled", this.$ul).filter(":visible"); 1013 visibleCheckboxes.prop('checked', false); 1014 1015 var values = visibleCheckboxes.map(function() { 1016 return $(this).val(); 1017 }).get(); 1018 1019 $("option:enabled", this.$select).filter(function(index) { 1020 return $.inArray($(this).val(), values) !== -1; 1021 }).prop('selected', false); 1022 1023 if (this.options.selectedClass) { 1024 $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass); 1025 } 1026 } 1027 else { 1028 $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false); 1029 $("option:enabled", this.$select).prop('selected', false); 1030 1031 if (this.options.selectedClass) { 1032 $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass); 1033 } 1034 } 1035 }, 1036 1037 /** 1038 * Rebuild the plugin. 1039 * 1040 * Rebuilds the dropdown, the filter and the select all option. 1041 */ 1042 rebuild: function() { 1043 this.$ul.html(''); 1044 1045 // Important to distinguish between radios and checkboxes. 1046 this.options.multiple = this.$select.attr('multiple') === "multiple"; 1047 1048 this.buildSelectAll(); 1049 this.buildDropdownOptions(); 1050 this.buildFilter(); 1051 1052 this.updateButtonText(); 1053 this.updateSelectAll(); 1054 1055 if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { 1056 this.disable(); 1057 } 1058 1059 if (this.options.dropRight) { 1060 this.$ul.addClass('pull-right'); 1061 } 1062 }, 1063 1064 /** 1065 * The provided data will be used to build the dropdown. 1066 */ 1067 dataprovider: function(dataprovider) { 1068 var optionDOM = ""; 1069 var groupCounter = 0; 1070 var tags = $(''); // create empty jQuery array 1071 1072 $.each(dataprovider, function (index, option) { 1073 var tag; 1074 if ($.isArray(option.children)) { // create optiongroup tag 1075 groupCounter++; 1076 tag = $('<optgroup/>').attr({ 1077 label: option.label || 'Group ' + groupCounter 1078 });
1079 forEach(option.children, function(subOption) { // add children option tags 1080 tag.append($('<option/>').attr({ 1081 value: subOption.value, 1082 label: subOption.label || subOption.value, 1083 title: subOption.title, 1084 selected: !!subOption.selected 1085 })); 1086 }); 1087 1088 optionDOM += '</optgroup>'; 1089 } 1090 else { // create option tag 1091 tag = $('<option/>').attr({ 1092 value: option.value, 1093 label: option.label || option.value, 1094 title: option.title, 1095 selected: !!option.selected 1096 }); 1097 } 1098 1099 tags = tags.add(tag); 1100 }); 1101 1102 this.$select.empty().append(tags); 1103 this.rebuild(); 1104 }, 1105 1106 /** 1107 * Enable the multiselect. 1108 */ 1109 enable: function() { 1110 this.$select.prop('disabled', false); 1111 this.$button.prop('disabled', false) 1112 .removeClass('disabled'); 1113 }, 1114 1115 /** 1116 * Disable the multiselect. 1117 */ 1118 disable: function() { 1119 this.$select.prop('disabled', true); 1120 this.$button.prop('disabled', true) 1121 .addClass('disabled'); 1122 }, 1123 1124 /** 1125 * Set the options. 1126 * 1127 * @param {Array} options 1128 */ 1129 setOptions: function(options) { 1130 this.options = this.mergeOptions(options); 1131 }, 1132 1133 /** 1134 * Merges the given options with the default options. 1135 * 1136 * @param {Array} options 1137 * @returns {Array} 1138 */ 1139 mergeOptions: function(options) { 1140 return $.extend(true, {}, this.defaults, options); 1141 }, 1142 1143 /** 1144 * Checks whether a select all checkbox is present. 1145 * 1146 * @returns {Boolean} 1147 */ 1148 hasSelectAll: function() { 1149 return $('li.' + this.options.selectAllValue, this.$ul).length > 0; 1150 }, 1151 1152 /** 1153 * Updates the select all checkbox based on the currently displayed and selected checkboxes. 1154 */ 1155 updateSelectAll: function() { 1156 if (this.hasSelectAll()) { 1157 var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul); 1158 var allBoxesLength = allBoxes.length; 1159 var checkedBoxesLength = allBoxes.filter(":checked").length; 1160 var selectAllLi = $("li." + this.options.selectAllValue, this.$ul); 1161 var selectAllInput = selectAllLi.find("input"); 1162 1163 if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) { 1164 selectAllInput.prop("checked", true); 1165 selectAllLi.addClass(this.options.selectedClass); 1166 } 1167 else { 1168 selectAllInput.prop("checked", false); 1169 selectAllLi.removeClass(this.options.selectedClass); 1170 } 1171 } 1172 }, 1173 1174 /** 1175 * Update the button text and its title based on the currently selected options. 1176 */ 1177 updateButtonText: function() { 1178 var options = this.getSelected(); 1179 1180 // First update the displayed button text. 1181 $('.multiselect', this.$container).html(this.options.buttonText(options, this.$select)); 1182 1183 // Now update the title attribute of the button. 1184 $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); 1185 }, 1186 1187 /** 1188 * Get all selected options. 1189 * 1190 * @returns {jQUery} 1191 */ 1192 getSelected: function() { 1193 return $('option', this.$select).filter(":selected"); 1194 }, 1195 1196 /** 1197 * Gets a select option by its value. 1198 * 1199 * @param {String} value 1200 * @returns {jQuery} 1201 */ 1202 getOptionByValue: function (value) { 1203 1204 var options = $('option', this.$select); 1205 var valueToCompare = value.toString(); 1206 1207 for (var i = 0; i < options.length; i = i + 1) { 1208 var option = options[i]; 1209 if (option.value === valueToCompare) { 1210 return $(option); 1211 } 1212 } 1213 }, 1214 1215 /** 1216 * Get the input (radio/checkbox) by its value. 1217 * 1218 * @param {String} value 1219 * @returns {jQuery} 1220 */ 1221 getInputByValue: function (value) { 1222 1223 var checkboxes = $('li input', this.$ul); 1224 var valueToCompare = value.toString(); 1225 1226 for (var i = 0; i < checkboxes.length; i = i + 1) { 1227 var checkbox = checkboxes[i]; 1228 if (checkbox.value === valueToCompare) { 1229 return $(checkbox); 1230 } 1231 } 1232 }, 1233 1234 /** 1235 * Used for knockout integration. 1236 */ 1237 updateOriginalOptions: function() { 1238 this.originalOptions = this.$select.clone()[0].options; 1239 }, 1240 1241 asyncFunction: function(callback, timeout, self) { 1242 var args = Array.prototype.slice.call(arguments, 3); 1243 return setTimeout(function() { 1244 callback.apply(self || window, args); 1245 }, timeout); 1246 } 1247 }; 1248 1249 $.fn.multiselect = function(option, parameter, extraOptions) { 1250 return this.each(function() { 1251 var data = $(this).data('multiselect'); 1252 var options = typeof option === 'object' && option; 1253 1254 // Initialize the multiselect. 1255 if (!data) { 1256 data = new Multiselect(this, options); 1257 $(this).data('multiselect', data); 1258 } 1259 1260 // Call multiselect method. 1261 if (typeof option === 'string') { 1262 data[option](parameter, extraOptions); 1263 1264 if (option === 'destroy') { 1265 $(this).data('multiselect', false); 1266 } 1267 } 1268 }); 1269 }; 1270 1271 $.fn.multiselect.Constructor = Multiselect; 1272 1273 $(function() { 1274 $("select[data-role=multiselect]").multiselect(); 1275 }); 1276 1277}(window.jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.