PageSourceSearch

https://www.ville-guyancourt.fr/wp-content/themes/novacity/assets/libs/octopus/dist/js/octopus.js?ver=2.18.3

js ville-guyancourt.fr collected 2026-09-24 10:15:44 UTC 80,131 bytes, 2,129 lines download raw bytes

1var is_touch_device = false;
2if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
3    var octopus = document.getElementsByClassName('octopus');
4    for (var i = 0; i < octopus.length; i++) {
5        octopus[i].className = octopus[i].className + ' octopus-touch';
6    }
7    is_touch_device = true;
8}
9;/**
10 * Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect)
11 * 
12 * Apache License, Version 2.0:
13 * Copyright (c) 2012 - 2015 David Stutz
14 * 
15 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
16 * use this file except in compliance with the License. You may obtain a
17 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0
18 * 
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
21 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
22 * License for the specific language governing permissions and limitations
23 * under the License.
24 * 
25 * BSD 3-Clause License:
26 * Copyright (c) 2012 - 2015 David Stutz
27 * All rights reserved.
28 * 
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions are met:
31 *    - Redistributions of source code must retain the above copyright notice,
32 *      this list of conditions and the following disclaimer.
33 *    - Redistributions in binary form must reproduce the above copyright notice,
34 *      this list of conditions and the following disclaimer in the documentation
35 *      and/or other materials provided with the distribution.
36 *    - Neither the name of David Stutz nor the names of its contributors may be
37 *      used to endorse or promote products derived from this software without
38 *      specific prior written permission.
39 * 
40 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
41 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
42 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
44 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
46 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
47 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
48 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
49 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
50 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 */
52!function ($) {
53    "use strict";// jshint ;_;
54
55    if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
56        ko.bindingHandlers.multiselect = {
57            after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
58
59            init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
60                var $element = $(element);
61                var config = ko.toJS(valueAccessor());
62
63                $element.multiselect(config);
64
65                if (allBindings.has('options')) {
66                    var options = allBindings.get('options');
67                    if (ko.isObservable(options)) {
68                        ko.computed({
69                            read: function() {
70                                options();
71                                setTimeout(function() {
72                                    var ms = $element.data('multiselect');
73                                    if (ms)
74                                        ms.updateOriginalOptions();//Not sure how beneficial this is.
75                                    $element.multiselect('rebuild');
76                                }, 1);
77                            },
78                            disposeWhenNodeIsRemoved: element
79                        });
80                    }
81                }
82
83                //value and selectedOptions are two-way, so these will be triggered even by our own actions.
84                //It needs some way to tell if they are triggered because of us or because of outside change.
85                //It doesn't loop but it's a waste of processing.
86                if (allBindings.has('value')) {
87                    var value = allBindings.get('value');
88                    if (ko.isObservable(value)) {
89                        ko.computed({
90                            read: function() {
vendor: 5,487 bytes, lines 91-231
91                                value();
92                                setTimeout(function() {
93                                    $element.multiselect('refresh');
94                                }, 1);
95                            },
96                            disposeWhenNodeIsRemoved: element
97                        }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
98                    }
99                }
100
101                //Switched from arrayChange subscription to general subscription using 'refresh'.
102                //Not sure performance is any better using 'select' and 'deselect'.
103                if (allBindings.has('selectedOptions')) {
104                    var selectedOptions = allBindings.get('selectedOptions');
105                    if (ko.isObservable(selectedOptions)) {
106                        ko.computed({
107                            read: function() {
108                                selectedOptions();
109                                setTimeout(function() {
110                                    $element.multiselect('refresh');
111                                }, 1);
112                            },
113                            disposeWhenNodeIsRemoved: element
114                        }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
115                    }
116                }
117
118                var setEnabled = function (enable) {
119                    setTimeout(function () {
120                        if (enable)
121                            $element.multiselect('enable');
122                        else
123                            $element.multiselect('disable');
124                    });
125                };
126
127                if (allBindings.has('enable')) {
128                    var enable = allBindings.get('enable');
129                    if (ko.isObservable(enable)) {
130                        ko.computed({
131                            read: function () {
132                                setEnabled(enable());
133                            },
134                            disposeWhenNodeIsRemoved: element
135                        }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
136                    } else {
137                        setEnabled(enable);
138                    }
139                }
140
141                if (allBindings.has('disable')) {
142                    var disable = allBindings.get('disable');
143                    if (ko.isObservable(disable)) {
144                        ko.computed({
145                            read: function () {
146                                setEnabled(!disable());
147                            },
148                            disposeWhenNodeIsRemoved: element
149                        }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
150                    } else {
151                        setEnabled(!disable);
152                    }
153                }
154
155                ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
156                    $element.multiselect('destroy');
157                });
158            },
159
160            update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
161                var $element = $(element);
162                var config = ko.toJS(valueAccessor());
163
164                $element.multiselect('setOptions', config);
165                $element.multiselect('rebuild');
166            }
167        };
168    }
169
170    function forEach(array, callback) {
171        for (var index = 0; index < array.length; ++index) {
172            callback(array[index], index);
173        }
174    }
175
176    /**
177     * Constructor to create a new multiselect using the given select.
178     *
179     * @param {jQuery} select
180     * @param {Object} options
181     * @returns {Multiselect}
182     */
183    function Multiselect(select, options) {
184
185        this.$select = $(select);
186        
187        // Placeholder via data attributes
188        if (this.$select.attr("data-placeholder")) {
189            options.nonSelectedText = this.$select.data("placeholder");
190        }
191        
192        this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
193
194        // Initialization.
195        // We have to clone to create a new reference.
196        this.originalOptions = this.$select.clone()[0].options;
197        this.query = '';
198        this.searchTimeout = null;
199        this.lastToggledInput = null;
200
201        this.options.multiple = this.$select.attr('multiple') === "multiple";
202        this.options.onChange = $.proxy(this.options.onChange, this);
203        this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
204        this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
205        this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
206        this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);
207        this.options.onInitialized = $.proxy(this.options.onInitialized, this);
208        
209        // Build select all if enabled.
210        this.buildContainer();
211        this.buildButton();
212        this.buildDropdown();
213        this.buildSelectAll();
214        this.buildDropdownOptions();
215        this.buildFilter();
216
217        this.updateButtonText();
218        this.updateSelectAll(true);
219
220        if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
221            this.disable();
222        }
223        
224        this.$select.hide().after(this.$container);
225        this.options.onInitialized(this.$select, this.$container);
226    }
227
228    Multiselect.prototype = {
229
230        defaults: {
231            /**
232             * Default text function will either print 'None selected' in case no
233             * option is selected or a list of the selected options up to a length
234             * of 3 selected options.
235             * 
236             * @param {jQuery} options
237             * @param {jQuery} select
238             * @returns {String}
239             */
240            buttonText: function(options, select) {
241                if (this.disabledText.length > 0 
242                        && (this.disableIfEmpty || select.prop('disabled')) 
243                        && options.length == 0) {
244                    
245                    return this.disabledText;
246                }
247                else if (options.length === 0) {
248                    return this.nonSelectedText;
249                }
250                else if (this.allSelectedText 
251                        && options.length === $('option', $(select)).length 
252                        && $('option', $(select)).length !== 1 
253                        && this.multiple) {
254
255                    if (this.selectAllNumber) {
256                        return this.allSelectedText + ' (' + options.length + ')';
257                    }
258                    else {
259                        return this.allSelectedText;
260                    }
261                }
262                else if (options.length > this.numberDisplayed) {
263                    return options.length + ' ' + this.nSelectedText;
264                }
265                else {
266                    var selected = '';
267                    var delimiter = this.delimiterText;
268                    
269                    options.each(function() {
270                        var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
271                        selected += label + delimiter;
272                    });
273                    
274                    return selected.substr(0, selected.length - 2);
275                }
276            },
277            /**
278             * Updates the title of the button similar to the buttonText function.
279             * 
280             * @param {jQuery} options
281             * @param {jQuery} select
282             * @returns {@exp;selected@call;substr}
283             */
284            buttonTitle: function(options, select) {
285                if (options.length === 0) {
286                    return this.nonSelectedText;
287                }
288                else {
289                    var selected = '';
290                    var delimiter = this.delimiterText;
291                    
292                    options.each(function () {
293                        var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
294                        selected += label + delimiter;
295                    });
296                    return selected.substr(0, selected.length - 2);
297                }
298            },
299            /**
300             * Create a label.
301             *
302             * @param {jQuery} element
303             * @returns {String}
304             */
305            optionLabel: function(element){
306                return $(element).attr('label') || $(element).text();
307            },
308            /**
309             * Create a class.
310             *
311             * @param {jQuery} element
312             * @returns {String}
313             */
314            optionClass: function(element) {
315                return $(element).attr('class') || '';
316            },
317            /**
318             * Triggered on change of the multiselect.
319             * 
320             * Not triggered when selecting/deselecting options manually.
321             * 
322             * @param {jQuery} option
323             * @param {Boolean} checked
324             */
325            onChange : function(option, checked) {
326
327            },
328            /**
329             * Triggered when the dropdown is shown.
330             *
331             * @param {jQuery} event
332             */
333            onDropdownShow: function(event) {
334
335            },
336            /**
337             * Triggered when the dropdown is hidden.
338             *
339             * @param {jQuery} event
340             */
341            onDropdownHide: function(event) {
342
343            },
344            /**
345             * Triggered after the dropdown is shown.
346             * 
347             * @param {jQuery} event
348             */
349            onDropdownShown: function(event) {
350                
351            },
352            /**
353             * Triggered after the dropdown is hidden.
354             * 
355             * @param {jQuery} event
356             */
357            onDropdownHidden: function(event) {
358                
359            },
360            /**
361             * Triggered on select all.
362             */
363            onSelectAll: function(checked) {
364                
365            },
366            /**
367             * Triggered after initializing.
368             *
369             * @param {jQuery} $select
370             * @param {jQuery} $container
371             */
372            onInitialized: function($select, $container) {
373
374            },
375            enableHTML: false,
376            buttonClass: 'btn btn-default',
377            inheritClass: false,
378            buttonWidth: 'auto',
379            buttonContainer: '<div class="btn-group" />',
380            dropRight: false,
381            dropUp: false,
382            selectedClass: 'active',
383            // Maximum height of the dropdown menu.
384            // If maximum height is exceeded a scrollbar will be displayed.
385            maxHeight: false,
386            checkboxName: false,
387            includeSelectAllOption: false,
388            includeSelectAllIfMoreThan: 0,
389            selectAllText: ' Select all',
390            selectAllValue: 'multiselect-all',
391            selectAllName: false,
392            selectAllNumber: true,
393            selectAllJustVisible: true,
394            enableFiltering: false,
395            enableCaseInsensitiveFiltering: false,
396            enableFullValueFiltering: false,
397            enableClickableOptGroups: false,
398            enableCollapsibelOptGroups: false,
399            filterPlaceholder: 'Search',
400            // possible options: 'text', 'value', 'both'
401            filterBehavior: 'text',
402            includeFilterClearBtn: true,
403            preventInputChangeEvent: false,
404            nonSelectedText: 'None selected',
405            nSelectedText: 'selected',
406            allSelectedText: 'All selected',
407            numberDisplayed: 3,
408            disableIfEmpty: false,
409            disabledText: '',
410            delimiterText: ', ',
411            templates: {
412                button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
413                ul: '<ul class="multiselect-container dropdown-menu"></ul>',
414                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>',
415                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>',
416                li: '<li><a tabindex="0"><label></label></a></li>',
417                divider: '<li class="multiselect-item divider"></li>',
418                liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>'
419            }
420        },
421
422        constructor: Multiselect,
423
424        /**
425         * Builds the container of the multiselect.
426         */
427        buildContainer: function() {
428            this.$container = $(this.options.buttonContainer);
429            this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
430            this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
431            this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
432            this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
433        },
434
435        /**
436         * Builds the button of the multiselect.
437         */
438        buildButton: function() {
439            this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
440            if (this.$select.attr('class') && this.options.inheritClass) {
441                this.$button.addClass(this.$select.attr('class'));
442            }
443            // Adopt active state.
444            if (this.$select.prop('disabled')) {
445                this.disable();
446            }
447            else {
448                this.enable();
449            }
450
451            // Manually add button width if set.
452            if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
453                this.$button.css({
454                    'width' : this.options.buttonWidth,
455                    'overflow' : 'hidden',
456                    'text-overflow' : 'ellipsis'
457                });
458                this.$container.css({
459                    'width': this.options.buttonWidth
460                });
461            }
462
463            // Keep the tab index from the select.
464            var tabindex = this.$select.attr('tabindex');
465            if (tabindex) {
466                this.$button.attr('tabindex', tabindex);
467            }
468
469            this.$container.prepend(this.$button);
470        },
471
472        /**
473         * Builds the ul representing the dropdown menu.
474         */
475        buildDropdown: function() {
476
477            // Build ul.
478            this.$ul = $(this.options.templates.ul);
479
480            if (this.options.dropRight) {
481                this.$ul.addClass('pull-right');
482            }
483
484            // Set max height of dropdown menu to activate auto scrollbar.
485            if (this.options.maxHeight) {
486                // TODO: Add a class for this option to move the css declarations.
487                this.$ul.css({
488                    'max-height': this.options.maxHeight + 'px',
489                    'overflow-y': 'auto',
490                    'overflow-x': 'hidden'
491                });
492            }
493            
494            if (this.options.dropUp) {
495                
496                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));
497                var moveCalc = height + 34;
498                
499                this.$ul.css({
500                    'max-height': height + 'px',
501                    'overflow-y': 'auto',
502                    'overflow-x': 'hidden',
503                    'margin-top': "-" + moveCalc + 'px'
504                });
505            }
506            
507            this.$container.append(this.$ul);
508        },
509
510        /**
511         * Build the dropdown options and binds all nessecary events.
512         * 
513         * Uses createDivider and createOptionValue to create the necessary options.
514         */
515        buildDropdownOptions: function() {
516
517            this.$select.children().each($.proxy(function(index, element) {
518
519                var $element = $(element);
vendor: 4,132 bytes, lines 520-626
520                // Support optgroups and options without a group simultaneously.
521                var tag = $element.prop('tagName')
522                    .toLowerCase();
523            
524                if ($element.prop('value') === this.options.selectAllValue) {
525                    return;
526                }
527
528                if (tag === 'optgroup') {
529                    this.createOptgroup(element);
530                }
531                else if (tag === 'option') {
532
533                    if ($element.data('role') === 'divider') {
534                        this.createDivider();
535                    }
536                    else {
537                        this.createOptionValue(element);
538                    }
539
540                }
541
542                // Other illegal tags will be ignored.
543            }, this));
544
545            // Bind the change event on the dropdown elements.
546            $('li input', this.$ul).on('change', $.proxy(function(event) {
547                var $target = $(event.target);
548
549                var checked = $target.prop('checked') || false;
550                var isSelectAllOption = $target.val() === this.options.selectAllValue;
551
552                // Apply or unapply the configured selected class.
553                if (this.options.selectedClass) {
554                    if (checked) {
555                        $target.closest('li')
556                            .addClass(this.options.selectedClass);
557                    }
558                    else {
559                        $target.closest('li')
560                            .removeClass(this.options.selectedClass);
561                    }
562                }
563
564                // Get the corresponding option.
565                var value = $target.val();
566                var $option = this.getOptionByValue(value);
567
568                var $optionsNotThis = $('option', this.$select).not($option);
569                var $checkboxesNotThis = $('input', this.$container).not($target);
570
571                if (isSelectAllOption) {
572                    if (checked) {
573                        this.selectAll(this.options.selectAllJustVisible);
574                    }
575                    else {
576                        this.deselectAll(this.options.selectAllJustVisible);
577                    }
578                }
579                else {
580                    if (checked) {
581                        $option.prop('selected', true);
582
583                        if (this.options.multiple) {
584                            // Simply select additional option.
585                            $option.prop('selected', true);
586                        }
587                        else {
588                            // Unselect all other options and corresponding checkboxes.
589                            if (this.options.selectedClass) {
590                                $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
591                            }
592
593                            $($checkboxesNotThis).prop('checked', false);
594                            $optionsNotThis.prop('selected', false);
595
596                            // It's a single selection, so close.
597                            this.$button.click();
598                        }
599
600                        if (this.options.selectedClass === "active") {
601                            $optionsNotThis.closest("a").css("outline", "");
602                        }
603                    }
604                    else {
605                        // Unselect option.
606                        $option.prop('selected', false);
607                    }
608                    
609                    // To prevent select all from firing onChange: #575
610                    this.options.onChange($option, checked);
611                }
612
613                this.$select.change();
614
615                this.updateButtonText();
616                this.updateSelectAll();
617
618                if(this.options.preventInputChangeEvent) {
619                    return false;
620                }
621            }, this));
622
623            $('li a', this.$ul).on('mousedown', function(e) {
624                if (e.shiftKey) {
625                    // Prevent selecting text by Shift+click
626                    return false;
627                }
628            });
629        
630            $('li a', this.$ul).on('touchstart click', $.proxy(function(event) {
631                event.stopPropagation();
632
633                var $target = $(event.target);
634                
635                if (event.shiftKey && this.options.multiple) {
636                    if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
637                        event.preventDefault();
638                        $target = $target.find("input");
639                        $target.prop("checked", !$target.prop("checked"));
640                    }
641                    var checked = $target.prop('checked') || false;
642
643                    if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
644                        var from = $target.closest("li").index();
645                        var to = this.lastToggledInput.closest("li").index();
646                        
647                        if (from > to) { // Swap the indices
648                            var tmp = to;
649                            to = from;
650                            from = tmp;
651                        }
652                        
653                        // Make sure we grab all elements since slice excludes the last index
654                        ++to;
655                        
656                        // Change the checkboxes and underlying options
657                        var range = this.$ul.find("li").slice(from, to).find("input");
658                        
659                        range.prop('checked', checked);
660                        
661                        if (this.options.selectedClass) {
662                            range.closest('li')
663                                .toggleClass(this.options.selectedClass, checked);
664                        }
665                        
666                        for (var i = 0, j = range.length; i < j; i++) {
667                            var $checkbox = $(range[i]);
668
669                            var $option = this.getOptionByValue($checkbox.val());
670
671                            $option.prop('selected', checked);
672                        }                   
673                    }
674                    
675                    // Trigger the select "change" event
676                    $target.trigger("change");
677                }
678                
679                // Remembers last clicked option
680                if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
681                    this.lastToggledInput = $target;
682                }
683
684                $target.blur();
685            }, this));
686
687            // Keyboard support.
688            this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
689                if ($('input[type="text"]', this.$container).is(':focus')) {
690                    return;
691                }
692
693                if (event.keyCode === 9 && this.$container.hasClass('open')) {
694                    this.$button.click();
695                }
696                else {
697                    var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
698
699                    if (!$items.length) {
700                        return;
701                    }
702
703                    var index = $items.index($items.filter(':focus'));
704
705                    // Navigation up.
706                    if (event.keyCode === 38 && index > 0) {
707                        index--;
708                    }
709                    // Navigate down.
710                    else if (event.keyCode === 40 && index < $items.length - 1) {
711                        index++;
712                    }
713                    else if (!~index) {
714                        index = 0;
715                    }
716
717                    var $current = $items.eq(index);
718                    $current.focus();
719
720                    if (event.keyCode === 32 || event.keyCode === 13) {
721                        var $checkbox = $current.find('input');
722
723                        $checkbox.prop("checked", !$checkbox.prop("checked"));
724                        $checkbox.change();
725                    }
726
727                    event.stopPropagation();
728                    event.preventDefault();
729                }
730            }, this));
731
732            if(this.options.enableClickableOptGroups && this.options.multiple) {
733                $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) {
734                    event.stopPropagation();
735                    console.log('test');
736                    var group = $(event.target).parent();
737
738                    // Search all option in optgroup
739                    var $options = group.nextUntil('li.multiselect-group');
740                    var $visibleOptions = $options.filter(":visible:not(.disabled)");
741
742                    // check or uncheck items
743                    var allChecked = true;
744                    var optionInputs = $visibleOptions.find('input');
745                    var values = [];
746                    
747                    optionInputs.each(function() {
748                        allChecked = allChecked && $(this).prop('checked');
749                        values.push($(this).val());
750                    });
751
752                    if (!allChecked) {
753                        this.select(values, false);
754                    }
755                    else {
756                        this.deselect(values, false);
757                    }
758                    
759                    this.options.onChange(optionInputs, !allChecked);
760               }, this));
761            }
762
763            if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
764                $("li.multiselect-group input", this.$ul).off();
765                $("li.multiselect-group", this.$ul).siblings().not("li.multiselect-group, li.multiselect-all", this.$ul).each( function () {
766                    $(this).toggleClass('hidden', true);
767                });
768                
769                $("li.multiselect-group", this.$ul).on("click", $.proxy(function(group) {
770                    group.stopPropagation();
771                }, this));
772                
773                $("li.multiselect-group > a > b", this.$ul).on("click", $.proxy(function(t) {
774                    t.stopPropagation();
775                    var n = $(t.target).closest('li');
776                    var r = n.nextUntil("li.multiselect-group");
777                    var i = true;
778                    
779                    r.each(function() {
780                        i = i && $(this).hasClass('hidden');
781                    });
782                    
783                    r.toggleClass('hidden', !i);
784                }, this));
785                
786                $("li.multiselect-group > a > input", this.$ul).on("change", $.proxy(function(t) {
787                    t.stopPropagation();
788                    var n = $(t.target).closest('li');
789                    var r = n.nextUntil("li.multiselect-group", ':not(.disabled)');
790                    var s = r.find("input");
791                    
792                    var i = true;
793                    s.each(function() {
794                        i = i && $(this).prop("checked");
795                    });
796                    
797                    s.prop("checked", !i).trigger("change");
798                }, this));
799                
800                // Set the initial selection state of the groups.
801                $('li.multiselect-group', this.$ul).each(function() {
802                    var r = $(this).nextUntil("li.multiselect-group", ':not(.disabled)');
803                    var s = r.find("input");
804                    
805                    var i = true;
806                    s.each(function() {
807                        i = i && $(this).prop("checked");
808                    });
809                    
810                    $(this).find('input').prop("checked", i);
811                });
812                
813                // Update the group checkbox based on new selections among the
814                // corresponding children.
815                $("li input", this.$ul).on("change", $.proxy(function(t) {
816                    t.stopPropagation();
817                    var n = $(t.target).closest('li');
818                    var r1 = n.prevUntil("li.multiselect-group", ':not(.disabled)');
819                    var r2 = n.nextUntil("li.multiselect-group", ':not(.disabled)');
820                    var s1 = r1.find("input");
821                    var s2 = r2.find("input");
822                    
823                    var i = $(t.target).prop('checked');
824                    s1.each(function() {
825                        i = i && $(this).prop("checked");
826                    });
827                    
828                    s2.each(function() {
829                        i = i && $(this).prop("checked");
830                    });
831                    
832                    n.prevAll('.multiselect-group').find('input').prop('checked', i);
833                }, this));
834                
835                $("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
836                $("li.multiselect-group > a, li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
837                $("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
838            }
839        },
840
841        /**
842         * Create an option using the given select option.
843         *
844         * @param {jQuery} element
845         */
846        createOptionValue: function(element) {
847            var $element = $(element);
848            if ($element.is(':selected')) {
849                $element.prop('selected', true);
850            }
851
852            // Support the label attribute on options.
853            var label = this.options.optionLabel(element);
854            var classes = this.options.optionClass(element);
855            var value = $element.val();
856            var inputType = this.options.multiple ? "checkbox" : "radio";
857
858            var $li = $(this.options.templates.li);
859            var $label = $('label', $li);
860            $label.addClass(inputType);
861            $li.addClass(classes);
862
863            if (this.options.enableHTML) {
864                $label.html(" " + label);
865            }
866            else {
867                $label.text(" " + label);
868            }
869        
870            var $checkbox = $('<input/>').attr('type', inputType);
871
872            if (this.options.checkboxName) {
873                $checkbox.attr('name', this.options.checkboxName);
874            }
875            $label.prepend($checkbox);
876
877            var selected = $element.prop('selected') || false;
878            $checkbox.val(value);
879
880            if (value === this.options.selectAllValue) {
881                $li.addClass("multiselect-item multiselect-all");
882                $checkbox.parent().parent()
883                    .addClass('multiselect-all');
884            }
885
886            $label.attr('title', $element.attr('title'));
887
888            this.$ul.append($li);
889
890            if ($element.is(':disabled')) {
891                $checkbox.attr('disabled', 'disabled')
892                    .prop('disabled', true)
893                    .closest('a')
894                    .attr("tabindex", "-1")
895                    .closest('li')
896                    .addClass('disabled');
897            }
898
899            $checkbox.prop('checked', selected);
900
901            if (selected && this.options.selectedClass) {
902                $checkbox.closest('li')
903                    .addClass(this.options.selectedClass);
904            }
905        },
906
907        /**
908         * Creates a divider using the given select option.
909         *
910         * @param {jQuery} element
911         */
912        createDivider: function(element) {
913            var $divider = $(this.options.templates.divider);
914            this.$ul.append($divider);
915        },
916
917        /**
918         * Creates an optgroup.
919         *
920         * @param {jQuery} group
921         */
922        createOptgroup: function(group) {            
923            if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
924                var label = $(group).attr("label");
925                var value = $(group).attr("value");
926                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>');
927
928                if (this.options.enableClickableOptGroups) {
929                    r.addClass("multiselect-group-clickable")
930                }
931                this.$ul.append(r);
932                if ($(group).is(":disabled")) {
933                    r.addClass("disabled")
934                }
935                $("option", group).each($.proxy(function($, group) {
936                    this.createOptionValue(group)
937                }, this))
938            }
939            else {
940                var groupName = $(group).prop('label');
941
942                // Add a header for the group.
943                var $li = $(this.options.templates.liGroup);
944
945                if (this.options.enableHTML) {
946                    $('label', $li).html(groupName);
947                }
948                else {
949                    $('label', $li).text(groupName);
950                }
951
952                if (this.options.enableClickableOptGroups) {
953                    $li.addClass('multiselect-group-clickable');
954                }
955
956                this.$ul.append($li);
957
958                if ($(group).is(':disabled')) {
959                    $li.addClass('disabled');
960                }
961
962                // Add the options of the group.
963                $('option', group).each($.proxy(function(index, element) {
964                    this.createOptionValue(element);
965                }, this));
966            }
967        },
968
969        /**
970         * Build the select all.
971         * 
972         * Checks if a select all has already been created.
973         */
974        buildSelectAll: function() {
975            if (typeof this.options.selectAllValue === 'number') {
976                this.options.selectAllValue = this.options.selectAllValue.toString();
977            }
978            
979            var alreadyHasSelectAll = this.hasSelectAll();
980
981            if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
982                    && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
983
984                // Check whether to add a divider after the select all.
985                if (this.options.includeSelectAllDivider) {
986                    this.$ul.prepend($(this.options.templates.divider));
987                }
988
989                var $li = $(this.options.templates.li);
990                $('label', $li).addClass("checkbox");
991                
992                if (this.options.enableHTML) {
993                    $('label', $li).html(" " + this.options.selectAllText);
994                }
995                else {
996                    $('label', $li).text(" " + this.options.selectAllText);
997                }
998                
999                if (this.options.selectAllName) {
1000                    $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
1001                }
1002                else {
1003                    $('label', $li).prepend('<input type="checkbox" />');
1004                }
1005                
1006                var $checkbox = $('input', $li);
1007                $checkbox.val(this.options.selectAllValue);
1008
1009                $li.addClass("multiselect-item multiselect-all");
1010                $checkbox.parent().parent()
1011                    .addClass('multiselect-all');
1012
1013                this.$ul.prepend($li);
1014
1015                $checkbox.prop('checked', false);
1016            }
1017        },
1018
1019        /**
1020         * Builds the filter.
1021         */
1022        buildFilter: function() {
1023
1024            // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1025            if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
1026                var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
1027
1028                if (this.$select.find('option').length >= enableFilterLength) {
1029
1030                    this.$filter = $(this.options.templates.filter);
1031                    $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1032                    
1033                    // Adds optional filter clear button
1034                    if(this.options.includeFilterClearBtn){
1035                        var clearBtn = $(this.options.templates.filterClearBtn);
1036                        clearBtn.on('click', $.proxy(function(event){
1037                            clearTimeout(this.searchTimeout);
1038                            this.$filter.find('.multiselect-search').val('');
1039                            $('li', this.$ul).show().removeClass("filter-hidden");
1040                            this.updateSelectAll();
1041                        }, this));
1042                        this.$filter.find('.input-group').append(clearBtn);
1043                    }
1044                    
1045                    this.$ul.prepend(this.$filter);
1046
1047                    this.$filter.val(this.query).on('click', function(event) {
1048                        event.stopPropagation();
1049                    }).on('input keydown', $.proxy(function(event) {
1050                        // Cancel enter key default behaviour
1051                        if (event.which === 13) {
1052                          event.preventDefault();
1053                        }
1054                        
1055                        // This is useful to catch "keydown" events after the browser has updated the control.
1056                        clearTimeout(this.searchTimeout);
1057
1058                        this.searchTimeout = this.asyncFunction($.proxy(function() {
1059
1060                            if (this.query !== event.target.value) {
1061                                this.query = event.target.value;
1062
1063                                var currentGroup, currentGroupVisible;
1064                                $.each($('li', this.$ul), $.proxy(function(index, element) {
1065                                    var value = $('input', element).length > 0 ? $('input', element).val() : "";
1066                                    var text = $('label', element).text();
1067
1068                                    var filterCandidate = '';
1069                                    if ((this.options.filterBehavior === 'text')) {
1070                                        filterCandidate = text;
1071                                    }
1072                                    else if ((this.options.filterBehavior === 'value')) {
1073                                        filterCandidate = value;
1074                                    }
1075                                    else if (this.options.filterBehavior === 'both') {
1076                                        filterCandidate = text + '\n' + value;
1077                                    }
1078
1079                                    if (value !== this.options.selectAllValue && text) {
1080
1081                                        // By default lets assume that element is not
1082                                        // interesting for this search.
1083                                        var showElement = false;
1084
1085                                        if (this.options.enableCaseInsensitiveFiltering) {
1086                                            filterCandidate = filterCandidate.toLowerCase();
1087                                            this.query = this.query.toLowerCase();
1088                                        }
1089
1090                                        if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') {
1091                                            var valueToMatch = filterCandidate.trim().substring(0, this.query.length);
1092                                            if (this.query.indexOf(valueToMatch) > -1) {
1093                                                showElement = true;
1094                                            }
1095                                        }
1096                                        else if (filterCandidate.indexOf(this.query) > -1) {
1097                                            showElement = true;
1098                                        }
1099
1100                                        // Toggle current element (group or group item) according to showElement boolean.
1101                                        $(element).toggle(showElement).toggleClass('filter-hidden', !showElement);
1102                                        
1103                                        // Differentiate groups and group items.
1104                                        if ($(element).hasClass('multiselect-group')) {
1105                                            // Remember group status.
1106                                            currentGroup = element;
1107                                            currentGroupVisible = showElement;
1108                                        }
1109                                        else {
1110                                            // Show group name when at least one of its items is visible.
1111                                            if (showElement) {
1112                                                $(currentGroup).show().removeClass('filter-hidden');
1113                                            }
1114                                            
1115                                            // Show all group items when group name satisfies filter.
1116                                            if (!showElement && currentGroupVisible) {
1117                                                $(element).show().removeClass('filter-hidden');
1118                                            }
1119                                        }
1120                                    }
1121                                }, this));
1122                            }
1123
1124                            this.updateSelectAll();
1125                        }, this), 300, this);
1126                    }, this));
1127                }
1128            }
1129        },
1130
1131        /**
1132         * Unbinds the whole plugin.
1133         */
1134        destroy: function() {
1135            this.$container.remove();
1136            this.$select.show();
1137            this.$select.data('multiselect', null);
1138        },
1139
1140        /**
1141         * Refreshs the multiselect based on the selected options of the select.
1142         */
1143        refresh: function () {
1144            var inputs = $.map($('li input', this.$ul), $);
1145            
1146            $('option', this.$select).each($.proxy(function (index, element) {
1147                var $elem = $(element);
1148                var value = $elem.val();
1149                var $input;
1150                for (var i = inputs.length; 0 < i--; /**/) {
1151                    if (value !== ($input = inputs[i]).val())
1152                        continue; // wrong li
1153
1154                    if ($elem.is(':selected')) {
1155                        $input.prop('checked', true);
1156
1157                        if (this.options.selectedClass) {
1158                            $input.closest('li')
1159                                .addClass(this.options.selectedClass);
1160                        }
1161                    }
1162                    else {
1163                        $input.prop('checked', false);
1164
1165                        if (this.options.selectedClass) {
1166                            $input.closest('li')
1167                                .removeClass(this.options.selectedClass);
1168                        }
1169                    }
1170
1171                    if ($elem.is(":disabled")) {
1172                        $input.attr('disabled', 'disabled')
1173                            .prop('disabled', true)
1174                            .closest('li')
1175                            .addClass('disabled');
1176                    }
1177                    else {
1178                        $input.prop('disabled', false)
1179                            .closest('li')
1180                            .removeClass('disabled');
1181                    }
1182                    break; // assumes unique values
1183                }
1184            }, this));
1185
1186            this.updateButtonText();
1187            this.updateSelectAll();
1188        },
1189
1190        /**
1191         * Select all options of the given values.
1192         * 
1193         * If triggerOnChange is set to true, the on change event is triggered if
1194         * and only if one value is passed.
1195         * 
1196         * @param {Array} selectValues
1197         * @param {Boolean} triggerOnChange
1198         */
1199        select: function(selectValues, triggerOnChange) {
1200            if(!$.isArray(selectValues)) {
1201                selectValues = [selectValues];
1202            }
1203
1204            for (var i = 0; i < selectValues.length; i++) {
1205                var value = selectValues[i];
1206
1207                if (value === null || value === undefined) {
1208                    continue;
1209                }
1210
1211                var $option = this.getOptionByValue(value);
1212                var $checkbox = this.getInputByValue(value);
1213
1214                if($option === undefined || $checkbox === undefined) {
1215                    continue;
1216                }
1217                
1218                if (!this.options.multiple) {
1219                    this.deselectAll(false);
1220                }
1221                
1222                if (this.options.selectedClass) {
1223                    $checkbox.closest('li')
1224                        .addClass(this.options.selectedClass);
1225                }
1226
1227                $checkbox.prop('checked', true);
1228                $option.prop('selected', true);
1229                
1230                if (triggerOnChange) {
1231                    this.options.onChange($option, true);
1232                }
1233            }
1234
1235            this.updateButtonText();
1236            this.updateSelectAll();
1237        },
1238
1239        /**
1240         * Clears all selected items.
1241         */
1242        clearSelection: function () {
1243            this.deselectAll(false);
1244            this.updateButtonText();
1245            this.updateSelectAll();
1246        },
1247
1248        /**
1249         * Deselects all options of the given values.
1250         * 
1251         * If triggerOnChange is set to true, the on change event is triggered, if
1252         * and only if one value is passed.
1253         * 
1254         * @param {Array} deselectValues
1255         * @param {Boolean} triggerOnChange
1256         */
1257        deselect: function(deselectValues, triggerOnChange) {
1258            if(!$.isArray(deselectValues)) {
1259                deselectValues = [deselectValues];
1260            }
1261
1262            for (var i = 0; i < deselectValues.length; i++) {
1263                var value = deselectValues[i];
1264
1265                if (value === null || value === undefined) {
1266                    continue;
1267                }
1268
1269                var $option = this.getOptionByValue(value);
1270                var $checkbox = this.getInputByValue(value);
1271
1272                if($option === undefined || $checkbox === undefined) {
1273                    continue;
1274                }
1275
1276                if (this.options.selectedClass) {
1277                    $checkbox.closest('li')
1278                        .removeClass(this.options.selectedClass);
1279                }
1280
1281                $checkbox.prop('checked', false);
1282                $option.prop('selected', false);
1283                
1284                if (triggerOnChange) {
1285                    this.options.onChange($option, false);
1286                }
1287            }
1288
1289            this.updateButtonText();
1290            this.updateSelectAll();
1291        },
1292        
1293        /**
1294         * Selects all enabled & visible options.
1295         *
1296         * If justVisible is true or not specified, only visible options are selected.
1297         *
1298         * @param {Boolean} justVisible
1299         * @param {Boolean} triggerOnSelectAll
1300         */
1301        selectAll: function (justVisible, triggerOnSelectAll) {
1302            justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible;
1303            
1304            var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1305            var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul);
1306            var visibleCheckboxes = allCheckboxes.filter(":visible");
1307            var allCheckboxesCount = allCheckboxes.length;
1308            var visibleCheckboxesCount = visibleCheckboxes.length;
1309            
1310            if(justVisible) {
1311                visibleCheckboxes.prop('checked', true);
1312                $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass);
1313            }
1314            else {
1315                allCheckboxes.prop('checked', true);
1316                $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass);
1317            }
1318                
1319            if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) {
1320                $("option:not([data-role='divider']):enabled", this.$select).prop('selected', true);
1321            }
1322            else {
1323                var values = visibleCheckboxes.map(function() {
1324                    return $(this).val();
1325                }).get();
1326                
1327                $("option:enabled", this.$select).filter(function(index) {
1328                    return $.inArray($(this).val(), values) !== -1;
1329                }).prop('selected', true);
1330            }
1331            
1332            if (triggerOnSelectAll) {
1333                this.options.onSelectAll();
1334            }
1335        },
1336
1337        /**
1338         * Deselects all options.
1339         * 
1340         * If justVisible is true or not specified, only visible options are deselected.
1341         * 
1342         * @param {Boolean} justVisible
1343         */
1344        deselectAll: function (justVisible) {
1345            justVisible = (this.options.enableCollapsibleOptGroups && this.options.multiple) ? false : justVisible;
1346            justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1347            
1348            if(justVisible) {              
1349                var visibleCheckboxes = $("li input[type='checkbox']:not(:disabled)", this.$ul).filter(":visible");
1350                visibleCheckboxes.prop('checked', false);
1351                
1352                var values = visibleCheckboxes.map(function() {
1353                    return $(this).val();
1354                }).get();
1355                
1356                $("option:enabled", this.$select).filter(function(index) {
1357                    return $.inArray($(this).val(), values) !== -1;
1358                }).prop('selected', false);
1359                
1360                if (this.options.selectedClass) {
1361                    $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass);
1362                }
1363            }
1364            else {
1365                $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false);
1366                $("option:enabled", this.$select).prop('selected', false);
1367                
1368                if (this.options.selectedClass) {
1369                    $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass);
1370                }
1371            }
1372        },
1373
1374        /**
1375         * Rebuild the plugin.
1376         * 
1377         * Rebuilds the dropdown, the filter and the select all option.
1378         */
1379        rebuild: function() {
1380            this.$ul.html('');
1381
1382            // Important to distinguish between radios and checkboxes.
1383            this.options.multiple = this.$select.attr('multiple') === "multiple";
1384
1385            this.buildSelectAll();
1386            this.buildDropdownOptions();
1387            this.buildFilter();
1388
1389            this.updateButtonText();
1390            this.updateSelectAll(true);
1391            
1392            if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1393                this.disable();
1394            }
1395            else {
1396                this.enable();
1397            }
1398            
1399            if (this.options.dropRight) {
1400                this.$ul.addClass('pull-right');
1401            }
1402        },
1403
1404        /**
1405         * The provided data will be used to build the dropdown.
1406         */
1407        dataprovider: function(dataprovider) {
1408            
1409            var groupCounter = 0;
1410            var $select = this.$select.empty();
1411            
1412            $.each(dataprovider, function (index, option) {
1413                var $tag;
1414                
1415                if ($.isArray(option.children)) { // create optiongroup tag
1416                    groupCounter++;
1417                    
1418                    $tag = $('<optgroup/>').attr({
1419                        label: option.label || 'Group ' + groupCounter,
1420                        disabled: !!option.disabled
1421                    });
1422                    
1423                    forEach(option.children, function(subOption) { // add children option tags
1424                        $tag.append($('<option/>').attr({
1425                            value: subOption.value,
1426                            label: subOption.label || subOption.value,
1427                            title: subOption.title,
1428                            selected: !!subOption.selected,
1429                            disabled: !!subOption.disabled
1430                        }));
1431                    });
1432                }
1433                else {
1434                    $tag = $('<option/>').attr({
1435                        value: option.value,
1436                        label: option.label || option.value,
1437                        title: option.title,
1438                        class: option.class,
1439                        selected: !!option.selected,
1440                        disabled: !!option.disabled
1441                    });
1442                    $tag.text(option.label || option.value);
1443                }
1444                
1445                $select.append($tag);
1446            });
1447            
1448            this.rebuild();
1449        },
1450
1451        /**
1452         * Enable the multiselect.
1453         */
1454        enable: function() {
1455            this.$select.prop('disabled', false);
1456            this.$button.prop('disabled', false)
1457                .removeClass('disabled');
1458        },
1459
1460        /**
1461         * Disable the multiselect.
1462         */
1463        disable: function() {
1464            this.$select.prop('disabled', true);
1465            this.$button.prop('disabled', true)
1466                .addClass('disabled');
1467        },
1468
1469        /**
1470         * Set the options.
1471         *
1472         * @param {Array} options
1473         */
1474        setOptions: function(options) {
1475            this.options = this.mergeOptions(options);
1476        },
1477
1478        /**
1479         * Merges the given options with the default options.
1480         *
1481         * @param {Array} options
1482         * @returns {Array}
1483         */
1484        mergeOptions: function(options) {
1485            return $.extend(true, {}, this.defaults, this.options, options);
1486        },
1487
1488        /**
1489         * Checks whether a select all checkbox is present.
1490         *
1491         * @returns {Boolean}
1492         */
1493        hasSelectAll: function() {
1494            return $('li.multiselect-all', this.$ul).length > 0;
1495        },
1496
1497        /**
1498         * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1499         */
1500        updateSelectAll: function(notTriggerOnSelectAll) {
1501            if (this.hasSelectAll()) {
1502                var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul);
1503                var allBoxesLength = allBoxes.length;
1504                var checkedBoxesLength = allBoxes.filter(":checked").length;
1505                var selectAllLi  = $("li.multiselect-all", this.$ul);
1506                var selectAllInput = selectAllLi.find("input");
1507                
1508                if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1509                    selectAllInput.prop("checked", true);
1510                    selectAllLi.addClass(this.options.selectedClass);
1511                    this.options.onSelectAll(true);
1512                }
1513                else {
1514                    selectAllInput.prop("checked", false);
1515                    selectAllLi.removeClass(this.options.selectedClass);
1516                    if (checkedBoxesLength === 0) {
1517                        if (!notTriggerOnSelectAll) {
1518                            this.options.onSelectAll(false);
1519                        }
1520                    }
1521                }
1522            }
1523        },
1524
1525        /**
1526         * Update the button text and its title based on the currently selected options.
1527         */
1528        updateButtonText: function() {
1529            var options = this.getSelected();
1530            
1531            // First update the displayed button text.
1532            if (this.options.enableHTML) {
1533                $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select));
1534            }
1535            else {
1536                $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select));
1537            }
1538            
1539            // Now update the title attribute of the button.
1540            $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
1541        },
1542
1543        /**
1544         * Get all selected options.
1545         *
1546         * @returns {jQUery}
1547         */
1548        getSelected: function() {
1549            return $('option', this.$select).filter(":selected");
1550        },
1551
1552        /**
1553         * Gets a select option by its value.
1554         *
1555         * @param {String} value
1556         * @returns {jQuery}
1557         */
1558        getOptionByValue: function (value) {
1559
1560            var options = $('option', this.$select);
1561            var valueToCompare = value.toString();
1562
1563            for (var i = 0; i < options.length; i = i + 1) {
1564                var option = options[i];
1565                if (option.value === valueToCompare) {
1566                    return $(option);
1567                }
1568            }
1569        },
1570
1571        /**
1572         * Get the input (radio/checkbox) by its value.
1573         *
1574         * @param {String} value
1575         * @returns {jQuery}
1576         */
1577        getInputByValue: function (value) {
1578
1579            var checkboxes = $('li input', this.$ul);
1580            var valueToCompare = value.toString();
1581
1582            for (var i = 0; i < checkboxes.length; i = i + 1) {
1583                var checkbox = checkboxes[i];
1584                if (checkbox.value === valueToCompare) {
1585                    return $(checkbox);
1586                }
1587            }
1588        },
1589
1590        /**
1591         * Used for knockout integration.
1592         */
1593        updateOriginalOptions: function() {
1594            this.originalOptions = this.$select.clone()[0].options;
1595        },
1596
1597        asyncFunction: function(callback, timeout, self) {
1598            var args = Array.prototype.slice.call(arguments, 3);
1599            return setTimeout(function() {
1600                callback.apply(self || window, args);
1601            }, timeout);
1602        },
1603
1604        setAllSelectedText: function(allSelectedText) {
1605            this.options.allSelectedText = allSelectedText;
1606            this.updateButtonText();
1607        }
1608    };
1609
1610    $.fn.multiselect = function(option, parameter, extraOptions) {
1611        return this.each(function() {
1612            var data = $(this).data('multiselect');
1613            var options = typeof option === 'object' && option;
1614
1615            // Initialize the multiselect.
1616            if (!data) {
1617                data = new Multiselect(this, options);
1618                $(this).data('multiselect', data);
1619            }
1620
1621            // Call multiselect method.
1622            if (typeof option === 'string') {
1623                data[option](parameter, extraOptions);
1624                
1625                if (option === 'destroy') {
1626                    $(this).data('multiselect', false);
1627                }
1628            }
1629        });
1630    };
1631
1632    $.fn.multiselect.Constructor = Multiselect;
1633
1634    $(function() {
1635        $("select[data-role=multiselect]").multiselect();
1636    });
1637
1638}(window.jQuery);
1639
1640;/*
1641  Bootstrap - File Input
1642  ======================
1643
1644  This is meant to convert all file input tags into a set of elements that displays consistently in all browsers.
1645
1646  Converts all
1647  <input type="file">
1648  into Bootstrap buttons
1649  <a class="btn">Browse</a>
1650
1651*/
1652(function($) {
1653
1654$.fn.bootstrapFileInput = function() {
1655
1656  this.each(function(i,elem){
1657
1658    var $elem = $(elem);
1659
1660    // Maybe some fields don't need to be standardized.
1661    if (typeof $elem.attr('data-bfi-disabled') != 'undefined') {
1662      return;
1663    }
1664
1665    // Set the word to be displayed on the button
1666    var buttonWord = 'Browse';
1667
1668    if (typeof $elem.attr('title') != 'undefined') {
1669      buttonWord = $elem.attr('title');
1670    }
1671
1672    var className = '';
1673
1674    if (!!$elem.attr('class')) {
1675      className = ' ' + $elem.attr('class');
1676    }
1677
1678    // Now we're going to wrap that input field with a Bootstrap button.
1679    // The input will actually still be there, it will just be float above and transparent (done with the CSS).
1680    $elem.wrap('<a class="file-input-wrapper btn btn-default ' + className + '"></a>
1680').parent().prepend($('<span></span>').html(buttonWord));
1681  })
1682
1683  // After we have found all of the file inputs let's apply a listener for tracking the mouse movement.
1684  // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh.
1685  .promise().done( function(){
1686
1687    // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor.
1688    // This gives us the pointer cursor that FF denies us
1689    $('.file-input-wrapper').mousemove(function(cursor) {
1690
1691      var input, wrapper,
1692        wrapperX, wrapperY,
1693        inputWidth, inputHeight,
1694        cursorX, cursorY;
1695
1696      // This wrapper element (the button surround this file input)
1697      wrapper = $(this);
1698      // The invisible file input element
1699      input = wrapper.find("input");
1700      // The left-most position of the wrapper
1701      wrapperX = wrapper.offset().left;
1702      // The top-most position of the wrapper
1703      wrapperY = wrapper.offset().top;
1704      // The with of the browsers input field
1705      inputWidth= input.width();
1706      // The height of the browsers input field
1707      inputHeight= input.height();
1708      //The position of the cursor in the wrapper
1709      cursorX = cursor.pageX;
1710      cursorY = cursor.pageY;
1711
1712      //The positions we are to move the invisible file input
1713      // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle
1714      moveInputX = cursorX - wrapperX - inputWidth + 20;
1715      // Slides the invisible input Browse button to be positioned middle under the cursor
1716      moveInputY = cursorY- wrapperY - (inputHeight/2);
1717
1718      // Apply the positioning styles to actually move the invisible file input
1719      input.css({
1720        left:moveInputX,
1721        top:moveInputY
1722      });
1723    });
1724
1725    $('body').on('change', '.file-input-wrapper input[type=file]', function(){
1726
1727      var fileName;
1728      fileName = $(this).val();
1729
1730      // Remove any previous file names
1731      $(this).parent().next('.file-input-name').remove();
1732      if (!!$(this).prop('files') && $(this).prop('files').length > 1) {
1733        fileName = $(this)[0].files.length+' files';
1734        //$(this).parent().after('<span class="file-input-name">'+$(this)[0].files.length+' files</span>');
1735      }
1736      else {
1737        // var fakepath = 'C:\\fakepath\\';
1738        // fileName = $(this).val().replace('C:\\fakepath\\','');
1739        fileName = fileName.substring(fileName.lastIndexOf('\\')+1,fileName.length);
1740      }
1741
1742      var selectedFileNamePlacement = $(this).data('filename-placement') || 'outside';
1743      if (selectedFileNamePlacement === 'inside') {
1744        // Print the fileName inside
1745        $(this).siblings('span').html(fileName);
1746        $(this).attr('title', fileName);
1747      } else if (selectedFileNamePlacement === 'outside') {
1748        // Print the fileName aside (right after the the button)
1749        $(this).parent().after('<span class="file-input-name">'+fileName+'</span>');
1750      } else {
1751        console.log('Error in bootstrap-file-input plugin : unknown placement [' + selectedFileNamePlacement + '] for selected filename');
1752      }
1753    });
1754
1755  });
1756
1757};
1758
1759// Add the styles before the first stylesheet
1760// This ensures they can be easily overridden with developer styles
1761var cssHtml = '<style>'+
1762  '.file-input-wrapper { overflow: hidden; position: relative; cursor: pointer; z-index: 1; }'+
1763  '.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover { position: absolute; top: 0; left: 0; cursor: pointer; opacity: 0; filter: alpha(opacity=0); z-index: 99; outline: 0; }'+
1764  '.file-input-name { margin-left: 8px; }'+
1765  '</style>';
1766$('link[rel=stylesheet]').eq(0).before(cssHtml);
1767
1768})(jQuery);
1769
1770;/* ===========================================================
1771 * Bootstrap: inputmask.js v3.1.0
1772 * http://jasny.github.io/bootstrap/javascript/#inputmask
1773 * 
1774 * Based on Masked Input plugin by Josh Bush (digitalbush.com)
1775 * ===========================================================
1776 * Copyright 2012-2014 Arnold Daniels
1777 *
1778 * Licensed under the Apache License, Version 2.0 (the "License")
1779 * you may not use this file except in compliance with the License.
1780 * You may obtain a copy of the License at
1781 *
1782 * http://www.apache.org/licenses/LICENSE-2.0
1783 *
1784 * Unless required by applicable law or agreed to in writing, software
1785 * distributed under the License is distributed on an "AS IS" BASIS,
1786 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1787 * See the License for the specific language governing permissions and
1788 * limitations under the License.
1789 * ========================================================== */
1790
1791+function ($) { "use strict";
1792
1793  var isIphone = (window.orientation !== undefined)
1794  var isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1
1795  var isIE = window.navigator.appName == 'Microsoft Internet Explorer'
1796
1797  // INPUTMASK PUBLIC CLASS DEFINITION
1798  // =================================
1799
1800  var Inputmask = function (element, options) {
1801    if (isAndroid) return // No support because caret positioning doesn't work on Android
1802    
1803    this.$element = $(element)
1804    this.options = $.extend({}, Inputmask.DEFAULTS, options)
1805    this.mask = String(this.options.mask)
1806    
1807    this.init()
1808    this.listen()
1809        
1810    this.checkVal() //Perform initial check for existing values
1811  }
1812
1813  Inputmask.DEFAULTS = {
1814    mask: "",
1815    placeholder: "_",
1816    definitions: {
1817      '9': "[0-9]",
1818      'a': "[A-Za-z]",
1819      'w': "[A-Za-z0-9]",
1820      '*': "."
1821    }
1822  }
1823
1824  Inputmask.prototype.init = function() {
1825    var defs = this.options.definitions
1826    var len = this.mask.length
1827
1828    this.tests = [] 
1829    this.partialPosition = this.mask.length
1830    this.firstNonMaskPos = null
1831
1832    $.each(this.mask.split(""), $.proxy(function(i, c) {
1833      if (c == '?') {
1834        len--
1835        this.partialPosition = i
1836      } else if (defs[c]) {
1837        this.tests.push(new RegExp(defs[c]))
1838        if (this.firstNonMaskPos === null)
1839          this.firstNonMaskPos =  this.tests.length - 1
1840      } else {
1841        this.tests.push(null)
1842      }
1843    }, this))
1844
1845    this.buffer = $.map(this.mask.split(""), $.proxy(function(c, i) {
1846      if (c != '?') return defs[c] ? this.options.placeholder : c
1847    }, this))
1848
1849    this.focusText = this.$element.val()
1850
1851    this.$element.data("rawMaskFn", $.proxy(function() {
1852      return $.map(this.buffer, function(c, i) {
1853        return this.tests[i] && c != this.options.placeholder ? c : null
1854      }).join('')
1855    }, this))
1856  }
1857    
1858  Inputmask.prototype.listen = function() {
1859    if (this.$element.attr("readonly")) return
1860
1861    var pasteEventName = (isIE ? 'paste' : 'input') + ".mask"
1862
1863    this.$element
1864      .on("unmask.bs.inputmask", $.proxy(this.unmask, this))
1865
1866      .on("focus.bs.inputmask", $.proxy(this.focusEvent, this))
1867      .on("blur.bs.inputmask", $.proxy(this.blurEvent, this))
1868
1869      .on("keydown.bs.inputmask", $.proxy(this.keydownEvent, this))
1870      .on("keypress.bs.inputmask", $.proxy(this.keypressEvent, this))
1871
1872      .on(pasteEventName, $.proxy(this.pasteEvent, this))
1873  }
1874
1875  //Helper Function for Caret positioning
1876  Inputmask.prototype.caret = function(begin, end) {
1877    if (this.$element.length === 0) return
1878    if (typeof begin == 'number') {
1879      end = (typeof end == 'number') ? end : begin
1880      return this.$element.each(function() {
1881        if (this.setSelectionRange) {
1882          this.setSelectionRange(begin, end)
1883        } else if (this.createTextRange) {
1884          var range = this.createTextRange()
1885          range.collapse(true)
1886          range.moveEnd('character', end)
1887          range.moveStart('character', begin)
1888          range.select()
1889        }
1890      })
1891    } else {
1892      if (this.$element[0].setSelectionRange) {
1893        begin = this.$element[0].selectionStart
1894        end = this.$element[0].selectionEnd
1895      } else if (document.selection && document.selection.createRange) {
1896        var range = document.selection.createRange()
1897        begin = 0 - range.duplicate().moveStart('character', -100000)
1898        end = begin + range.text.length
1899      }
1900      return {
1901        begin: begin, 
1902        end: end
1903      }
1904    }
1905  }
1906  
1907  Inputmask.prototype.seekNext = function(pos) {
1908    var len = this.mask.length
1909    while (++pos <= len && !this.tests[pos]);
1910
1911    return pos
1912  }
1913  
1914  Inputmask.prototype.seekPrev = function(pos) {
1915    while (--pos >= 0 && !this.tests[pos]);
1916
1917    return pos
1918  }
1919
1920  Inputmask.prototype.shiftL = function(begin,end) {
1921    var len = this.mask.length
1922
1923    if (begin < 0) return
1924
1925    for (var i = begin, j = this.seekNext(end); i < len; i++) {
1926      if (this.tests[i]) {
1927        if (j < len && this.tests[i].test(this.buffer[j])) {
1928          this.buffer[i] = this.buffer[j]
1929          this.buffer[j] = this.options.placeholder
1930        } else
1931          break
1932        j = this.seekNext(j)
1933      }
1934    }
1935    this.writeBuffer()
1936    this.caret(Math.max(this.firstNonMaskPos, begin))
1937  }
1938
1939  Inputmask.prototype.shiftR = function(pos) {
1940    var len = this.mask.length
1941
1942    for (var i = pos, c = this.options.placeholder; i < len; i++) {
1943      if (this.tests[i]) {
1944        var j = this.seekNext(i)
1945        var t = this.buffer[i]
1946        this.buffer[i] = c
1947        if (j < len && this.tests[j].test(t))
1948          c = t
1949        else
1950          break
1951      }
1952    }
1953  },
1954
1955  Inputmask.prototype.unmask = function() {
1956    this.$element
1957      .unbind(".mask")
1958      .removeData("inputmask")
1959  }
1960
1961  Inputmask.prototype.focusEvent = function() {
1962    this.focusText = this.$element.val()
1963    var len = this.mask.length 
1964    var pos = this.checkVal()
1965    this.writeBuffer()
1966
1967    var that = this
1968    var moveCaret = function() {
1969      if (pos == len)
1970        that.caret(0, pos)
1971      else
1972        that.caret(pos)
1973    }
1974
1975    moveCaret()
1976    setTimeout(moveCaret, 50)
1977  }
1978
1979  Inputmask.prototype.blurEvent = function() {
1980    this.checkVal()
1981    if (this.$element.val() !== this.focusText)
1982      this.$element.trigger('change')
1983  }
1984
1985  Inputmask.prototype.keydownEvent = function(e) {
1986    var k = e.which
1987
1988    //backspace, delete, and escape get special treatment
1989    if (k == 8 || k == 46 || (isIphone && k == 127)) {
1990      var pos = this.caret(),
1991      begin = pos.begin,
1992      end = pos.end
1993
1994      if (end - begin === 0) {
1995        begin = k != 46 ? this.seekPrev(begin) : (end = this.seekNext(begin - 1))
1996        end = k == 46 ? this.seekNext(end) : end
1997      }
1998      this.clearBuffer(begin, end)
1999      this.shiftL(begin, end - 1)
2000
2001      return false
2002    } else if (k == 27) {//escape
2003      this.$element.val(this.focusText)
2004      this.caret(0, this.checkVal())
2005      return false
2006    }
2007  }
2008
2009  Inputmask.prototype.keypressEvent = function(e) {
2010    var len = this.mask.length
2011
2012    var k = e.which,
2013    pos = this.caret()
2014
2015    if (e.ctrlKey || e.altKey || e.metaKey || k < 32)  {//Ignore
2016      return true
2017    } else if (k) {
2018      if (pos.end - pos.begin !== 0) {
2019        this.clearBuffer(pos.begin, pos.end)
2020        this.shiftL(pos.begin, pos.end - 1)
2021      }
2022
2023      var p = this.seekNext(pos.begin - 1)
2024      if (p < len) {
2025        var c = String.fromCharCode(k)
2026        if (this.tests[p].test(c)) {
2027          this.shiftR(p)
2028          this.buffer[p] = c
2029          this.writeBuffer()
2030          var next = this.seekNext(p)
2031          this.caret(next)
2032        }
2033      }
2034      return false
2035    }
2036  }
2037
2038  Inputmask.prototype.pasteEvent = function() {
2039    var that = this
2040
2041    setTimeout(function() {
2042      that.caret(that.checkVal(true))
2043    }, 0)
2044  }
2045
2046  Inputmask.prototype.clearBuffer = function(start, end) {
2047    var len = this.mask.length
2048
2049    for (var i = start; i < end && i < len; i++) {
2050      if (this.tests[i])
2051        this.buffer[i] = this.options.placeholder
2052    }
2053  }
2054
2055  Inputmask.prototype.writeBuffer = function() {
2056    return this.$element.val(this.buffer.join('')).val()
2057  }
2058
2059  Inputmask.prototype.checkVal = function(allow) {
2060    var len = this.mask.length
2061    //try to place characters where they belong
2062    var test = this.$element.val()
2063    var lastMatch = -1
2064
2065    for (var i = 0, pos = 0; i < len; i++) {
2066      if (this.tests[i]) {
2067        this.buffer[i] = this.options.placeholder
2068        while (pos++ < test.length) {
2069          var c = test.charAt(pos - 1)
2070          if (this.tests[i].test(c)) {
2071            this.buffer[i] = c
2072            lastMatch = i
2073            break
2074          }
2075        }
2076        if (pos > test.length)
2077          break
2078      } else if (this.buffer[i] == test.charAt(pos) && i != this.partialPosition) {
2079        pos++
2080        lastMatch = i
2081      }
2082    }
2083    if (!allow && lastMatch + 1 < this.partialPosition) {
2084      this.$element.val("")
2085      this.clearBuffer(0, len)
2086    } else if (allow || lastMatch + 1 >= this.partialPosition) {
2087      this.writeBuffer()
2088      if (!allow) this.$element.val(this.$element.val().substring(0, lastMatch + 1))
2089    }
2090    return (this.partialPosition ? i : this.firstNonMaskPos)
2091  }
2092
2093  
2094  // INPUTMASK PLUGIN DEFINITION
2095  // ===========================
2096
2097  var old = $.fn.inputmask
2098  
2099  $.fn.inputmask = function (options) {
2100    return this.each(function () {
2101      var $this = $(this)
2102      var data = $this.data('bs.inputmask')
2103      
2104      if (!data) $this.data('bs.inputmask', (data = new Inputmask(this, options)))
2105    })
2106  }
2107
2108  $.fn.inputmask.Constructor = Inputmask
2109
2110
2111  // INPUTMASK NO CONFLICT
2112  // ====================
2113
2114  $.fn.inputmask.noConflict = function () {
2115    $.fn.inputmask = old
2116    return this
2117  }
2118
2119
2120  // INPUTMASK DATA-API
2121  // ==================
2122
2123  $(document).on('focus.bs.inputmask.data-api', '[data-mask]', function (e) {
2124    var $this = $(this)
2125    if ($this.data('bs.inputmask')) return
2126    $this.inputmask($this.data())
2127  })
2128
2129}(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.