1/** 2 * bootstrap-multiselect.js 3 * https://github.com/davidstutz/bootstrap-multiselect 4 * 5 * Copyright 2012 - 2014 David Stutz 6 * 7 * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0. 8 */ 9!function($) { 10 11 "use strict";// jshint ;_; 12 13 if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) { 14 ko.bindingHandlers.multiselect = { 15 16 init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 17 18 var listOfSelectedItems = allBindingsAccessor().selectedOptions, 19 config = ko.utils.unwrapObservable(valueAccessor()); 20 21 $(element).multiselect(config); 22 23 if (isObservableArray(listOfSelectedItems)) { 24 // Subscribe to the selectedOptions: ko.observableArray 25 listOfSelectedItems.subscribe(function (changes) { 26 var addedArray = [], deletedArray = []; 27 changes.forEach(function (change) { 28 switch (change.status) { 29 case 'added': 30 addedArray.push(change.value); 31 break; 32 case 'deleted': 33 deletedArray.push(change.value); 34 break; 35 } 36 }); 37 if (addedArray.length > 0) { 38 $(element).multiselect('select', addedArray); 39 }; 40 if (deletedArray.length > 0) { 41 $(element).multiselect('deselect', deletedArray); 42 }; 43 }, null, "arrayChange"); 44 } 45 }, 46 47 update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 48 49 var listOfItems = allBindingsAccessor().options, 50 ms = $(element).data('multiselect'), 51 config = ko.utils.unwrapObservable(valueAccessor()); 52 53 if (isObservableArray(listOfItems)) { 54 // Subscribe to the options: ko.observableArray incase it changes later 55 listOfItems.subscribe(function (theArray) { 56 $(element).multiselect('rebuild'); 57 }); 58 } 59 60 if (!ms) { 61 $(element).multiselect(config); 62 } 63 else { 64 ms.updateOriginalOptions(); 65 } 66 } 67 }; 68 } 69 70 function isObservableArray(obj) { 71 return ko.isObservable(obj) && !(obj.destroyAll === undefined); 72 } 73 74 /** 75 * Constructor to create a new multiselect using the given select. 76 * 77 * @param {jQuery} select 78 * @param {Object} options 79 * @returns {Multiselect} 80 */ 81 function Multiselect(select, options) { 82 83 this.options = this.mergeOptions(options); 84 this.$select = $(select); 85 86 // Initialization. 87 // We have to clone to create a new reference. 88 this.originalOptions = this.$select.clone()[0].options; 89 this.query = ''; 90 this.searchTimeout = null; 91 92 this.options.multiple = this.$select.attr('multiple') === "multiple"; 93 this.options.onChange = $.proxy(this.options.onChange, this); 94 this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this); 95 this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this); 96 97 // Build select all if enabled. 98 this.buildContainer(); 99 this.buildButton(); 100 this.buildSelectAll(); 101 this.buildDropdown(); 102 this.buildDropdownOptions(); 103 this.buildFilter(); 104 105 this.updateButtonText(); 106 this.updateSelectAll(); 107 108 this.$select.hide().after(this.$container); 109 }; 110 111 Multiselect.prototype = { 112 113 defaults: { 114 /**
115 * Default text function will either print 'None selected' in case no 116 * option is selected or a list of the selected options up to a length of 3 selected options. 117 * 118 * @param {jQuery} options 119 * @param {jQuery} select 120 * @returns {String} 121 */ 122 buttonText: function(options, select) { 123 if (options.length === 0) { 124 return this.nonSelectedText + ' <b class="caret"></b>'; 125 } 126 else { 127 if (options.length > this.numberDisplayed) { 128 return options.length + ' ' + this.nSelectedText + ' <b class="caret"></b>'; 129 } 130 else { 131 var selected = ''; 132 options.each(function() { 133 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html(); 134 135 selected += label + ', '; 136 }); 137 return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>'; 138 } 139 } 140 }, 141 /** 142 * Updates the title of the button similar to the buttonText function. 143 * @param {jQuery} options 144 * @param {jQuery} select 145 * @returns {@exp;selected@call;substr} 146 */ 147 buttonTitle: function(options, select) { 148 if (options.length === 0) { 149 return this.nonSelectedText; 150 } 151 else { 152 var selected = ''; 153 options.each(function () { 154 selected += $(this).text() + ', '; 155 }); 156 return selected.substr(0, selected.length - 2); 157 } 158 }, 159 /** 160 * Create a label. 161 * 162 * @param {jQuery} element 163 * @returns {String} 164 */ 165 label: function(element){ 166 return $(element).attr('label') || $(element).html(); 167 }, 168 /** 169 * Triggered on change of the multiselect. 170 * Not triggered when selecting/deselecting options manually. 171 * 172 * @param {jQuery} option 173 * @param {Boolean} checked 174 */ 175 onChange : function(option, checked) { 176 177 }, 178 /** 179 * Triggered when the dropdown is shown. 180 * 181 * @param {jQuery} event 182 */ 183 onDropdownShow: function(event) { 184 185 }, 186 /** 187 * Triggered when the dropdown is hidden. 188 * 189 * @param {jQuery} event 190 */ 191 onDropdownHide: function(event) { 192 193 }, 194 buttonClass: 'btn btn-default', 195 dropRight: false, 196 selectedClass: 'active', 197 buttonWidth: 'auto', 198 buttonContainer: '<div class="btn-group" />', 199 // Maximum height of the dropdown menu. 200 // If maximum height is exceeded a scrollbar will be displayed. 201 maxHeight: false, 202 checkboxName: 'multiselect', 203 includeSelectAllOption: false, 204 includeSelectAllIfMoreThan: 0, 205 selectAllText: ' Select all', 206 selectAllValue: 'multiselect-all', 207 enableFiltering: false, 208 enableCaseInsensitiveFiltering: false, 209 filterPlaceholder: 'Search', 210 // possible options: 'text', 'value', 'both' 211 filterBehavior: 'text', 212 preventInputChangeEvent: false, 213 nonSelectedText: 'None selected', 214 nSelectedText: 'selected', 215 numberDisplayed: 3, 216 templates: { 217 button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"></button>', 218 ul: '<ul class="multiselect-container dropdown-menu"></ul>', 219 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>', 220 li: '<li><a href="javascript:void(0);"><label></label></a></li>', 221 divider: '<li class="divider"></li>', 222 liGroup: '<li><label class="multiselect-group"></label></li>' 223 } 224 }, 225 226 constructor: Multiselect, 227 228 /** 229 * Builds the container of the multiselect. 230 */ 231 buildContainer: function() { 232 this.$container = $(this.options.buttonContainer); 233 this.$container.on('show.bs.dropdown', this.options.onDropdownShow); 234 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide); 235 }, 236 237 /** 238 * Builds the button of the multiselect. 239 */ 240 buildButton: function() { 241 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass); 242 243 // Adopt active state. 244 if (this.$select.prop('disabled')) { 245 this.disable(); 246 } 247 else { 248 this.enable(); 249 } 250 251 // Manually add button width if set. 252 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') { 253 this.$button.css({ 254 'width' : this.options.buttonWidth 255 }); 256 } 257 258 // Keep the tab index from the select. 259 var tabindex = this.$select.attr('tabindex'); 260 if (tabindex) { 261 this.$button.attr('tabindex', tabindex); 262 } 263 264 this.$container.prepend(this.$button); 265 }, 266 267 /** 268 * Builds the ul representing the dropdown menu. 269 */ 270 buildDropdown: function() { 271 272 // Build ul. 273 this.$ul = $(this.options.templates.ul); 274 275 if (this.options.dropRight) { 276 this.$ul.addClass('pull-right'); 277 } 278 279 // Set max height of dropdown menu to activate auto scrollbar. 280 if (this.options.maxHeight) { 281 // TODO: Add a class for this option to move the css declarations. 282 this.$ul.css({ 283 'max-height': this.options.maxHeight + 'px', 284 'overflow-y': 'auto', 285 'overflow-x': 'hidden' 286 }); 287 } 288 289 this.$container.append(this.$ul); 290 }, 291 292 /** 293 * Build the dropdown options and binds all nessecary events. 294 * Uses createDivider and createOptionValue to create the necessary options. 295 */ 296 buildDropdownOptions: function() { 297 298 this.$select.children().each($.proxy(function(index, element) { 299
300 // Support optgroups and options without a group simultaneously. 301 var tag = $(element).prop('tagName') 302 .toLowerCase(); 303 304 if (tag === 'optgroup') { 305 this.createOptgroup(element); 306 } 307 else if (tag === 'option') { 308 309 if ($(element).data('role') === 'divider') { 310 this.createDivider(); 311 } 312 else { 313 this.createOptionValue(element); 314 } 315 316 } 317 318 // Other illegal tags will be ignored. 319 }, this)); 320 321 // Bind the change event on the dropdown elements. 322 $('li input', this.$ul).on('change', $.proxy(function(event) { 323 var $target = $(event.target); 324 325 var checked = $target.prop('checked') || false; 326 var isSelectAllOption = $target.val() === this.options.selectAllValue; 327 328 // Apply or unapply the configured selected class. 329 if (this.options.selectedClass) { 330 if (checked) { 331 $target.parents('li') 332 .addClass(this.options.selectedClass); 333 } 334 else { 335 $target.parents('li') 336 .removeClass(this.options.selectedClass); 337 } 338 } 339 340 // Get the corresponding option. 341 var value = $target.val(); 342 var $option = this.getOptionByValue(value); 343 344 var $optionsNotThis = $('option', this.$select).not($option); 345 var $checkboxesNotThis = $('input', this.$container).not($target); 346 347 if (isSelectAllOption) { 348 var values = []; 349 350 // Select the visible checkboxes except the "select-all" and possible divider. 351 var availableInputs = $('li input[value!="' + this.options.selectAllValue + '"][data-role!="divider"]', this.$ul).filter(':visible'); 352 for (var i = 0, j = availableInputs.length; i < j; i++) { 353 values.push(availableInputs[i].value); 354 } 355 356 if (checked) { 357 this.select(values); 358 } 359 else { 360 this.deselect(values); 361 } 362 } 363 364 if (checked) { 365 $option.prop('selected', true); 366 367 if (this.options.multiple) { 368 // Simply select additional option. 369 $option.prop('selected', true); 370 } 371 else { 372 // Unselect all other options and corresponding checkboxes. 373 if (this.options.selectedClass) { 374 $($checkboxesNotThis).parents('li').removeClass(this.options.selectedClass); 375 } 376 377 $($checkboxesNotThis).prop('checked', false); 378 $optionsNotThis.prop('selected', false); 379 380 // It's a single selection, so close. 381 this.$button.click(); 382 } 383 384 if (this.options.selectedClass === "active") { 385 $optionsNotThis.parents("a").css("outline", ""); 386 } 387 } 388 else { 389 // Unselect option. 390 $option.prop('selected', false); 391 } 392 393 this.$select.change(); 394 this.options.onChange($option, checked); 395 396 this.updateButtonText(); 397 this.updateSelectAll(); 398 399 if(this.options.preventInputChangeEvent) { 400 return false; 401 } 402 }, this)); 403 404 $('li a', this.$ul).on('touchstart click', function(event) { 405 event.stopPropagation(); 406 407 var $target = $(event.target); 408 409 if (event.shiftKey) { 410 var checked = $target.prop('checked') || false; 411 412 if (checked) { 413 var prev = $target.parents('li:last') 414 .siblings('li[class="active"]:first'); 415 416 var currentIdx = $target.parents('li') 417 .index(); 418 var prevIdx = prev.index(); 419 420 if (currentIdx > prevIdx) { 421 $target.parents("li:last").prevUntil(prev).each( 422 function() { 423 $(this).find("input:first").prop("checked", true) 424 .trigger("change"); 425 } 426 ); 427 } 428 else { 429 $target.parents("li:last").nextUntil(prev).each( 430 function() { 431 $(this).find("input:first").prop("checked", true) 432 .trigger("change"); 433 } 434 ); 435 } 436 } 437 } 438 439 $target.blur(); 440 }); 441 442 // Keyboard support. 443 this.$container.on('keydown', $.proxy(function(event) { 444 if ($('input[type="text"]', this.$container).is(':focus')) { 445 return; 446 } 447 if ((event.keyCode === 9 || event.keyCode === 27) 448 && this.$container.hasClass('open')) { 449 450 // Close on tab or escape. 451 this.$button.click(); 452 } 453 else { 454 var $items = $(this.$container).find("li:not(.divider):visible a"); 455 456 if (!$items.length) { 457 return; 458 } 459 460 var index = $items.index($items.filter(':focus')); 461 462 // Navigation up. 463 if (event.keyCode === 38 && index > 0) { 464 index--; 465 } 466 // Navigate down. 467 else if (event.keyCode === 40 && index < $items.length - 1) { 468 index++; 469 } 470 else if (!~index) { 471 index = 0; 472 } 473 474 var $current = $items.eq(index); 475 $current.focus(); 476 477 if (event.keyCode === 32 || event.keyCode === 13) { 478 var $checkbox = $current.find('input'); 479 480 $checkbox.prop("checked", !$checkbox.prop("checked")); 481 $checkbox.change(); 482 } 483 484 event.stopPropagation(); 485 event.preventDefault(); 486 } 487 }, this)); 488 }, 489 490 /** 491 * Create an option using the given select option. 492 * 493 * @param {jQuery} element 494 */ 495 createOptionValue: function(element) { 496 if ($(element).is(':selected')) { 497 $(element).prop('selected', true); 498 } 499
500 // Support the label attribute on options. 501 var label = this.options.label(element); 502 var value = $(element).val(); 503 var inputType = this.options.multiple ? "checkbox" : "radio"; 504 505 var $li = $(this.options.templates.li); 506 $('label', $li).addClass(inputType); 507 $('label', $li).append('<input type="' + inputType + '" name="' + this.options.checkboxName + '" />'); 508 509 var selected = $(element).prop('selected') || false; 510 var $checkbox = $('input', $li); 511 $checkbox.val(value); 512 513 if (value === this.options.selectAllValue) { 514 $checkbox.parent().parent() 515 .addClass('multiselect-all'); 516 } 517 518 $('label', $li).append(" " + label); 519 520 this.$ul.append($li); 521 522 if ($(element).is(':disabled')) { 523 $checkbox.attr('disabled', 'disabled') 524 .prop('disabled', true) 525 .parents('li') 526 .addClass('disabled'); 527 } 528 529 $checkbox.prop('checked', selected); 530 531 if (selected && this.options.selectedClass) { 532 $checkbox.parents('li') 533 .addClass(this.options.selectedClass); 534 } 535 }, 536 537 /** 538 * Creates a divider using the given select option. 539 * 540 * @param {jQuery} element 541 */ 542 createDivider: function(element) { 543 var $divider = $(this.options.templates.divider); 544 this.$ul.append($divider); 545 }, 546 547 /** 548 * Creates an optgroup. 549 * 550 * @param {jQuery} group 551 */ 552 createOptgroup: function(group) { 553 var groupName = $(group).prop('label'); 554 555 // Add a header for the group. 556 var $li = $(this.options.templates.liGroup); 557 $('label', $li).text(groupName); 558 559 this.$ul.append($li); 560 561 if ($(group).is(':disabled')) { 562 $li.addClass('disabled'); 563 } 564 565 // Add the options of the group. 566 $('option', group).each($.proxy(function(index, element) { 567 this.createOptionValue(element); 568 }, this)); 569 }, 570 571 /** 572 * Build the selct all. 573 * Checks if a select all ahs already been created. 574 */ 575 buildSelectAll: function() { 576 var alreadyHasSelectAll = this.hasSelectAll(); 577 578 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple 579 && $('option[data-role!="divider"]', this.$select).length > this.options.includeSelectAllIfMoreThan) { 580 581 // Check whether to add a divider after the select all. 582 if (this.options.includeSelectAllDivider) { 583 this.$select.prepend('<option value="" disabled="disabled" data-role="divider">'); 584 } 585 586 this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>'); 587 } 588 }, 589 590 /** 591 * Builds the filter. 592 */ 593 buildFilter: function() { 594 595 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength. 596 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { 597 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering); 598 599 if (this.$select.find('option').length >= enableFilterLength) { 600 601 this.$filter = $(this.options.templates.filter); 602 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder); 603 this.$ul.prepend(this.$filter); 604 605 this.$filter.val(this.query).on('click', function(event) { 606 event.stopPropagation(); 607 }).on('input keydown', $.proxy(function(event) { 608 // This is useful to catch "keydown" events after the browser has updated the control. 609 clearTimeout(this.searchTimeout); 610 611 this.searchTimeout = this.asyncFunction($.proxy(function() { 612 613 if (this.query !== event.target.value) { 614 this.query = event.target.value; 615 616 $.each($('li', this.$ul), $.proxy(function(index, element) { 617 var value = $('input', element).val(); 618 var text = $('label', element).text(); 619 620 var filterCandidate = ''; 621 if ((this.options.filterBehavior === 'text')) { 622 filterCandidate = text; 623 } 624 else if ((this.options.filterBehavior === 'value')) { 625 filterCandidate = value; 626 } 627 else if (this.options.filterBehavior === 'both') { 628 filterCandidate = text + '\n' + value; 629 } 630 631 if (value !== this.options.selectAllValue && text) { 632 // by default lets assume that element is not 633 // interesting for this search 634 var showElement = false;
635 636 if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) { 637 showElement = true; 638 } 639 else if (filterCandidate.indexOf(this.query) > -1) { 640 showElement = true; 641 } 642 643 if (showElement) { 644 $(element).show(); 645 } 646 else { 647 $(element).hide(); 648 } 649 } 650 }, this)); 651 } 652 653 // TODO: check whether select all option needs to be updated. 654 }, this), 300, this); 655 }, this)); 656 } 657 } 658 }, 659 660 /** 661 * Unbinds the whole plugin. 662 */ 663 destroy: function() { 664 this.$container.remove(); 665 this.$select.show(); 666 this.$select.data('multiselect', null); 667 }, 668 669 /** 670 * Refreshs the multiselect based on the selected options of the select. 671 */ 672 refresh: function() { 673 $('option', this.$select).each($.proxy(function(index, element) { 674 var $input = $('li input', this.$ul).filter(function() { 675 return $(this).val() === $(element).val(); 676 }); 677 678 if ($(element).is(':selected')) { 679 $input.prop('checked', true); 680 681 if (this.options.selectedClass) { 682 $input.parents('li') 683 .addClass(this.options.selectedClass); 684 } 685 } 686 else { 687 $input.prop('checked', false); 688 689 if (this.options.selectedClass) { 690 $input.parents('li') 691 .removeClass(this.options.selectedClass); 692 } 693 } 694 695 if ($(element).is(":disabled")) { 696 $input.attr('disabled', 'disabled') 697 .prop('disabled', true) 698 .parents('li') 699 .addClass('disabled'); 700 } 701 else { 702 $input.prop('disabled', false) 703 .parents('li') 704 .removeClass('disabled'); 705 } 706 }, this)); 707 708 this.updateButtonText(); 709 this.updateSelectAll(); 710 }, 711 712 /** 713 * Select all options of the given values. 714 * 715 * @param {Array} selectValues 716 */ 717 select: function(selectValues) { 718 if(!$.isArray(selectValues)) { 719 selectValues = [selectValues]; 720 } 721 722 for (var i = 0; i < selectValues.length; i++) { 723 var value = selectValues[i]; 724 725 var $option = this.getOptionByValue(value); 726 var $checkbox = this.getInputByValue(value); 727 728 if (this.options.selectedClass) { 729 $checkbox.parents('li') 730 .addClass(this.options.selectedClass); 731 } 732 733 $checkbox.prop('checked', true); 734 $option.prop('selected', true); 735 } 736 737 this.updateButtonText(); 738 }, 739 740 /** 741 * Clears all selected items 742 * 743 */ 744 clearSelection: function () { 745 746 var selected = this.getSelected(); 747 748 if (selected.length) { 749 750 var arry = []; 751 752 for (var i = 0; i < selected.length; i = i + 1) { 753 arry.push(selected[i].value); 754 } 755 756 this.deselect(arry); 757 this.$select.change(); 758 } 759 }, 760 761 /** 762 * Deselects all options of the given values. 763 * 764 * @param {Array} deselectValues 765 */ 766 deselect: function(deselectValues) { 767 if(!$.isArray(deselectValues)) { 768 deselectValues = [deselectValues]; 769 } 770 771 for (var i = 0; i < deselectValues.length; i++) { 772 773 var value = deselectValues[i]; 774 775 var $option = this.getOptionByValue(value); 776 var $checkbox = this.getInputByValue(value); 777 778 if (this.options.selectedClass) { 779 $checkbox.parents('li') 780 .removeClass(this.options.selectedClass); 781 } 782 783 $checkbox.prop('checked', false); 784 $option.prop('selected', false); 785 } 786 787 this.updateButtonText(); 788 }, 789 790 /** 791 * Rebuild the plugin. 792 * Rebuilds the dropdown, the filter and the select all option. 793 */ 794 rebuild: function() { 795 this.$ul.html(''); 796 797 // Remove select all option in select. 798 $('option[value="' + this.options.selectAllValue + '"]', this.$select).remove(); 799 800 // Important to distinguish between radios and checkboxes. 801 this.options.multiple = this.$select.attr('multiple') === "multiple"; 802 803 this.buildSelectAll(); 804 this.buildDropdownOptions(); 805 this.buildFilter(); 806 807 this.updateButtonText(); 808 this.updateSelectAll(); 809 }, 810 811 /** 812 * The provided data will be used to build the dropdown. 813 * 814 * @param {Array} dataprovider 815 */ 816 dataprovider: function(dataprovider) { 817 var optionDOM = ""; 818 dataprovider.forEach(function (option) { 819 optionDOM += '<option value="' + option.value + '">' + option.label + '</option>'; 820 }); 821 822 this.$select.html(optionDOM); 823 this.rebuild(); 824 }, 825 826 /** 827 * Enable the multiselect. 828 */ 829 enable: function() { 830 this.$select.prop('disabled', false); 831 this.$button.prop('disabled', false) 832 .removeClass('disabled'); 833 }, 834 835 /** 836 * Disable the multiselect. 837 */ 838 disable: function() { 839 this.$select.prop('disabled', true); 840 this.$button.prop('disabled', true) 841 .addClass('disabled'); 842 }, 843 844 /** 845 * Set the options. 846 * 847 * @param {Array} options 848 */ 849 setOptions: function(options) { 850 this.options = this.mergeOptions(options); 851 }, 852 853 /** 854 * Merges the given options with the default options. 855 * 856 * @param {Array} options 857 * @returns {Array} 858 */ 859 mergeOptions: function(options) { 860 return $.extend(true, {}, this.defaults, options); 861 }, 862 863 /** 864 * Checks whether a select all option is present. 865 * 866 * @returns {Boolean} 867 */ 868 hasSelectAll: function() { 869 return $('option[value="' + this.options.selectAllValue + '"]', this.$select).length > 0; 870 }, 871 872 /** 873 * Updates the select all option based on the currently selected options. 874 */ 875 updateSelectAll: function() { 876 if (this.hasSelectAll()) {
877 var selected = this.getSelected(); 878 879 if (selected.length === $('option:not([data-role=divider])', this.$select).length - 1) { 880 this.select(this.options.selectAllValue); 881 } 882 else { 883 this.deselect(this.options.selectAllValue); 884 } 885 } 886 }, 887 888 /** 889 * Update the button text and its title based on the currently selected options. 890 */ 891 updateButtonText: function() { 892 var options = this.getSelected(); 893 894 // First update the displayed button text. 895 $('button', this.$container).html(this.options.buttonText(options, this.$select)); 896 897 // Now update the title attribute of the button. 898 $('button', this.$container).attr('title', this.options.buttonTitle(options, this.$select)); 899 900 }, 901 902 /** 903 * Get all selected options. 904 * 905 * @returns {jQUery} 906 */ 907 getSelected: function() { 908 return $('option[value!="' + this.options.selectAllValue + '"]:selected', this.$select).filter(function() { 909 return $(this).prop('selected'); 910 }); 911 }, 912 913 /** 914 * Gets a select option by its value. 915 * 916 * @param {String} value 917 * @returns {jQuery} 918 */ 919 getOptionByValue: function (value) { 920 921 var options = $('option', this.$select); 922 var valueToCompare = value.toString(); 923 924 for (var i = 0; i < options.length; i = i + 1) { 925 var option = options[i]; 926 if (option.value === valueToCompare) { 927 return $(option); 928 } 929 } 930 }, 931 932 /** 933 * Get the input (radio/checkbox) by its value. 934 * 935 * @param {String} value 936 * @returns {jQuery} 937 */ 938 getInputByValue: function (value) { 939 940 var checkboxes = $('li input', this.$ul); 941 var valueToCompare = value.toString(); 942 943 for (var i = 0; i < checkboxes.length; i = i + 1) { 944 var checkbox = checkboxes[i]; 945 if (checkbox.value === valueToCompare) { 946 return $(checkbox); 947 } 948 } 949 }, 950 951 /** 952 * Used for knockout integration. 953 */ 954 updateOriginalOptions: function() { 955 this.originalOptions = this.$select.clone()[0].options; 956 }, 957 958 asyncFunction: function(callback, timeout, self) { 959 var args = Array.prototype.slice.call(arguments, 3); 960 return setTimeout(function() { 961 callback.apply(self || window, args); 962 }, timeout); 963 } 964 }; 965 966 $.fn.multiselect = function(option, parameter) { 967 return this.each(function() { 968 var data = $(this).data('multiselect'); 969 var options = typeof option === 'object' && option; 970 971 // Initialize the multiselect. 972 if (!data) { 973 data = new Multiselect(this, options); 974 $(this).data('multiselect', data); 975 } 976 977 // Call multiselect method. 978 if (typeof option === 'string') { 979 data[option](parameter); 980 981 if (option === 'destroy') { 982 $(this).data('multiselect', false); 983 } 984 } 985 }); 986 }; 987 988 $.fn.multiselect.Constructor = Multiselect; 989 990 $(function() { 991 $("select[data-role=multiselect]").multiselect(); 992 }); 993 994}(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.