PageSourceSearch

https://asia-guide.biz/wp-content/themes/noo-jobmonster/framework/…/bootstrap-multiselect.js

js asia-guide.biz collected 2026-10-08 09:22:47 UTC 68,996 bytes, 1,776 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.