PageSourceSearch

https://hydranome.com/catalog/view/javascript/bootstrap/js/bootstrap-multiselect.js

js hydranome.com collected 2026-10-02 00:30:28 UTC 47,919 bytes, 1,277 lines download raw bytes

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.