Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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