PageSourceSearch

https://www.nakheel.com/ResourcePackages/NakheelComponents/assets/…?package=NakheelComponents

js nakheel.com collected 2026-10-02 04:48:01 UTC 66,416 bytes, 1,630 lines download raw bytes

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

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