integ/lib/bootstrap-multiselect.js
changeset 1 ad760c317293
equal deleted inserted replaced
0:989a994c5163 1:ad760c317293
       
     1 /**
       
     2  * bootstrap-multiselect.js
       
     3  * https://github.com/davidstutz/bootstrap-multiselect
       
     4  *
       
     5  * Copyright 2012, 2013 David Stutz
       
     6  *
       
     7  * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0.
       
     8  */
       
     9 !function($) {
       
    10 
       
    11     "use strict";// jshint ;_;
       
    12 
       
    13     if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
       
    14         ko.bindingHandlers.multiselect = {
       
    15             init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {},
       
    16             update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
       
    17 
       
    18                var config = ko.utils.unwrapObservable(valueAccessor());
       
    19                var selectOptions = allBindingsAccessor().options;
       
    20                var ms = $(element).data('multiselect');
       
    21 
       
    22                if (!ms) {
       
    23                   $(element).multiselect(config);
       
    24                }
       
    25                else {
       
    26                   ms.updateOriginalOptions();
       
    27                   if (selectOptions && selectOptions().length !== ms.originalOptions.length) {
       
    28                      $(element).multiselect('rebuild');
       
    29                   }
       
    30                }
       
    31             }
       
    32         };
       
    33     }
       
    34 
       
    35     /**
       
    36      * Constructor to create a new multiselect using the given select.
       
    37      * 
       
    38      * @param {jQuery} select
       
    39      * @param {Object} options
       
    40      * @returns {Multiselect}
       
    41      */
       
    42     function Multiselect(select, options) {
       
    43 
       
    44         this.options = this.mergeOptions(options);
       
    45         this.$select = $(select);
       
    46 
       
    47         // Initialization.
       
    48         // We have to clone to create a new reference.
       
    49         this.originalOptions = this.$select.clone()[0].options;
       
    50         this.query = '';
       
    51         this.searchTimeout = null;
       
    52 
       
    53         this.options.multiple = this.$select.attr('multiple') === "multiple";
       
    54         this.options.onChange = $.proxy(this.options.onChange, this);
       
    55         this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
       
    56         this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
       
    57 
       
    58         // Build select all if enabled.
       
    59         this.buildContainer();
       
    60         this.buildButton();
       
    61         this.buildSelectAll();
       
    62         this.buildDropdown();
       
    63         this.buildDropdownOptions();
       
    64         this.buildFilter();
       
    65         
       
    66         this.updateButtonText();
       
    67         this.updateSelectAll();
       
    68         
       
    69         this.$select.hide().after(this.$container);
       
    70     };
       
    71 
       
    72     Multiselect.prototype = {
       
    73 
       
    74         defaults: {
       
    75             /**
       
    76              * Default text function will either print 'None selected' in case no
       
    77              * option is selected or a list of the selected options up to a length of 3 selected options.
       
    78              * 
       
    79              * @param {jQuery} options
       
    80              * @param {jQuery} select
       
    81              * @returns {String}
       
    82              */
       
    83             buttonText: function(options, select) {
       
    84                 if (options.length === 0) {
       
    85                     return this.nonSelectedText + ' <b class="caret"></b>';
       
    86                 }
       
    87                 else {
       
    88                     if (options.length > this.numberDisplayed) {
       
    89                         return options.length + ' ' + this.nSelectedText + ' <b class="caret"></b>';
       
    90                     }
       
    91                     else {
       
    92                         var selected = '';
       
    93                         options.each(function() {
       
    94                             var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html();
       
    95 
       
    96                             selected += label + ', ';
       
    97                         });
       
    98                         return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>';
       
    99                     }
       
   100                 }
       
   101             },
       
   102             /**
       
   103              * Updates the title of the button similar to the buttonText function.
       
   104              * @param {jQuery} options
       
   105              * @param {jQuery} select
       
   106              * @returns {@exp;selected@call;substr}
       
   107              */
       
   108             buttonTitle: function(options, select) {
       
   109                 if (options.length === 0) {
       
   110                     return this.nonSelectedText;
       
   111                 }
       
   112                 else {
       
   113                     var selected = '';
       
   114                     options.each(function () {
       
   115                         selected += $(this).text() + ', ';
       
   116                     });
       
   117                     return selected.substr(0, selected.length - 2);
       
   118                 }
       
   119             },
       
   120             /**
       
   121              * Create a label.
       
   122              * 
       
   123              * @param {jQuery} element
       
   124              * @returns {String}
       
   125              */
       
   126             label: function(element){
       
   127                 return $(element).attr('label') || $(element).html();
       
   128             },
       
   129             /**
       
   130              * Triggered on change of the multiselect.
       
   131              * Not triggered when selecting/deselecting options manually.
       
   132              * 
       
   133              * @param {jQuery} option
       
   134              * @param {Boolean} checked
       
   135              */
       
   136             onChange : function(option, checked) {
       
   137 
       
   138             },
       
   139             /**
       
   140              * Triggered when the dropdown is shown.
       
   141              * 
       
   142              * @param {jQuery} event
       
   143              */
       
   144             onDropdownShow: function(event) {
       
   145 
       
   146             },
       
   147             /**
       
   148              * Triggered when the dropdown is hidden.
       
   149              * 
       
   150              * @param {jQuery} event
       
   151              */
       
   152             onDropdownHide: function(event) {
       
   153 
       
   154             },
       
   155             buttonClass: 'btn btn-default',
       
   156             dropRight: false,
       
   157             selectedClass: 'active',
       
   158             buttonWidth: 'auto',
       
   159             buttonContainer: '<div class="btn-group" />',
       
   160             // Maximum height of the dropdown menu.
       
   161             // If maximum height is exceeded a scrollbar will be displayed.
       
   162             maxHeight: false,
       
   163             includeSelectAllOption: false,
       
   164             selectAllText: ' Select all',
       
   165             selectAllValue: 'multiselect-all',
       
   166             enableFiltering: false,
       
   167             enableCaseInsensitiveFiltering: false,
       
   168             filterPlaceholder: 'Search',
       
   169             // possible options: 'text', 'value', 'both'
       
   170             filterBehavior: 'text',
       
   171             preventInputChangeEvent: false,
       
   172             nonSelectedText: 'None selected',
       
   173             nSelectedText: 'selected',
       
   174             numberDisplayed: 3
       
   175         },
       
   176 
       
   177         templates: {
       
   178             button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"></button>',
       
   179             ul: '<ul class="multiselect-container dropdown-menu"></ul>',
       
   180             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>',
       
   181             li: '<li><a href="javascript:void(0);"><label></label></a></li>',
       
   182             divider: '<li class="divider"></li>',
       
   183             liGroup: '<li><label class="multiselect-group"></label></li>'
       
   184         },
       
   185 
       
   186         constructor: Multiselect,
       
   187 
       
   188         /**
       
   189          * Builds the container of the multiselect.
       
   190          */
       
   191         buildContainer: function() {
       
   192             this.$container = $(this.options.buttonContainer);
       
   193             this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
       
   194             this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
       
   195         },
       
   196 
       
   197         /**
       
   198          * Builds the button of the multiselect.
       
   199          */
       
   200         buildButton: function() {
       
   201             this.$button = $(this.templates.button).addClass(this.options.buttonClass);
       
   202 
       
   203             // Adopt active state.
       
   204             if (this.$select.prop('disabled')) {
       
   205                 this.disable();
       
   206             }
       
   207             else {
       
   208                 this.enable();
       
   209             }
       
   210 
       
   211             // Manually add button width if set.
       
   212             if (this.options.buttonWidth && this.options.buttonWidth != 'auto') {
       
   213                 this.$button.css({
       
   214                     'width' : this.options.buttonWidth
       
   215                 });
       
   216             }
       
   217 
       
   218             // Keep the tab index from the select.
       
   219             var tabindex = this.$select.attr('tabindex');
       
   220             if (tabindex) {
       
   221                 this.$button.attr('tabindex', tabindex);
       
   222             }
       
   223 
       
   224             this.$container.prepend(this.$button);
       
   225         },
       
   226 
       
   227         /**
       
   228          * Builds the ul representing the dropdown menu.
       
   229          */
       
   230         buildDropdown: function() {
       
   231 
       
   232             // Build ul.
       
   233             this.$ul = $(this.templates.ul);
       
   234 
       
   235             if (this.options.dropRight) {
       
   236                 this.$ul.addClass('pull-right');
       
   237             }
       
   238 
       
   239             // Set max height of dropdown menu to activate auto scrollbar.
       
   240             if (this.options.maxHeight) {
       
   241                 // TODO: Add a class for this option to move the css declarations.
       
   242                 this.$ul.css({
       
   243                     'max-height': this.options.maxHeight + 'px',
       
   244                     'overflow-y': 'auto',
       
   245                     'overflow-x': 'hidden'
       
   246                 });
       
   247             }
       
   248 
       
   249             this.$container.append(this.$ul);
       
   250         },
       
   251 
       
   252         /**
       
   253          * Build the dropdown options and binds all nessecary events.
       
   254          * Uses createDivider and createOptionValue to create the necessary options.
       
   255          */
       
   256         buildDropdownOptions: function() {
       
   257 
       
   258             this.$select.children().each($.proxy(function(index, element) {
       
   259                 
       
   260                 // Support optgroups and options without a group simultaneously.
       
   261                 var tag = $(element).prop('tagName')
       
   262                     .toLowerCase();
       
   263 
       
   264                 if (tag === 'optgroup') {
       
   265                     this.createOptgroup(element);
       
   266                 }
       
   267                 else if (tag === 'option') {
       
   268 
       
   269                     if ($(element).data('role') === 'divider') {
       
   270                         this.createDivider();
       
   271                     }
       
   272                     else {
       
   273                         this.createOptionValue(element);
       
   274                     }
       
   275 
       
   276                 }
       
   277                 
       
   278                 // Other illegal tags will be ignored.
       
   279             }, this));
       
   280 
       
   281             // Bind the change event on the dropdown elements.
       
   282             $('li input', this.$ul).on('change', $.proxy(function(event) {
       
   283                 var checked = $(event.target).prop('checked') || false;
       
   284                 var isSelectAllOption = $(event.target).val() === this.options.selectAllValue;
       
   285 
       
   286                 // Apply or unapply the configured selected class.
       
   287                 if (this.options.selectedClass) {
       
   288                     if (checked) {
       
   289                         $(event.target).parents('li')
       
   290                             .addClass(this.options.selectedClass);
       
   291                     }
       
   292                     else {
       
   293                         $(event.target).parents('li')
       
   294                             .removeClass(this.options.selectedClass);
       
   295                     }
       
   296                 }
       
   297 
       
   298                 // Get the corresponding option.
       
   299                 var value = $(event.target).val();
       
   300                 var $option = this.getOptionByValue(value);
       
   301 
       
   302                 var $optionsNotThis = $('option', this.$select).not($option);
       
   303                 var $checkboxesNotThis = $('input', this.$container).not($(event.target));
       
   304 
       
   305                 if (isSelectAllOption) {
       
   306                     if (this.$select[0][0].value === this.options.selectAllValue) {
       
   307                         var values = [];
       
   308                         var options = $('option[value!="' + this.options.selectAllValue + '"]', this.$select);
       
   309                         for (var i = 0; i < options.length; i++) {
       
   310                             // Additionally check whether the option is visible within the dropcown.
       
   311                             if (options[i].value !== this.options.selectAllValue && this.getInputByValue(options[i].value).is(':visible')) {
       
   312                                 values.push(options[i].value);
       
   313                             }
       
   314                         }
       
   315 
       
   316                         if (checked) {
       
   317                             this.select(values);
       
   318                         }
       
   319                         else {
       
   320                             this.deselect(values);
       
   321                         }
       
   322                     }
       
   323                 }
       
   324 
       
   325                 if (checked) {
       
   326                     $option.prop('selected', true);
       
   327 
       
   328                     if (this.options.multiple) {
       
   329                         // Simply select additional option.
       
   330                         $option.prop('selected', true);
       
   331                     }
       
   332                     else {
       
   333                         // Unselect all other options and corresponding checkboxes.
       
   334                         if (this.options.selectedClass) {
       
   335                             $($checkboxesNotThis).parents('li').removeClass(this.options.selectedClass);
       
   336                         }
       
   337 
       
   338                         $($checkboxesNotThis).prop('checked', false);
       
   339                         $optionsNotThis.prop('selected', false);
       
   340 
       
   341                         // It's a single selection, so close.
       
   342                         this.$button.click();
       
   343                     }
       
   344 
       
   345                     if (this.options.selectedClass === "active") {
       
   346                         $optionsNotThis.parents("a").css("outline", "");
       
   347                     }
       
   348                 }
       
   349                 else {
       
   350                     // Unselect option.
       
   351                     $option.prop('selected', false);
       
   352                 }
       
   353 
       
   354                 this.$select.change();
       
   355                 this.options.onChange($option, checked);
       
   356                 
       
   357                 this.updateButtonText();
       
   358                 this.updateSelectAll();
       
   359 
       
   360                 if(this.options.preventInputChangeEvent) {
       
   361                     return false;
       
   362                 }
       
   363             }, this));
       
   364 
       
   365             $('li a', this.$ul).on('touchstart click', function(event) {
       
   366                 event.stopPropagation();
       
   367 
       
   368                 if (event.shiftKey) {
       
   369                     var checked = $(event.target).prop('checked') || false;
       
   370 
       
   371                     if (checked) {
       
   372                         var prev = $(event.target).parents('li:last')
       
   373                             .siblings('li[class="active"]:first');
       
   374 
       
   375                         var currentIdx = $(event.target).parents('li')
       
   376                             .index();
       
   377                         var prevIdx = prev.index();
       
   378 
       
   379                         if (currentIdx > prevIdx) {
       
   380                             $(event.target).parents("li:last").prevUntil(prev).each(
       
   381                                 function() {
       
   382                                     $(this).find("input:first").prop("checked", true)
       
   383                                         .trigger("change");
       
   384                                 }
       
   385                             );
       
   386                         }
       
   387                         else {
       
   388                             $(event.target).parents("li:last").nextUntil(prev).each(
       
   389                                 function() {
       
   390                                     $(this).find("input:first").prop("checked", true)
       
   391                                         .trigger("change");
       
   392                                 }
       
   393                             );
       
   394                         }
       
   395                     }
       
   396                 }
       
   397 
       
   398                 $(event.target).blur();
       
   399             });
       
   400 
       
   401             // Keyboard support.
       
   402             this.$container.on('keydown', $.proxy(function(event) {
       
   403                 if ($('input[type="text"]', this.$container).is(':focus')) {
       
   404                     return;
       
   405                 }
       
   406                 if ((event.keyCode === 9 || event.keyCode === 27)
       
   407                         && this.$container.hasClass('open')) {
       
   408                     
       
   409                     // Close on tab or escape.
       
   410                     this.$button.click();
       
   411                 }
       
   412                 else {
       
   413                     var $items = $(this.$container).find("li:not(.divider):visible a");
       
   414 
       
   415                     if (!$items.length) {
       
   416                         return;
       
   417                     }
       
   418 
       
   419                     var index = $items.index($items.filter(':focus'));
       
   420 
       
   421                     // Navigation up.
       
   422                     if (event.keyCode === 38 && index > 0) {
       
   423                         index--;
       
   424                     }
       
   425                     // Navigate down.
       
   426                     else if (event.keyCode === 40 && index < $items.length - 1) {
       
   427                         index++;
       
   428                     }
       
   429                     else if (!~index) {
       
   430                         index = 0;
       
   431                     }
       
   432 
       
   433                     var $current = $items.eq(index);
       
   434                     $current.focus();
       
   435 
       
   436                     if (event.keyCode === 32 || event.keyCode === 13) {
       
   437                         var $checkbox = $current.find('input');
       
   438 
       
   439                         $checkbox.prop("checked", !$checkbox.prop("checked"));
       
   440                         $checkbox.change();
       
   441                     }
       
   442 
       
   443                     event.stopPropagation();
       
   444                     event.preventDefault();
       
   445                 }
       
   446             }, this));
       
   447         },
       
   448 
       
   449         /**
       
   450          * Create an option using the given select option.
       
   451          * 
       
   452          * @param {jQuery} element
       
   453          */
       
   454         createOptionValue: function(element) {
       
   455             if ($(element).is(':selected')) {
       
   456                 $(element).prop('selected', true);
       
   457             }
       
   458 
       
   459             // Support the label attribute on options.
       
   460             var label = this.options.label(element);
       
   461             var value = $(element).val();
       
   462             var inputType = this.options.multiple ? "checkbox" : "radio";
       
   463 
       
   464             var $li = $(this.templates.li);
       
   465             $('label', $li).addClass(inputType);
       
   466             $('label', $li).append('<input type="' + inputType + '" />');
       
   467 
       
   468             var selected = $(element).prop('selected') || false;
       
   469             var $checkbox = $('input', $li);
       
   470             $checkbox.val(value);
       
   471 
       
   472             if (value === this.options.selectAllValue) {
       
   473                 $checkbox.parent().parent()
       
   474                     .addClass('multiselect-all');
       
   475             }
       
   476 
       
   477             $('label', $li).append(" " + label);
       
   478 
       
   479             this.$ul.append($li);
       
   480 
       
   481             if ($(element).is(':disabled')) {
       
   482                 $checkbox.attr('disabled', 'disabled')
       
   483                     .prop('disabled', true)
       
   484                     .parents('li')
       
   485                     .addClass('disabled');
       
   486             }
       
   487 
       
   488             $checkbox.prop('checked', selected);
       
   489 
       
   490             if (selected && this.options.selectedClass) {
       
   491                 $checkbox.parents('li')
       
   492                     .addClass(this.options.selectedClass);
       
   493             }
       
   494         },
       
   495 
       
   496         /**
       
   497          * Creates a divider using the given select option.
       
   498          * 
       
   499          * @param {jQuery} element
       
   500          */
       
   501         createDivider: function(element) {
       
   502             var $divider = $(this.templates.divider);
       
   503             this.$ul.append($divider);
       
   504         },
       
   505 
       
   506         /**
       
   507          * Creates an optgroup.
       
   508          * 
       
   509          * @param {jQuery} group
       
   510          */
       
   511         createOptgroup: function(group) {
       
   512             var groupName = $(group).prop('label');
       
   513 
       
   514             // Add a header for the group.
       
   515             var $li = $(this.templates.liGroup);
       
   516             $('label', $li).text(groupName);
       
   517 
       
   518             this.$ul.append($li);
       
   519 
       
   520             // Add the options of the group.
       
   521             $('option', group).each($.proxy(function(index, element) {
       
   522                 this.createOptionValue(element);
       
   523             }, this));
       
   524         },
       
   525 
       
   526         /**
       
   527          * Build the selct all.
       
   528          * Checks if a select all ahs already been created.
       
   529          */
       
   530         buildSelectAll: function() {
       
   531             var alreadyHasSelectAll = this.hasSelectAll();
       
   532             
       
   533             // If options.includeSelectAllOption === true, add the include all checkbox.
       
   534             if (this.options.includeSelectAllOption && this.options.multiple && !alreadyHasSelectAll) {
       
   535                 this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>');
       
   536             }
       
   537         },
       
   538 
       
   539         /**
       
   540          * Builds the filter.
       
   541          */
       
   542         buildFilter: function() {
       
   543 
       
   544             // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
       
   545             if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
       
   546                 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
       
   547 
       
   548                 if (this.$select.find('option').length >= enableFilterLength) {
       
   549 
       
   550                     this.$filter = $(this.templates.filter);
       
   551                     $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
       
   552                     this.$ul.prepend(this.$filter);
       
   553 
       
   554                     this.$filter.val(this.query).on('click', function(event) {
       
   555                         event.stopPropagation();
       
   556                     }).on('input keydown', $.proxy(function(event) {
       
   557                         // This is useful to catch "keydown" events after the browser has updated the control.
       
   558                         clearTimeout(this.searchTimeout);
       
   559 
       
   560                         this.searchTimeout = this.asyncFunction($.proxy(function() {
       
   561 
       
   562                             if (this.query !== event.target.value) {
       
   563                                 this.query = event.target.value;
       
   564 
       
   565                                 $.each($('li', this.$ul), $.proxy(function(index, element) {
       
   566                                     var value = $('input', element).val();
       
   567                                     var text = $('label', element).text();
       
   568 
       
   569                                     if (value !== this.options.selectAllValue && text) {
       
   570                                         // by default lets assume that element is not
       
   571                                         // interesting for this search
       
   572                                         var showElement = false;
       
   573 
       
   574                                         var filterCandidate = '';
       
   575                                         if ((this.options.filterBehavior === 'text' || this.options.filterBehavior === 'both')) {
       
   576                                             filterCandidate = text;
       
   577                                         }
       
   578                                         if ((this.options.filterBehavior === 'value' || this.options.filterBehavior === 'both')) {
       
   579                                             filterCandidate = value;
       
   580                                         }
       
   581 
       
   582                                         if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
       
   583                                             showElement = true;
       
   584                                         }
       
   585                                         else if (filterCandidate.indexOf(this.query) > -1) {
       
   586                                             showElement = true;
       
   587                                         }
       
   588 
       
   589                                         if (showElement) {
       
   590                                             $(element).show();
       
   591                                         }
       
   592                                         else {
       
   593                                             $(element).hide();
       
   594                                         }
       
   595                                     }
       
   596                                 }, this));
       
   597                             }
       
   598 
       
   599                             // TODO: check whether select all option needs to be updated.
       
   600                         }, this), 300, this);
       
   601                     }, this));
       
   602                 }
       
   603             }
       
   604         },
       
   605 
       
   606         /**
       
   607          * Unbinds the whole plugin.
       
   608          */
       
   609         destroy: function() {
       
   610             this.$container.remove();
       
   611             this.$select.show();
       
   612         },
       
   613 
       
   614         /**
       
   615          * Refreshs the multiselect based on the selected options of the select.
       
   616          */
       
   617         refresh: function() {
       
   618             $('option', this.$select).each($.proxy(function(index, element) {
       
   619                 var $input = $('li input', this.$ul).filter(function() {
       
   620                     return $(this).val() === $(element).val();
       
   621                 });
       
   622 
       
   623                 if ($(element).is(':selected')) {
       
   624                     $input.prop('checked', true);
       
   625 
       
   626                     if (this.options.selectedClass) {
       
   627                         $input.parents('li')
       
   628                             .addClass(this.options.selectedClass);
       
   629                     }
       
   630                 }
       
   631                 else {
       
   632                     $input.prop('checked', false);
       
   633 
       
   634                     if (this.options.selectedClass) {
       
   635                         $input.parents('li')
       
   636                             .removeClass(this.options.selectedClass);
       
   637                     }
       
   638                 }
       
   639 
       
   640                 if ($(element).is(":disabled")) {
       
   641                     $input.attr('disabled', 'disabled')
       
   642                         .prop('disabled', true)
       
   643                         .parents('li')
       
   644                         .addClass('disabled');
       
   645                 }
       
   646                 else {
       
   647                     $input.prop('disabled', false)
       
   648                         .parents('li')
       
   649                         .removeClass('disabled');
       
   650                 }
       
   651             }, this));
       
   652 
       
   653             this.updateButtonText();
       
   654             this.updateSelectAll();
       
   655         },
       
   656 
       
   657         /**
       
   658          * Select all options of the given values.
       
   659          * 
       
   660          * @param {Array} selectValues
       
   661          */
       
   662         select: function(selectValues) {
       
   663             if(selectValues && !$.isArray(selectValues)) {
       
   664                 selectValues = [selectValues];
       
   665             }
       
   666 
       
   667             for (var i = 0; i < selectValues.length; i++) {
       
   668                 var value = selectValues[i];
       
   669 
       
   670                 var $option = this.getOptionByValue(value);
       
   671                 var $checkbox = this.getInputByValue(value);
       
   672 
       
   673                 if (this.options.selectedClass) {
       
   674                     $checkbox.parents('li')
       
   675                         .addClass(this.options.selectedClass);
       
   676                 }
       
   677 
       
   678                 $checkbox.prop('checked', true);
       
   679                 $option.prop('selected', true);
       
   680             }
       
   681 
       
   682             this.updateButtonText();
       
   683         },
       
   684 
       
   685         /**
       
   686          * Deselects all options of the given values.
       
   687          * 
       
   688          * @param {Array} deselectValues
       
   689          */
       
   690         deselect: function(deselectValues) {
       
   691             if(deselectValues && !$.isArray(deselectValues)) {
       
   692                 deselectValues = [deselectValues];
       
   693             }
       
   694 
       
   695             for (var i = 0; i < deselectValues.length; i++) {
       
   696 
       
   697                 var value = deselectValues[i];
       
   698 
       
   699                 var $option = this.getOptionByValue(value);
       
   700                 var $checkbox = this.getInputByValue(value);
       
   701 
       
   702                 if (this.options.selectedClass) {
       
   703                     $checkbox.parents('li')
       
   704                         .removeClass(this.options.selectedClass);
       
   705                 }
       
   706 
       
   707                 $checkbox.prop('checked', false);
       
   708                 $option.prop('selected', false);
       
   709             }
       
   710 
       
   711             this.updateButtonText();
       
   712         },
       
   713 
       
   714         /**
       
   715          * Rebuild the plugin.
       
   716          * Rebuilds the dropdown, the filter and the select all option.
       
   717          */
       
   718         rebuild: function() {
       
   719             this.$ul.html('');
       
   720 
       
   721             // Remove select all option in select.
       
   722             $('option[value="' + this.options.selectAllValue + '"]', this.$select).remove();
       
   723 
       
   724             // Important to distinguish between radios and checkboxes.
       
   725             this.options.multiple = this.$select.attr('multiple') === "multiple";
       
   726 
       
   727             this.buildSelectAll();
       
   728             this.buildDropdownOptions();
       
   729             this.buildFilter();
       
   730             
       
   731             this.updateButtonText();
       
   732             this.updateSelectAll();
       
   733         },
       
   734 
       
   735         /**
       
   736          * The provided data will be used to build the dropdown.
       
   737          * 
       
   738          * @param {Array} dataprovider
       
   739          */
       
   740         dataprovider: function(dataprovider) {
       
   741             var optionDOM = "";
       
   742             dataprovider.forEach(function (option) {
       
   743                 optionDOM += '<option value="' + option.value + '">' + option.label + '</option>';
       
   744             });
       
   745 
       
   746             this.$select.html(optionDOM);
       
   747             this.rebuild();
       
   748         },
       
   749 
       
   750         /**
       
   751          * Enable the multiselect.
       
   752          */
       
   753         enable: function() {
       
   754             this.$select.prop('disabled', false);
       
   755             this.$button.prop('disabled', false)
       
   756                 .removeClass('disabled');
       
   757         },
       
   758 
       
   759         /**
       
   760          * Disable the multiselect.
       
   761          */
       
   762         disable: function() {
       
   763             this.$select.prop('disabled', true);
       
   764             this.$button.prop('disabled', true)
       
   765                 .addClass('disabled');
       
   766         },
       
   767 
       
   768         /**
       
   769          * Set the options.
       
   770          * 
       
   771          * @param {Array} options
       
   772          */
       
   773         setOptions: function(options) {
       
   774             this.options = this.mergeOptions(options);
       
   775         },
       
   776 
       
   777         /**
       
   778          * Merges the given options with the default options.
       
   779          * 
       
   780          * @param {Array} options
       
   781          * @returns {Array}
       
   782          */
       
   783         mergeOptions: function(options) {
       
   784             return $.extend({}, this.defaults, options);
       
   785         },
       
   786         
       
   787         /**
       
   788          * Checks whether a select all option is present.
       
   789          * 
       
   790          * @returns {Boolean}
       
   791          */
       
   792         hasSelectAll: function() {
       
   793             return this.$select[0][0] ? this.$select[0][0].value === this.options.selectAllValue : false;
       
   794         },
       
   795         
       
   796         /**
       
   797          * Updates the select all option based on the currently selected options.
       
   798          */
       
   799         updateSelectAll: function() {
       
   800             if (this.hasSelectAll()) {
       
   801                 var selected = this.getSelected();
       
   802                 
       
   803                 if (selected.length === $('option', this.$select).length - 1) {
       
   804                     this.select(this.options.selectAllValue);
       
   805                 }
       
   806                 else {
       
   807                     this.deselect(this.options.selectAllValue);
       
   808                 }
       
   809             }
       
   810         },
       
   811         
       
   812         /**
       
   813          * Update the button text and its title base don the currenty selected options.
       
   814          */
       
   815         updateButtonText: function() {
       
   816             var options = this.getSelected();
       
   817             
       
   818             // First update the displayed button text.
       
   819             $('button', this.$container).html(this.options.buttonText(options, this.$select));
       
   820             
       
   821             // Now update the title attribute of the button.
       
   822             $('button', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
       
   823 
       
   824         },
       
   825 
       
   826         /**
       
   827          * Get all selected options.
       
   828          * 
       
   829          * @returns {jQUery}
       
   830          */
       
   831         getSelected: function() {
       
   832             return $('option[value!="' + this.options.selectAllValue + '"]:selected', this.$select).filter(function() {
       
   833                 return $(this).prop('selected');
       
   834             });
       
   835         },
       
   836 
       
   837         /**
       
   838          * Gets a select option by its value.
       
   839          * 
       
   840          * @param {String} value
       
   841          * @returns {jQuery}
       
   842          */
       
   843         getOptionByValue: function(value) {
       
   844             return $('option', this.$select).filter(function() {
       
   845                 return $(this).val() === value;
       
   846             });
       
   847         },
       
   848 
       
   849         /**
       
   850          * Get the input (radio/checkbox) by its value.
       
   851          * 
       
   852          * @param {String} value
       
   853          * @returns {jQuery}
       
   854          */
       
   855         getInputByValue: function(value) {
       
   856             return $('li input', this.$ul).filter(function() {
       
   857                 return $(this).val() === value;
       
   858             });
       
   859         },
       
   860 
       
   861         /**
       
   862          * Used for knockout integration.
       
   863          */
       
   864         updateOriginalOptions: function() {
       
   865             this.originalOptions = this.$select.clone()[0].options;
       
   866         },
       
   867 
       
   868         asyncFunction: function(callback, timeout, self) {
       
   869             var args = Array.prototype.slice.call(arguments, 3);
       
   870             return setTimeout(function() {
       
   871                 callback.apply(self || window, args);
       
   872             }, timeout);
       
   873         }
       
   874     };
       
   875 
       
   876     $.fn.multiselect = function(option, parameter) {
       
   877         return this.each(function() {
       
   878             var data = $(this).data('multiselect');
       
   879             var options = typeof option === 'object' && option;
       
   880 
       
   881             // Initialize the multiselect.
       
   882             if (!data) {
       
   883                 $(this).data('multiselect', ( data = new Multiselect(this, options)));
       
   884             }
       
   885 
       
   886             // Call multiselect method.
       
   887             if (typeof option === 'string') {
       
   888                 data[option](parameter);
       
   889             }
       
   890         });
       
   891     };
       
   892 
       
   893     $.fn.multiselect.Constructor = Multiselect;
       
   894 
       
   895     $(function() {
       
   896         $("select[data-role=multiselect]").multiselect();
       
   897     });
       
   898 
       
   899 }(window.jQuery);