PageSourceSearch

https://campusgroups.rivier.edu/static/plugins/bootstrap-multiselect/dist/js/bootstrap-multiselect.js

js rivier.edu collected 2026-09-24 09:12:08 UTC 78,854 bytes, 1,935 lines download raw bytes

vendor: 14,883 bytes, lines 1-358
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 aria-label 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" role="group" aria-label="Selection" onfocusout="dropdownMenuFocusoutA11yHandler(this);" />',
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" aria-expanded="false" aria-haspopup="menu"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
443                ul: '<ul class="multiselect-container dropdown-menu" role="listbox" aria-multiselectable="true"></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" aria-label="Search Filter"/></div></li>',
445                filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button" aria-label="Clear Search"><i class="glyphicon glyphicon-remove-circle"></i></button></span>',
446                li: '<li><a tabindex="0"><div><label></label></div></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);
vendor: 4,846 bytes, lines 551-665
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                // Handle radio buttons
601                if ($target.is('input[type="radio"]')) {
602                    // Just handle the visual state and skip all select-related logic
603                    if (this.options.selectedClass) {
604                        if (checked) {
605                            $target.closest('li').addClass(this.options.selectedClass);
606                        } else {
607                            $target.closest('li').removeClass(this.options.selectedClass);
608                        }
609                    }           
610                    this.updateButtonText();
611
612                    // When it's not a single selection: exit early - no dropdown closing, no select option manipulation
613                    if(this.options.multiple) {
614                        if(this.options.preventInputChangeEvent) {
615                            return false;
616                        }
617                        return;
618                    }
619                }
620
621                var $optionsNotThis = $('option', this.$select).not($option);
622                var $checkboxesNotThis = $('input', this.$container).not($target);
623
624                if (isSelectAllOption) {
625
626                    if (checked) {
627                        this.selectAll(this.options.selectAllJustVisible, true);
628                    }
629                    else {
630                        this.deselectAll(this.options.selectAllJustVisible, true);
631                    }
632                }
633                else {
634                    if (checked) {
635                        $option.prop('selected', true);
636
637                        if (this.options.multiple) {
638                            // Simply select additional option.
639                            $option.prop('selected', true);
640                        }
641                        else {
642                            // Unselect all other options and corresponding checkboxes.
643                            if (this.options.selectedClass) {
644                                $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
645                            }
646
647                            $($checkboxesNotThis).prop('checked', false);
648                            $optionsNotThis.prop('selected', false);
649
650                            // It's a single selection, so close.
651                            this.$button.click();
652                        }
653
654                        if (this.options.selectedClass === "active") {
655                            $optionsNotThis.closest("a").css("outline", "");
656                        }
657                    }
658                    else {
659                        // Unselect option.
660                        $option.prop('selected', false);
661                    }
662
663                    // To prevent select all from firing onChange: #575
664                    this.options.onChange($option, checked);
665
666                    // Do not update select all or optgroups on select all change!
667                    this.updateSelectAll();
668
669                    if (this.options.enableClickableOptGroups && this.options.multiple) {
670                        this.updateOptGroups();
671                    }
672                }
673
674                this.$select.change();
675                this.updateButtonText();
676
677                if(this.options.preventInputChangeEvent) {
678                    return false;
679                }
680            }, this));
681
682            $('li a', this.$ul).on('mousedown', function(e) {
683                if (e.shiftKey) {
684                    // Prevent selecting text by Shift+click
685                    return false;
686                }
687            });
688
689            $(this.$ul).on('touchstart click', 'li a', $.proxy(function(event) {
690                event.stopPropagation();
691
692                var $target = $(event.target);
693
694                if (event.shiftKey && this.options.multiple) {
695                    if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
696                        event.preventDefault();
697                        $target = $target.find("input");
698                        $target.prop("checked", !$target.prop("checked"));
699                    }
700                    var checked = $target.prop('checked') || false;
701
702                    if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
703                        var from = this.$ul.find("li:visible").index($target.parents("li"));
704                        var to = this.$ul.find("li:visible").index(this.lastToggledInput.parents("li"));
705
706                        if (from > to) { // Swap the indices
707                            var tmp = to;
708                            to = from;
709                            from = tmp;
710                        }
711
712                        // Make sure we grab all elements since slice excludes the last index
713                        ++to;
714
715                        // Change the checkboxes and underlying options
716                        var range = this.$ul.find("li").not(".multiselect-filter-hidden").slice(from, to).find("input");
717
718                        range.prop('checked', checked);
719
720                        if (this.options.selectedClass) {
721                            range.closest('li')
722                                .toggleClass(this.options.selectedClass, checked);
723                        }
724
725                        for (var i = 0, j = range.length; i < j; i++) {
726                            var $checkbox = $(range[i]);
727
728                            var $option = this.getOptionByValue($checkbox.val());
729
730                            $option.prop('selected', checked);
731                        }
732                    }
733
734                    // Trigger the select "change" event
735                    $target.trigger("change");
736                }
737
738                // Remembers last clicked option
739                if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
740                    this.lastToggledInput = $target;
741                }
742
743                $target.blur();
744            }, this));
745
746            // Keyboard support.
747            this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
748                if ($('input[type="text"]', this.$container).is(':focus')) {
749                    return;
750                }
751
752                var $items = $(this.$container).find("li[role='option']:not(.disabled), input[type='radio']:not(.disabled)").filter(":visible");
753
754                if (!$items.length) {
755                    return;
756                }
757
758                // Tab key navigation - focus on li elements
759                if (event.keyCode === 9) {
760                    var currentFocused = $items.filter(':focus');
761                    var currentIndex = $items.index(currentFocused);
762
763                    if (event.shiftKey) {
764                        // Shift+Tab - go to previous item or exit
765                        if (currentIndex > 0) {
766                            $items.eq(currentIndex - 1).focus();
767                            event.preventDefault();
768                        } else {
769                            // At first item or no focus, close the dropdown if it is one and do nothing to let tab continue naturally
770                            if (this.$ul.hasClass('dropdown-menu')) {
771                                this.$button.click();
772                            }
773                        }
774                    } else {
775                        // Tab - go to next item or exit
776                        if (currentIndex >= 0 && currentIndex < $items.length - 1) {
777                            $items.eq(currentIndex + 1).focus();
778                            event.preventDefault();
779                        } else if (currentIndex === -1) {
780                            // First tab into dropdown
781                            $items.eq(0).focus();
782                            event.preventDefault();
783                        } else {
784                            // At last item, close the dropdown if it is one and do nothing to let tab continue naturally
785                            if (this.$ul.hasClass('dropdown-menu')) {
786                                this.$button.click();
787                            } else {
788                                // Since there is no dropdowns to close, the focus will go to the link inside the li which has a tabindex=0.
789                                // The focus will not be visible and the next time the user types the tab key, the currentIndex will be -1
790                                // and it will focus again the options from the start.
791                                // If there is no dropdowns to close, we need to find the second next focusable element in the dom, 
792                                // skipping the link contained in the li.
793                                var focusableSelectors = 'li[role="option"]:not(.disabled), input[type="radio"]:not(.disabled), a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
794                                var $allFocusable = $(focusableSelectors).filter(':visible').not('[aria-hidden="true"]');
795                                var containerIndex = $allFocusable.index(currentFocused);
796                                if (containerIndex >= 0 && containerIndex < $allFocusable.length - 2) {
797                                    $allFocusable.eq(containerIndex + 2).focus(); // +2 to skip the next one which is a link in the li.
798                                    event.preventDefault();
799                                }
800                            }
801                        }
802                    }
803                }
804                // Arrow key navigation
805                else {
806                    var index = $items.index($items.filter(':focus'));
807                    var newIndex = index;
808                    
809                    // Navigation up
810                    if (event.keyCode === 38) {
811                        if (index > 0) {
812                            newIndex = index - 1;
813                        } else if (index === 0) {
814                            // At first item, exit dropdown
815                            this.$button.focus();
816                            this.$button.click();
817                            return;
818                        } else if (index === -1) {
819                            // No item focused, focus last item
820                            newIndex = $items.length - 1;
821                        }
822                    }
823                    // Navigate down
824                    else if (event.keyCode === 40) {
825                        if (index >= 0 && index < $items.length - 1) {
826                            newIndex = index + 1;
827                        } else if (index === $items.length - 1) {
828                            // At last item, exit dropdown
829                            this.$button.focus();
830                            this.$button.click();
831                            return;
832                        } else if (index === -1) {
833                            // No item focused, focus first item
834                            newIndex = 0;
835                        }
836                    }
837
838                    // Only proceed if we have a valid index
839                    if (newIndex >= 0 && newIndex < $items.length) {
840                        var $current = $items.eq(newIndex);
841                        $current.focus();
842                    }
843
844                    if (event.keyCode === 32 || event.keyCode === 13) {
845                        var $focusedItem = $items.filter(':focus');
846                        if ($focusedItem.length) {
847                            // Handle radio buttons
848                            if ($focusedItem.is('input[type="radio"]')) {
849                                // For radio buttons, handle the state change manually without triggering change event
850                                // First, uncheck other radio buttons in the same group
851                                var radioName = $focusedItem.attr('name');
852                                if (radioName) {
853                                    $('input[type="radio"][name="' + radioName + '"]', this.$container).prop("checked", false);
854                                }
855                                // Check the focused radio button
856                                $focusedItem.prop("checked", true);
857                                // Update visual state manually (without triggering change event)
858                                if (this.options.selectedClass) {
859                                    $('input[type="radio"][name="' + radioName + '"]', this.$container).closest('li').removeClass(this.options.selectedClass);
860                                    $focusedItem.closest('li').addClass(this.options.selectedClass);
861                                }
862                                
863                                this.updateButtonText();
864                                
865                                // Trigger a custom radioclick event on the .radio container div
866                                // This will be caught by the new radioclick handler in listing_bootstrap__.inc
867                                // which doesn't have the problematic button click that closes the dropdown
868                                setTimeout(function() {
869                                    $focusedItem.closest('.radio').trigger('radioclick');
870                                }, 0);
871                                
872                                // Prevent any further event processing
873                                event.stopPropagation();
874                                event.preventDefault();
875                                return false;
876                            } else {
877                                // Handle regular checkboxes
878                                var $checkbox = $focusedItem.find('input[aria-hidden="true"]');
879                                var isChecked = !$checkbox.prop("checked");
880
881                                $checkbox.prop("checked", isChecked);
882                                $focusedItem.attr('aria-selected', isChecked ? 'true' : 'false');
883                                $checkbox.change();
884                            }
885                        }
886                    }
887
888                    event.stopPropagation();
889                    event.preventDefault();
890                }
891            }, this));
892
893            if (this.options.enableClickableOptGroups && this.options.multiple) {
894                $("li.multiselect-group input", this.$ul).on("change", $.proxy(function(event) {
895                    event.stopPropagation();
896
897                    var $target = $(event.target);
898                    var checked = $target.prop('checked') || false;
899
900                    var $li = $(event.target).closest('li');
901                    var $group = $li.nextUntil("li.multiselect-group")
902                        .not('.multiselect-filter-hidden')
903                        .not('.disabled');
904
905                    var $inputs = $group.find("input");
906
907                    var values = [];
908                    var $options = [];
909
910                    if (this.options.selectedClass) {
911                        if (checked) {
912                            $li.addClass(this.options.selectedClass);
913                        }
914                        else {
915                            $li.removeClass(this.options.selectedClass);
916                        }
917                    }
918
919                    $.each($inputs, $.proxy(function(index, input) {
920                        var value = $(input).val();
921                        var $option = this.getOptionByValue(value);
922
923                        if (checked) {
924                            $(input).prop('checked', true);
925                            $(input).closest('li')
926                                .addClass(this.options.selectedClass);
927
928                            $option.prop('selected', true);
929                        }
930                        else {
931                            $(input).prop('checked', false);
932                            $(input).closest('li')
933                                .removeClass(this.options.selectedClass);
934
935                            $option.prop('selected', false);
936                        }
937
938                        $options.push(this.getOptionByValue(value));
939                    }, this))
940
941                    // Cannot use select or deselect here because it would call updateOptGroups again.
942
943                    this.options.onChange($options, checked);
944
945                    this.$select.change();
946                    this.updateButtonText();
947                    this.updateSelectAll();
948                }, this));
949            }
950
951            if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
952                $("li.multiselect-group .caret-container", this.$ul).on("click", $.proxy(function(event) {
953                    var $li = $(event.target).closest('li');
954                    var $inputs = $li.nextUntil("li.multiselect-group")
955                            .not('.multiselect-filter-hidden');
956
957                    var visible = true;
958                    $inputs.each(function() {
959                        visible = visible && !$(this).hasClass('multiselect-collapsible-hidden');
960                    });
961
962                    if (visible) {
963                        $inputs.hide()
964                            .addClass('multiselect-collapsible-hidden');
965                    }
966                    else {
967                        $inputs.show()
968                            .removeClass('multiselect-collapsible-hidden');
969                    }
970                }, this));
971
972                $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
973                $("li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
974                $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
975            }
976        },
977
978        /**
979         * Create an option using the given select option.
980         *
981         * @param {jQuery} element
982         */
983        createOptionValue: function(element) {
984            var $element = $(element);
985            if ($element.is(':selected')) {
986                $element.prop('selected', true);
987            }
988
989            // Support the label attribute on options.
990            var label = this.options.optionLabel(element);
991            var classes = this.options.optionClass(element);
992            var value = $element.val();
993            var inputType = this.options.multiple ? "checkbox" : "radio";
994
995            var $li = $(this.options.templates.li);
996            var $label = $('label', $li);
997            var $div = $('div', $li);
998            $div.addClass(inputType);
999            $label.attr("aria-label", label);
1000            $li.addClass(classes);
1001
1002            // Hide all children items when collapseOptGroupsByDefault is true
1003            if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
1004                $li.addClass("multiselect-collapsible-hidden");
1005                $li.hide();
1006            }
1007
1008            if (this.options.enableHTML) {
1009                $label.html(label);
1010            }
1011            else {
1012                $label.text(" " + label);
1013            }
1014
1015            var $checkbox = $('<input/>').attr({
1016                'type': inputType,
1017                'aria-hidden': 'true'  // Hide from screen readers
1018            });
1019
1020            var name = this.options.checkboxName($element);
1021            if (name) {
1022                $checkbox.attr('name', name);
1023            }
1024
1025            $div.prepend($checkbox);
1026
1027            var selected = $element.prop('selected') || false;
1028            $checkbox.val(value);
1029
1030            $checkbox.attr('id', this.$select.attr('name') + '__' + inputType + '_' + value);
1031            $label.attr('for', this.$select.attr('name') + '__' + inputType + '_' + value);
1032            
1033            // Add accessibility attributes to the li
1034            $li.attr({
1035                'role': 'option',
1036                'aria-selected': selected ? 'true' : 'false',
1037                'data-value': value,
1038                'tabindex': '0'  // Make li elements focusable
1039            });
1040
1041            if (value === this.options.selectAllValue) {
1042                $li.addClass("multiselect-item multiselect-all");
1043                $checkbox.parent().parent()
1044                    .addClass('multiselect-all');
1045            } else {
1046                $li.addClass("acc-keyboard-mode--multiselect-item");
1047            }
1048
1049            $label.attr('aria-label', $element.attr('aria-label'));
1050
1051            this.$ul.append($li);
1052
1053            if ($element.is(':disabled')) {
1054                $checkbox.attr('disabled', 'disabled').prop('disabled', true);
1055                $li.attr('tabindex', '-1')
1056                   .attr('aria-disabled', 'true')
1057                   .addClass('disabled');
1058            }
1059
1060            $checkbox.prop('checked', selected);
1061
1062            if (selected && this.options.selectedClass) {
1063                $li.addClass(this.options.selectedClass);
1064            }
1065        },
1066
1067        /**
1068         * Creates a divider using the given select option.
1069         *
1070         * @param {jQuery} element
1071         */
1072        createDivider: function(element) {
1073            var $divider = $(this.options.templates.divider);
1074            this.$ul.append($divider);
1075        },
1076
1077        /**
1078         * Creates an optgroup.
1079         *
1080         * @param {jQuery} group
1081         */
1082        createOptgroup: function(group) {
1083            var label = $(group).attr("label");
1084            var value = $(group).attr("value");
1085            var $li = $('<li class="multiselect-item multiselect-group"><a href="javascript:void(0);"><label><b></b></label></a></li>');
1086
1087            var classes = this.options.optionClass(group);
1088            $li.addClass(classes);
1089
1090            if (this.options.enableHTML) {
1091                $('label b', $li).html(" " + label);
1092            }
1093            else {
1094                $('label b', $li).text(" " + label);
1095            }
1096
1097            if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
1098                $('a', $li).append('<span class="caret-container"><b class="caret"></b></span>');
1099            }
1100
1101            if (this.options.enableClickableOptGroups && this.options.multiple) {
1102                $('label', $li).prepend('<input type="checkbox" value="' + value + '"/>');
1103            }
1104
1105            if ($(group).is(':disabled')) {
1106                $li.addClass('disabled');
1107            }
1108
1109            this.$ul.append($li);
1110
1111            $("option", group).each($.proxy(function($, group) {
1112                this.createOptionValue(group);
1113            }, this))
1114        },
1115
1116        /**
1117         * Build the reset.
1118         *
1119         */
1120        buildReset: function() {
1121            if (this.options.includeResetOption) {
1122
1123                // Check whether to add a divider after the reset.
1124                if (this.options.includeResetDivider) {
1125                    this.$ul.prepend($(this.options.templates.divider));
1126                }
1127
1128                var $resetButton = $(this.options.templates.resetButton);
1129
1130                if (this.options.enableHTML) {
1131                    $('a', $resetButton).html(this.options.resetText);
1132                }
1133                else {
1134                    $('a', $resetButton).text(this.options.resetText);
1135                }
1136
1137                $('a', $resetButton).click($.proxy(function(){
1138                    this.clearSelection();
1139                }, this));
1140
1141                this.$ul.prepend($resetButton);
1142            }
1143        },
1144
1145        /**
1146         * Build the select all.
1147         *
1148         * Checks if a select all has already been created.
1149         */
1150        buildSelectAll: function() {
1151            if (typeof this.options.selectAllValue === 'number') {
1152                this.options.selectAllValue = this.options.selectAllValue.toString();
1153            }
1154
1155            var alreadyHasSelectAll = this.hasSelectAll();
1156
1157            if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
1158                    && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
1159
1160                // Check whether to add a divider after the select all.
1161                if (this.options.includeSelectAllDivider) {
1162                    this.$ul.prepend($(this.options.templates.divider));
1163                }
1164
1165                var $li = $(this.options.templates.li);
1166                $('div', $li).addClass("checkbox");
1167
1168                if (this.options.enableHTML) {
1169                    $('label', $li).html(" " + this.options.selectAllText);
1170                }
1171                else {
1172                    $('label', $li).text(" " + this.options.selectAllText);
1173                }
1174
1175                if (this.options.selectAllName) {
1176                    $('div', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
1177                }
1178                else {
1179                    $('div', $li).prepend('<input type="checkbox" />');
1180                }
1181
1182                var $checkbox = $('input', $li);
1183                $checkbox.val(this.options.selectAllValue);
1184
1185                $li.addClass("multiselect-item multiselect-all");
1186                $checkbox.parent().parent()
1187                    .addClass('multiselect-all');
1188
1189                this.$ul.prepend($li);
1190
1191                $checkbox.prop('checked', false);
1192            }
1193        },
1194
1195        /**
1196         * Builds the filter.
1197         */
1198        buildFilter: function() {
1199
1200            // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1201            if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
1202                var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
1203
1204                if (this.$select.find('option').length >= enableFilterLength) {
1205
1206                    this.$filter = $(this.options.templates.filter);
1207                    $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1208
1209                    // Adds optional filter clear button
1210                    if(this.options.includeFilterClearBtn) {
1211                        var clearBtn = $(this.options.templates.filterClearBtn);
1212                        var clearAction = $.proxy(function(event){
1213                            clearTimeout(this.searchTimeout);
1214
1215                            this.query = '';
1216                            this.$filter.find('.multiselect-search').val('');
1217                            $('li', this.$ul).show().removeClass('multiselect-filter-hidden');
1218
1219                            this.updateSelectAll();
1220
1221                            if (this.options.enableClickableOptGroups && this.options.multiple) {
1222                                this.updateOptGroups();
1223                            }
1224
1225                        }, this);
1226                        
1227                        clearBtn.on('click', clearAction);
1228                        
1229                        // Add keyboard support for Enter and Space keys
1230                        clearBtn.on('keyup', $.proxy(function(event){
1231                            // Enter (keyCode 13) or Space (keyCode 32)
1232                            if (event.keyCode === 13 || event.keyCode === 32) {
1233                                event.preventDefault();
1234                                event.stopPropagation();
1235                                clearAction(event);
1236                                // Return focus to search input after clearing
1237                                this.$filter.find('.multiselect-search').focus();
1238                            }
1239                        }, this));
1240                        
1241                        this.$filter.find('.input-group').append(clearBtn);
1242                    }
1243
1244                    this.$ul.prepend(this.$filter);
1245
1246                    this.$filter.val(this.query).on('click', function(event) {
1247                        event.stopPropagation();
1248                    }).on('input keydown', $.proxy(function(event) {
1249                        // Only process events originating from the search input;
1250                        // ignore bubbled keydowns from the clear-filter button
1251                        // (whose empty .value would reset the filter query).
1252                        if (!$(event.target).hasClass('multiselect-search')) {
1253                            return;
1254                        }
1255
1256                        // Cancel enter key default behaviour
1257                        if (event.which === 13) {
1258                          event.preventDefault();
1259                      }
1260
1261                        // This is useful to catch "keydown" events after the browser has updated the control.
1262                        clearTimeout(this.searchTimeout);
1263
1264                        this.searchTimeout = this.asyncFunction($.proxy(function() {
1265
1266                            if (this.query !== event.target.value) {
1267                                this.query = event.target.value;
1268
1269                                var currentGroup, currentGroupVisible;
1270                                $.each($('li', this.$ul), $.proxy(function(index, element) {
1271                                    var value = $('input', element).length > 0 ? $('input', element).val() : "";
1272                                    var text = $('label', element).text();
1273
1274                                    var filterCandidate = '';
1275                                    if ((this.options.filterBehavior === 'text')) {
1276                                        filterCandidate = text;
1277                                    }
1278                                    else if ((this.options.filterBehavior === 'value')) {
1279                                        filterCandidate = value;
1280                                    }
1281                                    else if (this.options.filterBehavior === 'both') {
1282                                        filterCandidate = text + '\n' + value;
1283                                    }
1284
1285                                    if (value !== this.options.selectAllValue && text) {
1286
1287                                        // By default lets assume that element is not
1288                                        // interesting for this search.
1289                                        var showElement = false;
vendor: 20,177 bytes, lines 1290-1825
1290
1291                                        if (this.options.enableCaseInsensitiveFiltering) {
1292                                            filterCandidate = filterCandidate.toLowerCase();
1293                                            this.query = this.query.toLowerCase();
1294                                        }
1295
1296                                        if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') {
1297                                            var valueToMatch = filterCandidate.trim().substring(0, this.query.length);
1298                                            if (this.query.indexOf(valueToMatch) > -1) {
1299                                                showElement = true;
1300                                            }
1301                                        }
1302                                        else if (filterCandidate.indexOf(this.query) > -1) {
1303                                            showElement = true;
1304                                        }
1305
1306                                        // Toggle current element (group or group item) according to showElement boolean.
1307                                        if(!showElement){
1308                                          $(element).css('display', 'none');
1309                                          $(element).addClass('multiselect-filter-hidden');
1310                                        }
1311                                        if(showElement){
1312                                          $(element).css('display', 'block');
1313                                          $(element).removeClass('multiselect-filter-hidden');
1314                                        }
1315
1316                                        // Differentiate groups and group items.
1317                                        if ($(element).hasClass('multiselect-group')) {
1318                                            // Remember group status.
1319                                            currentGroup = element;
1320                                            currentGroupVisible = showElement;
1321                                        }
1322                                        else {
1323                                            // Show group name when at least one of its items is visible.
1324                                            if (showElement) {
1325                                                $(currentGroup).show()
1326                                                    .removeClass('multiselect-filter-hidden');
1327                                            }
1328
1329                                            // Show all group items when group name satisfies filter.
1330                                            if (!showElement && currentGroupVisible) {
1331                                                $(element).show()
1332                                                    .removeClass('multiselect-filter-hidden');
1333                                            }
1334                                        }
1335                                    }
1336                                }, this));
1337                            }
1338
1339                            this.updateSelectAll();
1340
1341                            if (this.options.enableClickableOptGroups && this.options.multiple) {
1342                                this.updateOptGroups();
1343                            }
1344
1345                            this.options.onFiltering(event.target);
1346
1347                        }, this), 300, this);
1348                    }, this));
1349                }
1350            }
1351        },
1352
1353        /**
1354         * Unbinds the whole plugin.
1355         */
1356        destroy: function() {
1357            this.$container.remove();
1358            this.$select.show();
1359
1360            // reset original state
1361            this.$select.prop('disabled', this.options.wasDisabled);
1362
1363            this.$select.data('multiselect', null);
1364        },
1365
1366        /**
1367         * Refreshs the multiselect based on the selected options of the select.
1368         */
1369        refresh: function () {
1370            var inputs = {};
1371            $('li input', this.$ul).each(function() {
1372              inputs[$(this).val()] = $(this);
1373            });
1374
1375            $('option', this.$select).each($.proxy(function (index, element) {
1376                var $elem = $(element);
1377                var $input = inputs[$(element).val()];
1378
1379                if ($elem.is(':selected')) {
1380                    $input.prop('checked', true);
1381
1382                    if (this.options.selectedClass) {
1383                        $input.closest('li')
1384                            .addClass(this.options.selectedClass);
1385                    }
1386                }
1387                else {
1388                    $input.prop('checked', false);
1389
1390                    if (this.options.selectedClass) {
1391                        $input.closest('li')
1392                            .removeClass(this.options.selectedClass);
1393                    }
1394                }
1395
1396                if ($elem.is(":disabled")) {
1397                    $input.attr('disabled', 'disabled')
1398                        .prop('disabled', true)
1399                        .closest('li')
1400                        .addClass('disabled');
1401                }
1402                else {
1403                    $input.prop('disabled', false)
1404                        .closest('li')
1405                        .removeClass('disabled');
1406                }
1407            }, this));
1408
1409            this.updateButtonText();
1410            this.updateSelectAll();
1411
1412            if (this.options.enableClickableOptGroups && this.options.multiple) {
1413                this.updateOptGroups();
1414            }
1415        },
1416
1417        /**
1418         * Select all options of the given values.
1419         *
1420         * If triggerOnChange is set to true, the on change event is triggered if
1421         * and only if one value is passed.
1422         *
1423         * @param {Array} selectValues
1424         * @param {Boolean} triggerOnChange
1425         */
1426        select: function(selectValues, triggerOnChange) {
1427            if(!$.isArray(selectValues)) {
1428                selectValues = [selectValues];
1429            }
1430
1431            for (var i = 0; i < selectValues.length; i++) {
1432                var value = selectValues[i];
1433
1434                if (value === null || value === undefined) {
1435                    continue;
1436                }
1437
1438                var $option = this.getOptionByValue(value);
1439                var $checkbox = this.getInputByValue(value);
1440
1441                if($option === undefined || $checkbox === undefined) {
1442                    continue;
1443                }
1444
1445                if (!this.options.multiple) {
1446                    this.deselectAll(false);
1447                }
1448
1449                if (this.options.selectedClass) {
1450                    $checkbox.closest('li')
1451                        .addClass(this.options.selectedClass);
1452                }
1453
1454                $checkbox.prop('checked', true);
1455                $option.prop('selected', true);
1456
1457                if (triggerOnChange) {
1458                    this.options.onChange($option, true);
1459                }
1460            }
1461
1462            this.updateButtonText();
1463            this.updateSelectAll();
1464
1465            if (this.options.enableClickableOptGroups && this.options.multiple) {
1466                this.updateOptGroups();
1467            }
1468        },
1469
1470        /**
1471         * Clears all selected items.
1472         */
1473        clearSelection: function () {
1474            this.deselectAll(false);
1475            this.updateButtonText();
1476            this.updateSelectAll();
1477
1478            if (this.options.enableClickableOptGroups && this.options.multiple) {
1479                this.updateOptGroups();
1480            }
1481        },
1482
1483        /**
1484         * Deselects all options of the given values.
1485         *
1486         * If triggerOnChange is set to true, the on change event is triggered, if
1487         * and only if one value is passed.
1488         *
1489         * @param {Array} deselectValues
1490         * @param {Boolean} triggerOnChange
1491         */
1492        deselect: function(deselectValues, triggerOnChange) {
1493            if(!$.isArray(deselectValues)) {
1494                deselectValues = [deselectValues];
1495            }
1496
1497            for (var i = 0; i < deselectValues.length; i++) {
1498                var value = deselectValues[i];
1499
1500                if (value === null || value === undefined) {
1501                    continue;
1502                }
1503
1504                var $option = this.getOptionByValue(value);
1505                var $checkbox = this.getInputByValue(value);
1506
1507                if($option === undefined || $checkbox === undefined) {
1508                    continue;
1509                }
1510
1511                if (this.options.selectedClass) {
1512                    $checkbox.closest('li')
1513                        .removeClass(this.options.selectedClass);
1514                }
1515
1516                $checkbox.prop('checked', false);
1517                $option.prop('selected', false);
1518
1519                if (triggerOnChange) {
1520                    this.options.onChange($option, false);
1521                }
1522            }
1523
1524            this.updateButtonText();
1525            this.updateSelectAll();
1526
1527            if (this.options.enableClickableOptGroups && this.options.multiple) {
1528                this.updateOptGroups();
1529            }
1530        },
1531
1532        /**
1533         * Selects all enabled & visible options.
1534         *
1535         * If justVisible is true or not specified, only visible options are selected.
1536         *
1537         * @param {Boolean} justVisible
1538         * @param {Boolean} triggerOnSelectAll
1539         */
1540        selectAll: function (justVisible, triggerOnSelectAll) {
1541
1542            var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1543            var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1544            var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1545
1546            if(justVisible) {
1547                $('input:enabled' , visibleLis).prop('checked', true);
1548                visibleLis.addClass(this.options.selectedClass);
1549
1550                $('input:enabled' , visibleLis).each($.proxy(function(index, element) {
1551                    var value = $(element).val();
1552                    var option = this.getOptionByValue(value);
1553                    $(option).prop('selected', true);
1554                }, this));
1555            }
1556            else {
1557                $('input:enabled' , allLis).prop('checked', true);
1558                allLis.addClass(this.options.selectedClass);
1559
1560                $('input:enabled' , allLis).each($.proxy(function(index, element) {
1561                    var value = $(element).val();
1562                    var option = this.getOptionByValue(value);
1563                    $(option).prop('selected', true);
1564                }, this));
1565            }
1566
1567            $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', true);
1568
1569            if (this.options.enableClickableOptGroups && this.options.multiple) {
1570                this.updateOptGroups();
1571            }
1572
1573            if (triggerOnSelectAll) {
1574                this.options.onSelectAll();
1575            }
1576        },
1577
1578        /**
1579         * Deselects all options.
1580         *
1581         * If justVisible is true or not specified, only visible options are deselected.
1582         *
1583         * @param {Boolean} justVisible
1584         */
1585        deselectAll: function (justVisible, triggerOnDeselectAll) {
1586
1587            var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1588            var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
1589            var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
1590
1591            if(justVisible) {
1592                $('input[type="checkbox"]:enabled' , visibleLis).prop('checked', false);
1593                visibleLis.removeClass(this.options.selectedClass);
1594
1595                $('input[type="checkbox"]:enabled' , visibleLis).each($.proxy(function(index, element) {
1596                    var value = $(element).val();
1597                    var option = this.getOptionByValue(value);
1598                    $(option).prop('selected', false);
1599                }, this));
1600            }
1601            else {
1602                $('input[type="checkbox"]:enabled' , allLis).prop('checked', false);
1603                allLis.removeClass(this.options.selectedClass);
1604
1605                $('input[type="checkbox"]:enabled' , allLis).each($.proxy(function(index, element) {
1606                    var value = $(element).val();
1607                    var option = this.getOptionByValue(value);
1608                    $(option).prop('selected', false);
1609                }, this));
1610            }
1611
1612            $('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', false);
1613
1614            if (this.options.enableClickableOptGroups && this.options.multiple) {
1615                this.updateOptGroups();
1616            }
1617
1618            if (triggerOnDeselectAll) {
1619                this.options.onDeselectAll();
1620            }
1621        },
1622
1623        /**
1624         * Rebuild the plugin.
1625         *
1626         * Rebuilds the dropdown, the filter and the select all option.
1627         */
1628        rebuild: function() {
1629            this.$ul.html('');
1630
1631            // Important to distinguish between radios and checkboxes.
1632            this.options.multiple = this.$select.attr('multiple') === "multiple";
1633
1634            this.buildSelectAll();
1635            this.buildDropdownOptions();
1636            this.buildFilter();
1637
1638            this.updateButtonText();
1639            this.updateSelectAll(true);
1640
1641            if (this.options.enableClickableOptGroups && this.options.multiple) {
1642                this.updateOptGroups();
1643            }
1644
1645            if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1646                this.disable();
1647            }
1648            else {
1649                this.enable();
1650            }
1651
1652            if (this.options.dropRight) {
1653                this.$ul.addClass('pull-right');
1654            }
1655        },
1656
1657        /**
1658         * The provided data will be used to build the dropdown.
1659         */
1660        dataprovider: function(dataprovider) {
1661
1662            var groupCounter = 0;
1663            var $select = this.$select.empty();
1664
1665            $.each(dataprovider, function (index, option) {
1666                var $tag;
1667
1668                if ($.isArray(option.children)) { // create optiongroup tag
1669                    groupCounter++;
1670
1671                    $tag = $('<optgroup/>').attr({
1672                        label: option.label || 'Group ' + groupCounter,
1673                        disabled: !!option.disabled,
1674                        value: option.value
1675                    });
1676
1677                    forEach(option.children, function(subOption) { // add children option tags
1678                        var attributes = {
1679                            value: subOption.value,
1680                            label: subOption.label || subOption.value,
1681                            ariaLabel: subOption.ariaLabel,
1682                            selected: !!subOption.selected,
1683                            disabled: !!subOption.disabled
1684                        };
1685
1686                        //Loop through attributes object and add key-value for each attribute
1687                       for (var key in subOption.attributes) {
1688                            attributes['data-' + key] = subOption.attributes[key];
1689                       }
1690                         //Append original attributes + new data attributes to option
1691                        $tag.append($('<option/>').attr(attributes));
1692                    });
1693                }
1694                else {
1695
1696                    var attributes = {
1697                        'value': option.value,
1698                        'label': option.label || option.value,
1699                        'ariaLabel': option.ariaLabel,
1700                        'class': option['class'],
1701                        'selected': !!option['selected'],
1702                        'disabled': !!option['disabled']
1703                    };
1704                    //Loop through attributes object and add key-value for each attribute
1705                    for (var key in option.attributes) {
1706                      attributes['data-' + key] = option.attributes[key];
1707                    }
1708                    //Append original attributes + new data attributes to option
1709                    $tag = $('<option/>').attr(attributes);
1710
1711                    $tag.text(option.label || option.value);
1712                }
1713
1714                $select.append($tag);
1715            });
1716
1717            this.rebuild();
1718        },
1719
1720        /**
1721         * Enable the multiselect.
1722         */
1723        enable: function() {
1724            this.$select.prop('disabled', false);
1725            this.$button.prop('disabled', false)
1726                .removeClass('disabled');
1727        },
1728
1729        /**
1730         * Disable the multiselect.
1731         */
1732        disable: function() {
1733            this.$select.prop('disabled', true);
1734            this.$button.prop('disabled', true)
1735                .addClass('disabled');
1736        },
1737
1738        /**
1739         * Set the options.
1740         *
1741         * @param {Array} options
1742         */
1743        setOptions: function(options) {
1744            this.options = this.mergeOptions(options);
1745        },
1746
1747        /**
1748         * Merges the given options with the default options.
1749         *
1750         * @param {Array} options
1751         * @returns {Array}
1752         */
1753        mergeOptions: function(options) {
1754            return $.extend(true, {}, this.defaults, this.options, options);
1755        },
1756
1757        /**
1758         * Checks whether a select all checkbox is present.
1759         *
1760         * @returns {Boolean}
1761         */
1762        hasSelectAll: function() {
1763            return $('li.multiselect-all', this.$ul).length > 0;
1764        },
1765
1766        /**
1767         * Update opt groups.
1768         */
1769        updateOptGroups: function() {
1770            var $groups = $('li.multiselect-group', this.$ul)
1771            var selectedClass = this.options.selectedClass;
1772
1773            $groups.each(function() {
1774                var $options = $(this).nextUntil('li.multiselect-group')
1775                    .not('.multiselect-filter-hidden')
1776                    .not('.disabled');
1777
1778                var checked = true;
1779                $options.each(function() {
1780                    var $input = $('input', this);
1781
1782                    if (!$input.prop('checked')) {
1783                        checked = false;
1784                    }
1785                });
1786
1787                if (selectedClass) {
1788                    if (checked) {
1789                        $(this).addClass(selectedClass);
1790                    }
1791                    else {
1792                        $(this).removeClass(selectedClass);
1793                    }
1794                }
1795
1796                $('input', this).prop('checked', checked);
1797            });
1798        },
1799
1800        /**
1801         * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1802         */
1803        updateSelectAll: function(notTriggerOnSelectAll) {
1804            if (this.hasSelectAll()) {
1805                var allBoxes = $("li:not(.multiselect-item):not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$ul);
1806                var allBoxesLength = allBoxes.length;
1807                var checkedBoxesLength = allBoxes.filter(":checked").length;
1808                var selectAllLi  = $("li.multiselect-all", this.$ul);
1809                var selectAllInput = selectAllLi.find("input");
1810
1811                if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1812                    selectAllInput.prop("checked", true);
1813                    selectAllLi.addClass(this.options.selectedClass);
1814                }
1815                else {
1816                    selectAllInput.prop("checked", false);
1817                    selectAllLi.removeClass(this.options.selectedClass);
1818                }
1819            }
1820        },
1821
1822        /**
1823         * Update the button text and its aria-label based on the currently selected options.
1824         */
1825        updateButtonText: function() {
1826            var options = this.getSelected();
1827
1828            // First update the displayed button text.
1829            if (this.options.enableHTML) {
1830                $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select));
1831            }
1832            else {
1833                $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select));
1834            }
1835
1836            // Now update the aria-label attribute of the button.
1837            $('.multiselect', this.$container).attr('aria-label', this.options.buttonTitle(options, this.$select));
1838        },
1839
1840        /**
1841         * Get all selected options.
1842         *
1843         * @returns {jQUery}
1844         */
1845        getSelected: function() {
1846            return $('option', this.$select).filter(":selected");
1847        },
1848
1849        /**
1850         * Gets a select option by its value.
1851         *
1852         * @param {String} value
1853         * @returns {jQuery}
1854         */
1855        getOptionByValue: function (value) {
1856
1857            var options = $('option', this.$select);
1858            var valueToCompare = value.toString();
1859
1860            for (var i = 0; i < options.length; i = i + 1) {
1861                var option = options[i];
1862                if (option.value === valueToCompare) {
1863                    return $(option);
1864                }
1865            }
1866        },
1867
1868        /**
1869         * Get the input (radio/checkbox) by its value.
1870         *
1871         * @param {String} value
1872         * @returns {jQuery}
1873         */
1874        getInputByValue: function (value) {
1875
1876            var checkboxes = $('li input:not(.multiselect-search)', this.$ul);
1877            var valueToCompare = value.toString();
1878
1879            for (var i = 0; i < checkboxes.length; i = i + 1) {
1880                var checkbox = checkboxes[i];
1881                if (checkbox.value === valueToCompare) {
1882                    return $(checkbox);
1883                }
1884            }
1885        },
1886
1887        /**
1888         * Used for knockout integration.
1889         */
1890        updateOriginalOptions: function() {
1891            this.originalOptions = this.$select.clone()[0].options;
1892        },
1893
1894        asyncFunction: function(callback, timeout, self) {
1895            var args = Array.prototype.slice.call(arguments, 3);
1896            return setTimeout(function() {
1897                callback.apply(self || window, args);
1898            }, timeout);
1899        },
1900
1901        setAllSelectedText: function(allSelectedText) {
1902            this.options.allSelectedText = allSelectedText;
1903            this.updateButtonText();
1904        }
1905    };
1906
1907    $.fn.multiselect = function(option, parameter, extraOptions) {
1908        return this.each(function() {
1909            var data = $(this).data('multiselect');
1910            var options = typeof option === 'object' && option;
1911
1912            // Initialize the multiselect.
1913            if (!data) {
1914                data = new Multiselect(this, options);
1915                $(this).data('multiselect', data);
1916            }
1917
1918            // Call multiselect method.
1919            if (typeof option === 'string') {
1920                data[option](parameter, extraOptions);
1921
1922                if (option === 'destroy') {
1923                    $(this).data('multiselect', false);
1924                }
1925            }
1926        });
1927    };
1928
1929    $.fn.multiselect.Constructor = Multiselect;
1930
1931    $(function() {
1932        $("select[data-role=multiselect]").multiselect();
1933    });
1934
1935});

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.