PageSourceSearch

https://www.tricare.com.au/app/themes/pvtl-child/dist/js/vendor/bootstrap-multiselect.js?ver=1788928598

js tricare.com.au collected 2026-10-04 09:41:38 UTC 57,154 bytes, 1,776 lines download raw bytes

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

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