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