PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 3.06.06
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v3.06.06
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | js/bootstrap-multiselect.js +402 -1037 6.5.3 → 3.06.06 View file →
@@ -1,66 +1,19 @@
1 1 /**
2 - * Bootstrap Multiselect (http://davidstutz.de/bootstrap-multiselect/)
3 - *
4 - * Apache License, Version 2.0:
5 - * Copyright (c) 2012 - 2021 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 - 2021 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.
2 + * Bootstrap Multiselect v0.9.13 (https://github.com/davidstutz/bootstrap-multiselect)
3 + *
4 + * Copyright 2012 - 2015 David Stutz
5 + *
6 + * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0.
43 7 */
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) {
8 +!function ($) {
56 9 "use strict";// jshint ;_;
57 10
58 11 if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
59 12 ko.bindingHandlers.multiselect = {
60 - after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
13 + after: ['options', 'value', 'selectedOptions'],
61 14
62 - init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
15 + init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
63 16 var $element = $(element);
64 17 var config = ko.toJS(valueAccessor());
65 18
66 19 $element.multiselect(config);
@@ -68,11 +21,11 @@
68 21 if (allBindings.has('options')) {
69 22 var options = allBindings.get('options');
70 23 if (ko.isObservable(options)) {
71 24 ko.computed({
72 - read: function () {
25 + read: function() {
73 26 options();
74 - setTimeout(function () {
27 + setTimeout(function() {
75 28 var ms = $element.data('multiselect');
76 29 if (ms)
77 30 ms.updateOriginalOptions();//Not sure how beneficial this is.
78 31 $element.multiselect('rebuild');
@@ -89,11 +42,11 @@
89 42 if (allBindings.has('value')) {
90 43 var value = allBindings.get('value');
91 44 if (ko.isObservable(value)) {
92 45 ko.computed({
93 - read: function () {
46 + read: function() {
94 47 value();
95 - setTimeout(function () {
48 + setTimeout(function() {
96 49 $element.multiselect('refresh');
97 50 }, 1);
98 51 },
99 52 disposeWhenNodeIsRemoved: element
@@ -106,11 +59,11 @@
106 59 if (allBindings.has('selectedOptions')) {
107 60 var selectedOptions = allBindings.get('selectedOptions');
108 61 if (ko.isObservable(selectedOptions)) {
109 62 ko.computed({
110 - read: function () {
63 + read: function() {
111 64 selectedOptions();
112 - setTimeout(function () {
65 + setTimeout(function() {
113 66 $element.multiselect('refresh');
114 67 }, 1);
115 68 },
116 69 disposeWhenNodeIsRemoved: element
@@ -117,51 +70,14 @@
117 70 }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
118 71 }
119 72 }
120 73
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 () {
74 + ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
159 75 $element.multiselect('destroy');
160 76 });
161 77 },
162 78
163 - update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
79 + update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
164 80 var $element = $(element);
165 81 var config = ko.toJS(valueAccessor());
166 82
167 83 $element.multiselect('setOptions', config);
@@ -185,14 +101,15 @@
185 101 */
186 102 function Multiselect(select, options) {
187 103
188 104 this.$select = $(select);
189 - this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
190 -
105 +
191 106 // Placeholder via data attributes
192 107 if (this.$select.attr("data-placeholder")) {
193 - this.options.nonSelectedText = this.$select.data("placeholder");
108 + options.nonSelectedText = this.$select.data("placeholder");
194 109 }
110 +
111 + this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
195 112
196 113 // Initialization.
197 114 // We have to clone to create a new reference.
198 115 this.originalOptions = this.$select.clone()[0].options;
@@ -201,47 +118,29 @@
201 118 this.lastToggledInput = null;
202 119
203 120 this.options.multiple = this.$select.attr('multiple') === "multiple";
204 121 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 122 this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
208 123 this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
209 124 this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
210 125 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 -
126 +
214 127 // Build select all if enabled.
215 128 this.buildContainer();
216 129 this.buildButton();
217 130 this.buildDropdown();
218 - this.buildReset();
219 131 this.buildSelectAll();
220 132 this.buildDropdownOptions();
221 133 this.buildFilter();
222 - this.buildButtons();
223 134
224 135 this.updateButtonText();
225 - this.updateSelectAll(true);
136 + this.updateSelectAll();
226 137
227 - if (this.options.enableClickableOptGroups && this.options.multiple) {
228 - this.updateOptGroups();
138 + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
139 + this.disable();
229 140 }
230 -
231 - this.options.wasDisabled = this.$select.prop('disabled');
232 - if (this.options.disableIfEmpty && $('option', this.$select).length <= 0 && !this.options.wasDisabled) {
233 - this.disable(true);
234 - }
235 -
236 - this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container);
237 - this.$select.prop('tabindex', '-1');
238 -
239 - if (this.options.widthSynchronizationMode !== 'never') {
240 - this.synchronizeButtonAndPopupWidth();
241 - }
242 -
243 - this.options.onInitialized(this.$select, this.$container);
141 +
142 + this.$select.hide().after(this.$container);
244 143 }
245 144
246 145 Multiselect.prototype = {
247 146
@@ -249,55 +148,52 @@
249 148 /**
250 149 * Default text function will either print 'None selected' in case no
251 150 * option is selected or a list of the selected options up to a length
252 151 * of 3 selected options.
253 - *
152 + *
254 153 * @param {jQuery} options
255 154 * @param {jQuery} select
256 155 * @returns {String}
257 156 */
258 - buttonText: function (selectedOptions, select) {
259 - if (this.disabledText.length > 0 && select.prop('disabled')) {
260 - return this.disabledText;
261 - }
262 - else if (selectedOptions.length === 0) {
157 + buttonText: function(options, select) {
158 + if (options.length === 0) {
263 159 return this.nonSelectedText;
264 160 }
265 - else if (this.allSelectedText
266 - && selectedOptions.length === $('option', $(select)).length
267 - && $('option', $(select)).length !== 1
268 - && this.multiple) {
161 + else if (this.allSelectedText
162 + && options.length === $('option', $(select)).length
163 + && $('option', $(select)).length !== 1
164 + && this.multiple) {
269 165
270 166 if (this.selectAllNumber) {
271 - return this.allSelectedText + ' (' + selectedOptions.length + ')';
167 + return this.allSelectedText + ' (' + options.length + ')';
272 168 }
273 169 else {
274 170 return this.allSelectedText;
275 171 }
276 172 }
277 - else if (this.numberDisplayed != 0 && selectedOptions.length > this.numberDisplayed) {
278 - return selectedOptions.length + ' ' + this.nSelectedText;
173 + else if (options.length > this.numberDisplayed) {
174 + return options.length + ' ' + this.nSelectedText;
279 175 }
280 176 else {
281 177 var selected = '';
282 178 var delimiter = this.delimiterText;
283 -
284 - selectedOptions.each(function () {
179 +
180 + options.each(function() {
285 181 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
286 182 selected += label + delimiter;
287 183 });
288 -
289 - return selected.substr(0, selected.length - this.delimiterText.length);
184 +
185 + return selected.substr(0, selected.length - 2);
290 186 }
291 187 },
292 188 /**
293 189 * Updates the title of the button similar to the buttonText function.
294 - *
190 + *
295 191 * @param {jQuery} options
296 192 * @param {jQuery} select
297 193 * @returns {@exp;selected@call;substr}
298 194 */
299 - buttonTitle: function (options, select) {
195 + buttonTitle: function(options, select) {
300 196 if (options.length === 0) {
301 197 return this.nonSelectedText;
302 198 }
303 199 else {
@@ -302,19 +198,16 @@
302 198 }
303 199 else {
304 200 var selected = '';
305 201 var delimiter = this.delimiterText;
306 -
202 +
307 203 options.each(function () {
308 204 var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
309 205 selected += label + delimiter;
310 206 });
311 - return selected.substr(0, selected.length - this.delimiterText.length);
207 + return selected.substr(0, selected.length - 2);
312 208 }
313 209 },
314 - checkboxName: function (option) {
315 - return false; // no checkbox name
316 - },
317 210 /**
318 211 * Create a label.
319 212 *
320 213 * @param {jQuery} element
@@ -319,29 +212,20 @@
319 212 *
320 213 * @param {jQuery} element
321 214 * @returns {String}
322 215 */
323 - optionLabel: function (element) {
216 + optionLabel: function(element){
324 217 return $(element).attr('label') || $(element).text();
325 218 },
326 219 /**
327 - * Create a class.
328 - *
329 - * @param {jQuery} element
330 - * @returns {String}
331 - */
332 - optionClass: function (element) {
333 - return $(element).attr('class') || '';
334 - },
335 - /**
336 220 * Triggered on change of the multiselect.
337 - *
221 + *
338 222 * Not triggered when selecting/deselecting options manually.
339 - *
223 + *
340 224 * @param {jQuery} option
341 225 * @param {Boolean} checked
342 226 */
343 - onChange: function (option, checked) {
227 + onChange : function(option, checked) {
344 228
345 229 },
346 230 /**
347 231 * Triggered when the dropdown is shown.
@@ -347,9 +231,9 @@
347 231 * Triggered when the dropdown is shown.
348 232 *
349 233 * @param {jQuery} event
350 234 */
351 - onDropdownShow: function (event) {
235 + onDropdownShow: function(event) {
352 236
353 237 },
354 238 /**
355 239 * Triggered when the dropdown is hidden.
@@ -355,67 +239,44 @@
355 239 * Triggered when the dropdown is hidden.
356 240 *
357 241 * @param {jQuery} event
358 242 */
359 - onDropdownHide: function (event) {
243 + onDropdownHide: function(event) {
360 244
361 245 },
362 246 /**
363 247 * Triggered after the dropdown is shown.
364 - *
248 + *
365 249 * @param {jQuery} event
366 250 */
367 - onDropdownShown: function (event) {
368 -
251 + onDropdownShown: function(event) {
252 +
369 253 },
370 254 /**
371 255 * Triggered after the dropdown is hidden.
372 - *
256 + *
373 257 * @param {jQuery} event
374 258 */
375 - onDropdownHidden: function (event) {
376 -
259 + onDropdownHidden: function(event) {
260 +
377 261 },
378 262 /**
379 263 * Triggered on select all.
380 264 */
381 - onSelectAll: function () {
382 -
265 + onSelectAll: function() {
266 +
383 267 },
384 - /**
385 - * Triggered on deselect all.
386 - */
387 - onDeselectAll: function () {
388 -
389 - },
390 - /**
391 - * Triggered after initializing.
392 - *
393 - * @param {jQuery} $select
394 - * @param {jQuery} $container
395 - */
396 - onInitialized: function ($select, $container) {
397 -
398 - },
399 - /**
400 - * Triggered on filtering.
401 - *
402 - * @param {jQuery} $filter
403 - */
404 - onFiltering: function ($filter) {
405 -
406 - },
407 268 enableHTML: false,
408 - buttonClass: 'custom-select',
269 + buttonClass: 'btn btn-default',
409 270 inheritClass: false,
410 271 buttonWidth: 'auto',
411 272 buttonContainer: '<div class="btn-group" />',
412 273 dropRight: false,
413 - dropUp: false,
414 274 selectedClass: 'active',
415 275 // Maximum height of the dropdown menu.
416 276 // If maximum height is exceeded a scrollbar will be displayed.
417 277 maxHeight: false,
278 + checkboxName: false,
418 279 includeSelectAllOption: false,
419 280 includeSelectAllIfMoreThan: 0,
420 281 selectAllText: ' Select all',
421 282 selectAllValue: 'multiselect-all',
@@ -420,15 +281,11 @@
420 281 selectAllText: ' Select all',
421 282 selectAllValue: 'multiselect-all',
422 283 selectAllName: false,
423 284 selectAllNumber: true,
424 - selectAllJustVisible: true,
425 285 enableFiltering: false,
426 286 enableCaseInsensitiveFiltering: false,
427 - enableFullValueFiltering: false,
428 287 enableClickableOptGroups: false,
429 - enableCollapsibleOptGroups: false,
430 - collapseOptGroupsByDefault: false,
431 288 filterPlaceholder: 'Search',
432 289 // possible options: 'text', 'value', 'both'
433 290 filterBehavior: 'text',
434 291 includeFilterClearBtn: true,
@@ -435,31 +292,19 @@
435 292 preventInputChangeEvent: false,
436 293 nonSelectedText: 'None selected',
437 294 nSelectedText: 'selected',
438 295 allSelectedText: 'All selected',
439 - resetButtonText: 'Reset',
440 296 numberDisplayed: 3,
441 297 disableIfEmpty: false,
442 - disabledText: '',
443 298 delimiterText: ', ',
444 - includeResetOption: false,
445 - includeResetDivider: false,
446 - resetText: 'Reset',
447 - indentGroupOptions: true,
448 - // possible options: 'never', 'always', 'ifPopupIsSmaller', 'ifPopupIsWider'
449 - widthSynchronizationMode: 'never',
450 - buttonTextAlignment: 'center',
451 - enableResetButton: false,
452 299 templates: {
453 - button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span></button>',
454 - popupContainer: '<div class="multiselect-container dropdown-menu"></div>',
455 - filter: '<div class="multiselect-filter d-flex align-items-center"><i class="fas fa-sm fa-search text-muted"></i><input type="search" class="multiselect-search form-control" /></div>',
456 - buttonGroup: '<div class="multiselect-buttons btn-group" style="display:flex;"></div>',
457 - buttonGroupReset: '<button type="button" class="multiselect-reset btn btn-secondary btn-block"></button>',
458 - option: '<button type="button" class="multiselect-option dropdown-item"></button>',
459 - divider: '<div class="dropdown-divider"></div>',
460 - optionGroup: '<button type="button" class="multiselect-group dropdown-item"></button>',
461 - resetButton: '<div class="multiselect-reset text-center p-2"><button type="button" class="btn btn-sm btn-block btn-outline-secondary"></button></div>'
300 + button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
301 + ul: '<ul class="multiselect-container dropdown-menu"></ul>',
302 + filter: '<li class="multiselect-item filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text"></div></li>',
303 + 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>',
304 + li: '<li><a tabindex="0"><label></label></a></li>',
305 + divider: '<li class="multiselect-item divider"></li>',
306 + liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>'
462 307 }
463 308 },
464 309
465 310 constructor: Multiselect,
@@ -466,20 +311,11 @@
466 311
467 312 /**
468 313 * Builds the container of the multiselect.
469 314 */
470 - buildContainer: function () {
315 + buildContainer: function() {
471 316 this.$container = $(this.options.buttonContainer);
472 - if (this.options.widthSynchronizationMode !== 'never') {
473 - this.$container.on('show.bs.dropdown', $.proxy(function () {
474 - // the width needs to be synchronized again in case the width of the button changed in between
475 - this.synchronizeButtonAndPopupWidth();
476 - this.options.onDropdownShow();
477 - }, this));
478 - }
479 - else {
480 - this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
481 - }
317 + this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
482 318 this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
483 319 this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
484 320 this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
485 321 },
@@ -486,9 +322,9 @@
486 322
487 323 /**
488 324 * Builds the button of the multiselect.
489 325 */
490 - buildButton: function () {
326 + buildButton: function() {
491 327 this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
492 328 if (this.$select.attr('class') && this.options.inheritClass) {
493 329 this.$button.addClass(this.$select.attr('class'));
494 330 }
@@ -502,9 +338,11 @@
502 338
503 339 // Manually add button width if set.
504 340 if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
505 341 this.$button.css({
506 - 'width': '100%' //this.options.buttonWidth,
342 + 'width' : this.options.buttonWidth,
343 + 'overflow' : 'hidden',
344 + 'text-overflow' : 'ellipsis'
507 345 });
508 346 this.$container.css({
509 347 'width': this.options.buttonWidth
510 348 });
@@ -509,22 +347,8 @@
509 347 'width': this.options.buttonWidth
510 348 });
511 349 }
512 350
513 - if (this.options.buttonTextAlignment) {
514 - switch (this.options.buttonTextAlignment) {
515 - case 'left':
516 - this.$button.addClass('text-left');
517 - break;
518 - case 'center':
519 - this.$button.addClass('text-center');
520 - break;
521 - case 'right':
522 - this.$button.addClass('text-right');
523 - break;
524 - }
525 - }
526 -
527 351 // Keep the tab index from the select.
528 352 var tabindex = this.$select.attr('tabindex');
529 353 if (tabindex) {
530 354 this.$button.attr('tabindex', tabindex);
@@ -533,26 +357,23 @@
533 357 this.$container.prepend(this.$button);
534 358 },
535 359
536 360 /**
537 - * Builds the popup container representing the dropdown menu.
361 + * Builds the ul representing the dropdown menu.
538 362 */
539 - buildDropdown: function () {
363 + buildDropdown: function() {
540 364
541 - // Build popup container.
542 - this.$popupContainer = $(this.options.templates.popupContainer);
365 + // Build ul.
366 + this.$ul = $(this.options.templates.ul);
543 367
544 368 if (this.options.dropRight) {
545 - this.$container.addClass('dropright');
369 + this.$ul.addClass('pull-right');
546 370 }
547 - else if (this.options.dropUp) {
548 - this.$container.addClass("dropup");
549 - }
550 371
551 372 // Set max height of dropdown menu to activate auto scrollbar.
552 373 if (this.options.maxHeight) {
553 374 // TODO: Add a class for this option to move the css declarations.
554 - this.$popupContainer.css({
375 + this.$ul.css({
555 376 'max-height': this.options.maxHeight + 'px',
556 377 'overflow-y': 'auto',
557 378 'overflow-x': 'hidden'
558 379 });
@@ -557,53 +378,25 @@
557 378 'overflow-x': 'hidden'
558 379 });
559 380 }
560 381
561 - if (this.options.widthSynchronizationMode !== 'never') {
562 - this.$popupContainer.css('overflow-x', 'hidden');
563 - }
564 -
565 - this.$popupContainer.on("touchstart click", function (e) {
566 - e.stopPropagation();
567 - });
568 -
569 - this.$container.append(this.$popupContainer);
382 + this.$container.append(this.$ul);
570 383 },
571 384
572 - synchronizeButtonAndPopupWidth: function () {
573 - if (!this.$popupContainer || this.options.widthSynchronizationMode === 'never') {
574 - return;
575 - }
576 -
577 - var buttonWidth = this.$button.outerWidth();
578 - switch (this.options.widthSynchronizationMode) {
579 - case 'always':
580 - this.$popupContainer.css('min-width', buttonWidth);
581 - this.$popupContainer.css('max-width', buttonWidth);
582 - break;
583 - case 'ifPopupIsSmaller':
584 - this.$popupContainer.css('min-width', buttonWidth);
585 - break;
586 - case 'ifPopupIsWider':
587 - this.$popupContainer.css('max-width', buttonWidth);
588 - break;
589 - }
590 - },
591 -
592 385 /**
593 - * Build the dropdown options and binds all necessary events.
594 - *
386 + * Build the dropdown options and binds all nessecary events.
387 + *
595 388 * Uses createDivider and createOptionValue to create the necessary options.
596 389 */
597 - buildDropdownOptions: function () {
390 + buildDropdownOptions: function() {
598 391
599 - this.$select.children().each($.proxy(function (index, element) {
392 + this.$select.children().each($.proxy(function(index, element) {
600 393
601 394 var $element = $(element);
602 395 // Support optgroups and options without a group simultaneously.
603 396 var tag = $element.prop('tagName')
604 397 .toLowerCase();
605 -
398 +
606 399 if ($element.prop('value') === this.options.selectAllValue) {
607 400 return;
608 401 }
609 402
@@ -615,9 +408,9 @@
615 408 if ($element.data('role') === 'divider') {
616 409 this.createDivider();
617 410 }
618 411 else {
619 - this.createOptionValue(element, false);
412 + this.createOptionValue(element);
620 413 }
621 414
622 415 }
623 416
@@ -624,10 +417,9 @@
624 417 // Other illegal tags will be ignored.
625 418 }, this));
626 419
627 420 // Bind the change event on the dropdown elements.
628 - $(this.$popupContainer).off('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]');
629 - $(this.$popupContainer).on('change', '> *:not(.multiselect-group) input[type="checkbox"], > *:not(.multiselect-group) input[type="radio"]', $.proxy(function (event) {
421 + $('li input', this.$ul).on('change', $.proxy(function(event) {
630 422 var $target = $(event.target);
631 423
632 424 var checked = $target.prop('checked') || false;
633 425 var isSelectAllOption = $target.val() === this.options.selectAllValue;
@@ -634,13 +426,13 @@
634 426
635 427 // Apply or unapply the configured selected class.
636 428 if (this.options.selectedClass) {
637 429 if (checked) {
638 - $target.closest('.multiselect-option')
430 + $target.closest('li')
639 431 .addClass(this.options.selectedClass);
640 432 }
641 433 else {
642 - $target.closest('.multiselect-option')
434 + $target.closest('li')
643 435 .removeClass(this.options.selectedClass);
644 436 }
645 437 }
646 438
@@ -651,17 +443,17 @@
651 443 var $optionsNotThis = $('option', this.$select).not($option);
652 444 var $checkboxesNotThis = $('input', this.$container).not($target);
653 445
654 446 if (isSelectAllOption) {
655 -
656 447 if (checked) {
657 - this.selectAll(this.options.selectAllJustVisible, true);
448 + this.selectAll();
658 449 }
659 450 else {
660 - this.deselectAll(this.options.selectAllJustVisible, true);
451 + this.deselectAll();
661 452 }
662 453 }
663 - else {
454 +
455 + if(!isSelectAllOption){
664 456 if (checked) {
665 457 $option.prop('selected', true);
666 458
667 459 if (this.options.multiple) {
@@ -670,9 +462,9 @@
670 462 }
671 463 else {
672 464 // Unselect all other options and corresponding checkboxes.
673 465 if (this.options.selectedClass) {
674 - $($checkboxesNotThis).closest('.dropdown-item').removeClass(this.options.selectedClass);
466 + $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
675 467 }
676 468
677 469 $($checkboxesNotThis).prop('checked', false);
678 470 $optionsNotThis.prop('selected', false);
@@ -681,9 +473,9 @@
681 473 this.$button.click();
682 474 }
683 475
684 476 if (this.options.selectedClass === "active") {
685 - $optionsNotThis.closest(".dropdown-item").css("outline", "");
477 + $optionsNotThis.closest("a").css("outline", "");
686 478 }
687 479 }
688 480 else {
689 481 // Unselect option.
@@ -688,73 +480,65 @@
688 480 else {
689 481 // Unselect option.
690 482 $option.prop('selected', false);
691 483 }
692 -
693 - // To prevent select all from firing onChange: #575
694 - this.options.onChange($option, checked);
695 -
696 - // Do not update select all or optgroups on select all change!
697 - this.updateSelectAll();
698 -
699 - if (this.options.enableClickableOptGroups && this.options.multiple) {
700 - this.updateOptGroups();
701 - }
702 484 }
703 485
704 486 this.$select.change();
487 +
705 488 this.updateButtonText();
489 + this.updateSelectAll();
706 490
707 - if (this.options.preventInputChangeEvent) {
491 + this.options.onChange($option, checked);
492 +
493 + if(this.options.preventInputChangeEvent) {
708 494 return false;
709 495 }
710 496 }, this));
711 497
712 - $('.multiselect-option', this.$popupContainer).off('mousedown');
713 - $('.multiselect-option', this.$popupContainer).on('mousedown', function (e) {
498 + $('li a', this.$ul).on('mousedown', function(e) {
714 499 if (e.shiftKey) {
715 500 // Prevent selecting text by Shift+click
716 501 return false;
717 502 }
718 503 });
719 -
720 - $(this.$popupContainer).off('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group');
721 - $(this.$popupContainer).on('touchstart click', '.multiselect-option, .multiselect-all, .multiselect-group', $.proxy(function (event) {
504 +
505 + $('li a', this.$ul).on('touchstart click', $.proxy(function(event) {
722 506 event.stopPropagation();
723 507
724 508 var $target = $(event.target);
725 -
509 +
726 510 if (event.shiftKey && this.options.multiple) {
727 - if (!$target.is("input")) { // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
511 + if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
728 512 event.preventDefault();
729 - $target = $target.closest(".multiselect-option").find("input");
513 + $target = $target.find("input");
730 514 $target.prop("checked", !$target.prop("checked"));
731 515 }
732 516 var checked = $target.prop('checked') || false;
733 517
734 518 if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
735 - var from = this.$popupContainer.find(".multiselect-option:visible").index($target.closest(".multiselect-option"));
736 - var to = this.$popupContainer.find(".multiselect-option:visible").index(this.lastToggledInput.closest(".multiselect-option"));
737 -
519 + var from = $target.closest("li").index();
520 + var to = this.lastToggledInput.closest("li").index();
521 +
738 522 if (from > to) { // Swap the indices
739 523 var tmp = to;
740 524 to = from;
741 525 from = tmp;
742 526 }
743 -
527 +
744 528 // Make sure we grab all elements since slice excludes the last index
745 529 ++to;
746 -
530 +
747 531 // Change the checkboxes and underlying options
748 - var range = this.$popupContainer.find(".multiselect-option:not(.multiselect-filter-hidden)").slice(from, to).find("input");
749 -
532 + var range = this.$ul.find("li").slice(from, to).find("input");
533 +
750 534 range.prop('checked', checked);
751 -
535 +
752 536 if (this.options.selectedClass) {
753 - range.closest('.multiselect-option')
537 + range.closest('li')
754 538 .toggleClass(this.options.selectedClass, checked);
755 539 }
756 -
540 +
757 541 for (var i = 0, j = range.length; i < j; i++) {
758 542 var $checkbox = $(range[i]);
759 543
760 544 var $option = this.getOptionByValue($checkbox.val());
@@ -759,218 +543,96 @@
759 543
760 544 var $option = this.getOptionByValue($checkbox.val());
761 545
762 546 $option.prop('selected', checked);
763 - }
547 + }
764 548 }
765 -
549 +
766 550 // Trigger the select "change" event
767 551 $target.trigger("change");
768 552 }
769 - else if (!$target.is('input')) {
770 - var $checkbox = $target.closest('.multiselect-option, .multiselect-all').find('.form-check-input');
771 - if ($checkbox.length > 0) {
772 - if (this.options.multiple || !$checkbox.prop('checked')) {
773 - $checkbox.prop('checked', !$checkbox.prop('checked'));
774 - $checkbox.change();
775 - }
776 - }
777 - else if (this.options.enableClickableOptGroups && this.options.multiple && !$target.hasClass("caret-container")) {
778 - var groupItem = $target;
779 - if (!groupItem.hasClass("multiselect-group")) {
780 - groupItem = $target.closest('.multiselect-group');
781 - }
782 - $checkbox = groupItem.find(".form-check-input");
783 - if ($checkbox.length > 0) {
784 - $checkbox.prop('checked', !$checkbox.prop('checked'));
785 - $checkbox.change();
786 - }
787 - }
788 -
789 - event.preventDefault();
790 - }
791 -
553 +
792 554 // Remembers last clicked option
793 - var $input = $target.closest(".multiselect-option").find("input[type='checkbox'], input[type='radio']");
794 - if ($input.length > 0) {
555 + if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
795 556 this.lastToggledInput = $target;
796 557 }
797 - else {
798 - this.lastToggledInput = null;
799 - }
800 558
801 559 $target.blur();
802 560 }, this));
803 561
804 - //Keyboard support.
805 - this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function (event) {
806 - var $items = $(this.$container).find(".multiselect-option:not(.disabled), .multiselect-group:not(.disabled), .multiselect-all").filter(":visible");
807 - var index = $items.index($items.filter(':focus'));
808 - var $search = $('.multiselect-search', this.$container);
562 + // Keyboard support.
563 + this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
564 + if ($('input[type="text"]', this.$container).is(':focus')) {
565 + return;
566 + }
809 567
810 - // keyCode 9 == Tab
811 - if (event.keyCode === 9 && this.$container.hasClass('show')) {
568 + if (event.keyCode === 9 && this.$container.hasClass('open')) {
812 569 this.$button.click();
813 570 }
814 - // keyCode 13 = Enter
815 - else if (event.keyCode == 13) {
816 - var $current = $items.eq(index);
817 - setTimeout(function () {
818 - $current.focus();
819 - }, 1);
820 - }
821 - // keyCode 38 = Arrow Up
822 - else if (event.keyCode == 38) {
823 - if (index == 0 && !$search.is(':focus')) {
824 - setTimeout(function () {
825 - $search.focus();
826 - }, 1);
571 + else {
572 + var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
573 +
574 + if (!$items.length) {
575 + return;
827 576 }
828 - }
829 - // keyCode 40 = Arrow Down
830 - else if (event.keyCode == 40) {
831 - if ($search.is(':focus')) {
832 - var $first = $items.eq(0);
833 - setTimeout(function () {
834 - $search.blur();
835 - $first.focus();
836 - }, 1);
577 +
578 + var index = $items.index($items.filter(':focus'));
579 +
580 + // Navigation up.
581 + if (event.keyCode === 38 && index > 0) {
582 + index--;
837 583 }
838 - else if (index == -1) {
839 - setTimeout(function () {
840 - $search.focus();
841 - }, 1);
584 + // Navigate down.
585 + else if (event.keyCode === 40 && index < $items.length - 1) {
586 + index++;
842 587 }
843 - }
844 - }, this));
588 + else if (!~index) {
589 + index = 0;
590 + }
845 591
846 - if (this.options.enableClickableOptGroups && this.options.multiple) {
847 - $(".multiselect-group input", this.$popupContainer).off("change");
848 - $(".multiselect-group input", this.$popupContainer).on("change", $.proxy(function (event) {
849 - event.stopPropagation();
592 + var $current = $items.eq(index);
593 + $current.focus();
850 594
851 - var $target = $(event.target);
852 - var checked = $target.prop('checked') || false;
595 + if (event.keyCode === 32 || event.keyCode === 13) {
596 + var $checkbox = $current.find('input');
853 597
854 - var $item = $(event.target).closest('.dropdown-item');
855 - var $group = $item.nextUntil(".multiselect-group")
856 - .not('.multiselect-filter-hidden')
857 - .not('.disabled');
858 -
859 - var $inputs = $group.find("input");
860 -
861 - var $options = [];
862 -
863 - if (this.options.selectedClass) {
864 - if (checked) {
865 - $item.addClass(this.options.selectedClass);
866 - }
867 - else {
868 - $item.removeClass(this.options.selectedClass);
869 - }
598 + $checkbox.prop("checked", !$checkbox.prop("checked"));
599 + $checkbox.change();
870 600 }
871 601
872 - $.each($inputs, $.proxy(function (index, input) {
873 - var $input = $(input);
874 - var value = $input.val();
875 - var $option = this.getOptionByValue(value);
602 + event.stopPropagation();
603 + event.preventDefault();
604 + }
605 + }, this));
876 606
877 - if (checked) {
878 - $input.prop('checked', true);
879 - $input.closest('.dropdown-item')
880 - .addClass(this.options.selectedClass);
607 + if(this.options.enableClickableOptGroups && this.options.multiple) {
608 + $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) {
609 + event.stopPropagation();
881 610
882 - $option.prop('selected', true);
883 - }
884 - else {
885 - $input.prop('checked', false);
886 - $input.closest('.dropdown-item')
887 - .removeClass(this.options.selectedClass);
611 + var group = $(event.target).parent();
888 612
889 - $option.prop('selected', false);
890 - }
613 + // Search all option in optgroup
614 + var $options = group.nextUntil('li.multiselect-group');
615 + var $visibleOptions = $options.filter(":visible:not(.disabled)");
891 616
892 - $options.push(this.getOptionByValue(value));
893 - }, this))
894 -
895 - // Cannot use select or deselect here because it would call updateOptGroups again.
896 -
897 - this.options.onChange($options, checked);
898 -
899 - this.$select.change();
900 - this.updateButtonText();
901 - this.updateSelectAll();
902 - }, this));
903 - }
904 -
905 - if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
906 - $(".multiselect-group .caret-container", this.$popupContainer).off("click");
907 - $(".multiselect-group .caret-container", this.$popupContainer).on("click", $.proxy(function (event) {
908 - var $group = $(event.target).closest('.multiselect-group');
909 - var $inputs = $group.nextUntil(".multiselect-group")
910 - .not('.multiselect-filter-hidden');
911 -
912 - var visible = true;
913 - $inputs.each(function () {
914 - visible = visible && !$(this).hasClass('multiselect-collapsible-hidden');
617 + // check or uncheck items
618 + var allChecked = true;
619 + var optionInputs = $visibleOptions.find('input');
620 + optionInputs.each(function() {
621 + allChecked = allChecked && $(this).prop('checked');
915 622 });
916 623
917 - if (visible) {
918 - $inputs.hide()
919 - .addClass('multiselect-collapsible-hidden');
920 - }
921 - else {
922 - $inputs.show()
923 - .removeClass('multiselect-collapsible-hidden');
924 - }
925 - }, this));
624 + optionInputs.prop('checked', !allChecked).trigger('change');
625 + }, this));
926 626 }
927 627 },
928 628
929 629 /**
930 - * Create a checkbox container with input and label based on given values
931 - * @param {JQuery} $item
932 - * @param {String} label
933 - * @param {String} name
934 - * @param {String} value
935 - * @param {String} inputType
936 - * @returns {JQuery}
937 - */
938 - createCheckbox: function ($item, labelContent, name, value, title, inputType) {
939 - var $wrapper = $('<span />');
940 - $wrapper.addClass("form-check");
941 -
942 - if (this.options.enableHTML && $(labelContent).length > 0) {
943 - var $checkboxLabel = $('<label class="form-check-label" />');
944 - $checkboxLabel.html(labelContent);
945 - $wrapper.append($checkboxLabel);
946 - }
947 - else {
948 - var $checkboxLabel = $('<label class="form-check-label" />');
949 - $checkboxLabel.text(labelContent);
950 - $wrapper.append($checkboxLabel);
951 - }
952 -
953 - var $checkbox = $('<input class="form-check-input"/>').attr('type', inputType);
954 - $checkbox.val(value);
955 - $wrapper.prepend($checkbox);
956 -
957 - if (name) {
958 - $checkbox.attr('name', name);
959 - }
960 -
961 - $item.prepend($wrapper);
962 - $item.attr("title", title || labelContent);
963 -
964 - return $checkbox;
965 - },
966 -
967 - /**
968 630 * Create an option using the given select option.
969 631 *
970 632 * @param {jQuery} element
971 633 */
972 - createOptionValue: function (element, isGroupOption) {
634 + createOptionValue: function(element) {
973 635 var $element = $(element);
974 636 if ($element.is(':selected')) {
975 637 $element.prop('selected', true);
976 638 }
@@ -976,44 +638,48 @@
976 638 }
977 639
978 640 // Support the label attribute on options.
979 641 var label = this.options.optionLabel(element);
980 - var classes = this.options.optionClass(element);
981 642 var value = $element.val();
982 643 var inputType = this.options.multiple ? "checkbox" : "radio";
983 - var title = $element.attr('title');
984 644
985 - var $option = $(this.options.templates.option);
986 - $option.addClass(classes);
645 + var $li = $(this.options.templates.li);
646 + var $label = $('label', $li);
647 + $label.addClass(inputType);
987 648
988 - if (isGroupOption && this.options.indentGroupOptions) {
989 - $option.addClass("multiselect-group-option-indented")
649 + if (this.options.enableHTML) {
650 + $label.html(" " + label);
990 651 }
652 + else {
653 + $label.text(" " + label);
654 + }
655 +
656 + var $checkbox = $('<input/>').attr('type', inputType);
991 657
992 - // Hide all children items when collapseOptGroupsByDefault is true
993 - if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
994 - $option.addClass("multiselect-collapsible-hidden");
995 - $option.hide();
658 + if (this.options.checkboxName) {
659 + $checkbox.attr('name', this.options.checkboxName);
996 660 }
661 + $label.prepend($checkbox);
997 662
998 - var name = this.options.checkboxName($element);
999 - var $checkbox = this.createCheckbox($option, label, name, value, title, inputType);
1000 -
1001 663 var selected = $element.prop('selected') || false;
664 + $checkbox.val(value);
1002 665
1003 666 if (value === this.options.selectAllValue) {
1004 - $option.addClass("multiselect-all");
1005 - $option.removeClass("multiselect-option");
667 + $li.addClass("multiselect-item multiselect-all");
1006 668 $checkbox.parent().parent()
1007 669 .addClass('multiselect-all');
1008 670 }
1009 671
1010 - this.$popupContainer.append($option);
672 + $label.attr('title', $element.attr('title'));
1011 673
674 + this.$ul.append($li);
675 +
1012 676 if ($element.is(':disabled')) {
1013 677 $checkbox.attr('disabled', 'disabled')
1014 678 .prop('disabled', true)
1015 - .closest('.dropdown-item')
679 + .closest('a')
680 + .attr("tabindex", "-1")
681 + .closest('li')
1016 682 .addClass('disabled');
1017 683 }
1018 684
1019 685 $checkbox.prop('checked', selected);
@@ -1018,9 +684,9 @@
1018 684
1019 685 $checkbox.prop('checked', selected);
1020 686
1021 687 if (selected && this.options.selectedClass) {
1022 - $checkbox.closest('.dropdown-item')
688 + $checkbox.closest('li')
1023 689 .addClass(this.options.selectedClass);
1024 690 }
1025 691 },
1026 692
@@ -1028,11 +694,11 @@
1028 694 * Creates a divider using the given select option.
1029 695 *
1030 696 * @param {jQuery} element
1031 697 */
1032 - createDivider: function (element) {
698 + createDivider: function(element) {
1033 699 var $divider = $(this.options.templates.divider);
1034 - this.$popupContainer.append($divider);
700 + this.$ul.append($divider);
1035 701 },
1036 702
1037 703 /**
1038 704 * Creates an optgroup.
@@ -1038,107 +704,82 @@
1038 704 * Creates an optgroup.
1039 705 *
1040 706 * @param {jQuery} group
1041 707 */
1042 - createOptgroup: function (group) {
1043 - var $group = $(group);
1044 - var label = $group.attr("label");
1045 - var value = $group.attr("value");
1046 - var title = $group.attr('title');
708 + createOptgroup: function(group) {
709 + var groupName = $(group).prop('label');
1047 710
1048 - var $groupOption = $("<span class='multiselect-group dropdown-item-text'></span>");
1049 -
1050 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1051 - $groupOption = $(this.options.templates.optionGroup);
1052 - var $checkbox = this.createCheckbox($groupOption, label, null, value, title, "checkbox");
711 + // Add a header for the group.
712 + var $li = $(this.options.templates.liGroup);
713 +
714 + if (this.options.enableHTML) {
715 + $('label', $li).html(groupName);
1053 716 }
1054 717 else {
1055 - if (this.options.enableHTML) {
1056 - $groupOption.html(" " + label);
1057 - }
1058 - else {
1059 - $groupOption.text(" " + label);
1060 - }
718 + $('label', $li).text(groupName);
1061 719 }
720 +
721 + if (this.options.enableClickableOptGroups) {
722 + $li.addClass('multiselect-group-clickable');
723 + }
1062 724
1063 - var classes = this.options.optionClass(group);
1064 - $groupOption.addClass(classes);
725 + this.$ul.append($li);
1065 726
1066 - if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
1067 - $groupOption.find('.form-check').addClass('d-inline-block');
1068 - $groupOption.append('<span class="caret-container dropdown-toggle pl-1"></span>');
727 + if ($(group).is(':disabled')) {
728 + $li.addClass('disabled');
1069 729 }
1070 730
1071 - if ($group.is(':disabled')) {
1072 - $groupOption.addClass('disabled');
1073 - }
1074 -
1075 - this.$popupContainer.append($groupOption);
1076 -
1077 - $("option", group).each($.proxy(function ($, group) {
1078 - this.createOptionValue(group, true);
731 + // Add the options of the group.
732 + $('option', group).each($.proxy(function(index, element) {
733 + this.createOptionValue(element);
1079 734 }, this));
1080 735 },
1081 736
1082 737 /**
1083 - * Build the reset.
1084 - *
1085 - */
1086 - buildReset: function () {
1087 - if (this.options.includeResetOption) {
1088 -
1089 - // Check whether to add a divider after the reset.
1090 - if (this.options.includeResetDivider) {
1091 - var divider = $(this.options.templates.divider);
1092 - divider.addClass("mt-0");
1093 - this.$popupContainer.prepend(divider);
1094 - }
1095 -
1096 - var $resetButton = $(this.options.templates.resetButton);
1097 -
1098 - if (this.options.enableHTML) {
1099 - $('button', $resetButton).html(this.options.resetText);
1100 - }
1101 - else {
1102 - $('button', $resetButton).text(this.options.resetText);
1103 - }
1104 -
1105 - $('button', $resetButton).click($.proxy(function () {
1106 - this.clearSelection();
1107 - }, this));
1108 -
1109 - this.$popupContainer.prepend($resetButton);
1110 - }
1111 - },
1112 -
1113 - /**
1114 - * Build the select all.
1115 - *
738 + * Build the selct all.
739 + *
1116 740 * Checks if a select all has already been created.
1117 741 */
1118 - buildSelectAll: function () {
742 + buildSelectAll: function() {
1119 743 if (typeof this.options.selectAllValue === 'number') {
1120 744 this.options.selectAllValue = this.options.selectAllValue.toString();
1121 745 }
1122 -
746 +
1123 747 var alreadyHasSelectAll = this.hasSelectAll();
1124 748
1125 749 if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
1126 - && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
750 + && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
1127 751
1128 752 // Check whether to add a divider after the select all.
1129 753 if (this.options.includeSelectAllDivider) {
1130 - this.$popupContainer.prepend($(this.options.templates.divider));
754 + this.$ul.prepend($(this.options.templates.divider));
1131 755 }
1132 756
1133 - var $option = $(this.options.templates.li || this.options.templates.option);
1134 - var $checkbox = this.createCheckbox($option, this.options.selectAllText, this.options.selectAllName, this.options.selectAllValue, this.options.selectAllText, "checkbox");
757 + var $li = $(this.options.templates.li);
758 + $('label', $li).addClass("checkbox");
759 +
760 + if (this.options.enableHTML) {
761 + $('label', $li).html(" " + this.options.selectAllText);
762 + }
763 + else {
764 + $('label', $li).text(" " + this.options.selectAllText);
765 + }
766 +
767 + if (this.options.selectAllName) {
768 + $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
769 + }
770 + else {
771 + $('label', $li).prepend('<input type="checkbox" />');
772 + }
773 +
774 + var $checkbox = $('input', $li);
775 + $checkbox.val(this.options.selectAllValue);
1135 776
1136 - $option.addClass("multiselect-all");
1137 - $option.removeClass("multiselect-option");
1138 - $option.find(".form-check-label").addClass("font-weight-bold");
777 + $li.addClass("multiselect-item multiselect-all");
778 + $checkbox.parent().parent()
779 + .addClass('multiselect-all');
1139 780
1140 - this.$popupContainer.prepend($option);
781 + this.$ul.prepend($li);
1141 782
1142 783 $checkbox.prop('checked', false);
1143 784 }
1144 785 },
@@ -1145,9 +786,9 @@
1145 786
1146 787 /**
1147 788 * Builds the filter.
1148 789 */
1149 - buildFilter: function () {
790 + buildFilter: function() {
1150 791
1151 792 // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
1152 793 if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
1153 794 var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
@@ -1155,69 +796,43 @@
1155 796 if (this.$select.find('option').length >= enableFilterLength) {
1156 797
1157 798 this.$filter = $(this.options.templates.filter);
1158 799 $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
1159 -
1160 - // Handles optional filter clear button
1161 - if (!this.options.includeFilterClearBtn) {
1162 - this.$filter.find(".multiselect-search").attr("type", "text");
1163 -
1164 - // Remove clear button if the old design of the filter with input groups and separated clear button is used
1165 - this.$filter.find(".multiselect-clear-filter").remove();
1166 - }
1167 - else {
1168 - // Firefox does not support a clear button in search inputs right now therefore it must be added manually
1169 - if (this.isFirefox() && this.$filter.find(".multiselect-clear-filter").length === 0) {
1170 - this.$filter.append("<i class='fas fa-times text-muted multiselect-clear-filter multiselect-moz-clear-filter'></i>");
1171 - }
1172 -
1173 - this.$filter.find(".multiselect-clear-filter").on('click', $.proxy(function (event) {
800 +
801 + // Adds optional filter clear button
802 + if(this.options.includeFilterClearBtn){
803 + var clearBtn = $(this.options.templates.filterClearBtn);
804 + clearBtn.on('click', $.proxy(function(event){
1174 805 clearTimeout(this.searchTimeout);
1175 -
1176 - this.query = '';
1177 806 this.$filter.find('.multiselect-search').val('');
1178 - $('.dropdown-item', this.$popupContainer).show().removeClass('multiselect-filter-hidden');
1179 -
807 + $('li', this.$ul).show().removeClass("filter-hidden");
1180 808 this.updateSelectAll();
1181 -
1182 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1183 - this.updateOptGroups();
1184 - }
1185 -
1186 809 }, this));
810 + this.$filter.find('.input-group').append(clearBtn);
1187 811 }
812 +
813 + this.$ul.prepend(this.$filter);
1188 814
1189 - this.$popupContainer.prepend(this.$filter);
1190 -
1191 - this.$filter.val(this.query).on('click', function (event) {
815 + this.$filter.val(this.query).on('click', function(event) {
1192 816 event.stopPropagation();
1193 - }).on('input keydown', $.proxy(function (event) {
817 + }).on('input keydown', $.proxy(function(event) {
1194 818 // Cancel enter key default behaviour
1195 819 if (event.which === 13) {
1196 - event.preventDefault();
820 + event.preventDefault();
1197 821 }
1198 -
1199 - if (this.isFirefox() && this.options.includeFilterClearBtn) {
1200 - if (event.target.value) {
1201 - this.$filter.find(".multiselect-moz-clear-filter").show();
1202 - }
1203 - else {
1204 - this.$filter.find(".multiselect-moz-clear-filter").hide();
1205 - }
1206 - }
1207 -
822 +
1208 823 // This is useful to catch "keydown" events after the browser has updated the control.
1209 824 clearTimeout(this.searchTimeout);
1210 825
1211 - this.searchTimeout = this.asyncFunction($.proxy(function () {
826 + this.searchTimeout = this.asyncFunction($.proxy(function() {
1212 827
1213 828 if (this.query !== event.target.value) {
1214 829 this.query = event.target.value;
1215 830
1216 831 var currentGroup, currentGroupVisible;
1217 - $.each($('.multiselect-option, .multiselect-group', this.$popupContainer), $.proxy(function (index, element) {
832 + $.each($('li', this.$ul), $.proxy(function(index, element) {
1218 833 var value = $('input', element).length > 0 ? $('input', element).val() : "";
1219 - var text = $('.form-check-label', element).text();
834 + var text = $('label', element).text();
1220 835
1221 836 var filterCandidate = '';
1222 837 if ((this.options.filterBehavior === 'text')) {
1223 838 filterCandidate = text;
@@ -1229,38 +844,22 @@
1229 844 filterCandidate = text + '\n' + value;
1230 845 }
1231 846
1232 847 if (value !== this.options.selectAllValue && text) {
1233 -
1234 848 // By default lets assume that element is not
1235 849 // interesting for this search.
1236 850 var showElement = false;
1237 851
1238 - if (this.options.enableCaseInsensitiveFiltering) {
1239 - filterCandidate = filterCandidate.toLowerCase();
1240 - this.query = this.query.toLowerCase();
852 + if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
853 + showElement = true;
1241 854 }
1242 -
1243 - if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') {
1244 - var valueToMatch = filterCandidate.trim().substring(0, this.query.length);
1245 - if (this.query.indexOf(valueToMatch) > -1) {
1246 - showElement = true;
1247 - }
1248 - }
1249 855 else if (filterCandidate.indexOf(this.query) > -1) {
1250 856 showElement = true;
1251 857 }
1252 858
1253 859 // Toggle current element (group or group item) according to showElement boolean.
1254 - if (!showElement) {
1255 - $(element).css('display', 'none');
1256 - $(element).addClass('multiselect-filter-hidden');
1257 - }
1258 - if (showElement) {
1259 - $(element).css('display', 'block');
1260 - $(element).removeClass('multiselect-filter-hidden');
1261 - }
1262 -
860 + $(element).toggle(showElement).toggleClass('filter-hidden', !showElement);
861 +
1263 862 // Differentiate groups and group items.
1264 863 if ($(element).hasClass('multiselect-group')) {
1265 864 // Remember group status.
1266 865 currentGroup = element;
@@ -1268,16 +867,14 @@
1268 867 }
1269 868 else {
1270 869 // Show group name when at least one of its items is visible.
1271 870 if (showElement) {
1272 - $(currentGroup).show()
1273 - .removeClass('multiselect-filter-hidden');
871 + $(currentGroup).show().removeClass('filter-hidden');
1274 872 }
1275 -
873 +
1276 874 // Show all group items when group name satisfies filter.
1277 875 if (!showElement && currentGroupVisible) {
1278 - $(element).show()
1279 - .removeClass('multiselect-filter-hidden');
876 + $(element).show().removeClass('filter-hidden');
1280 877 }
1281 878 }
1282 879 }
1283 880 }, this));
@@ -1283,17 +880,8 @@
1283 880 }, this));
1284 881 }
1285 882
1286 883 this.updateSelectAll();
1287 -
1288 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1289 - this.updateOptGroups();
1290 - }
1291 -
1292 - this.updatePopupPosition();
1293 -
1294 - this.options.onFiltering(event.target);
1295 -
1296 884 }, this), 300, this);
1297 885 }, this));
1298 886 }
1299 887 }
@@ -1299,73 +887,13 @@
1299 887 }
1300 888 },
1301 889
1302 890 /**
1303 - * Builds the filter.
1304 - */
1305 - buildButtons: function () {
1306 - if (this.options.enableResetButton) {
1307 - var $buttonGroup = $(this.options.templates.buttonGroup);
1308 - this.$buttonGroupReset = $(this.options.templates.buttonGroupReset).text(this.options.resetButtonText);
1309 - $buttonGroup.append(this.$buttonGroupReset);
1310 - this.$popupContainer.prepend($buttonGroup);
1311 -
1312 - // We save all options that were previously selected.
1313 - this.defaultSelection = {};
1314 - $('option', this.$select).each($.proxy(function(index, element) {
1315 - var $option = $(element);
1316 - this.defaultSelection[$option.val()] = $option.prop('selected');
1317 - }, this));
1318 -
1319 - this.$buttonGroupReset.on('click', $.proxy(function(event) {
1320 - $('option', this.$select).each($.proxy(function(index, element) {
1321 - var $option = $(element);
1322 - $option.prop('selected', this.defaultSelection[$option.val()]);
1323 - }, this));
1324 - this.refresh();
1325 -
1326 - if (this.options.enableFiltering) {
1327 - this.$filter.trigger('keydown');
1328 - $('input', this.$filter).val('');
1329 - }
1330 - }, this));
1331 - }
1332 - },
1333 -
1334 - updatePopupPosition: function() {
1335 - // prevent gaps between popup and select when filter is used (#1199)
1336 - var transformMatrix = this.$popupContainer.css("transform");
1337 - var matrixType = transformMatrix.substring(0, transformMatrix.indexOf('('));
1338 - var values = transformMatrix.substring(transformMatrix.indexOf('(') + 1, transformMatrix.length - 1);
1339 - var valuesArray = values.split(',');
1340 -
1341 - var valueIndex = 5;
1342 - if(matrixType === "matrix3d") {
1343 - valueIndex = 13;
1344 - }
1345 -
1346 - var yTransformation = valuesArray[valueIndex];
1347 - // Need to check to avoid errors when testing and in some other situations.
1348 - yTransformation = typeof yTransformation === 'undefined' ? 0 : yTransformation.trim();
1349 - if (yTransformation < 0) {
1350 - yTransformation = this.$popupContainer.css("height").replace('px', '') * -1;
1351 - valuesArray[valueIndex] = yTransformation;
1352 - transformMatrix = matrixType + '(' + valuesArray.join(',') + ')';
1353 - this.$popupContainer.css("transform", transformMatrix);
1354 - }
1355 - },
1356 -
1357 - /**
1358 891 * Unbinds the whole plugin.
1359 892 */
1360 - destroy: function () {
893 + destroy: function() {
1361 894 this.$container.remove();
1362 - this.$select.unwrap();
1363 895 this.$select.show();
1364 -
1365 - // reset original state
1366 - this.$select.prop('disabled', this.options.wasDisabled);
1367 -
1368 896 this.$select.data('multiselect', null);
1369 897 },
1370 898
1371 899 /**
@@ -1370,23 +898,19 @@
1370 898
1371 899 /**
1372 900 * Refreshs the multiselect based on the selected options of the select.
1373 901 */
1374 - refresh: function () {
1375 - var inputs = {};
1376 - $('.multiselect-option input', this.$popupContainer).each(function () {
1377 - inputs[$(this).val()] = $(this);
1378 - });
902 + refresh: function() {
903 + $('option', this.$select).each($.proxy(function(index, element) {
904 + var $input = $('li input', this.$ul).filter(function() {
905 + return $(this).val() === $(element).val();
906 + });
1379 907
1380 - $('option', this.$select).each($.proxy(function (index, element) {
1381 - var $elem = $(element);
1382 - var $input = inputs[$(element).val()];
1383 -
1384 - if ($elem.is(':selected')) {
908 + if ($(element).is(':selected')) {
1385 909 $input.prop('checked', true);
1386 910
1387 911 if (this.options.selectedClass) {
1388 - $input.closest('.multiselect-option')
912 + $input.closest('li')
1389 913 .addClass(this.options.selectedClass);
1390 914 }
1391 915 }
1392 916 else {
@@ -1392,22 +916,22 @@
1392 916 else {
1393 917 $input.prop('checked', false);
1394 918
1395 919 if (this.options.selectedClass) {
1396 - $input.closest('.multiselect-option')
920 + $input.closest('li')
1397 921 .removeClass(this.options.selectedClass);
1398 922 }
1399 923 }
1400 924
1401 - if ($elem.is(":disabled")) {
925 + if ($(element).is(":disabled")) {
1402 926 $input.attr('disabled', 'disabled')
1403 927 .prop('disabled', true)
1404 - .closest('.multiselect-option')
928 + .closest('li')
1405 929 .addClass('disabled');
1406 930 }
1407 931 else {
1408 932 $input.prop('disabled', false)
1409 - .closest('.multiselect-option')
933 + .closest('li')
1410 934 .removeClass('disabled');
1411 935 }
1412 936 }, this));
1413 937
@@ -1412,25 +936,21 @@
1412 936 }, this));
1413 937
1414 938 this.updateButtonText();
1415 939 this.updateSelectAll();
1416 -
1417 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1418 - this.updateOptGroups();
1419 - }
1420 940 },
1421 941
1422 942 /**
1423 943 * Select all options of the given values.
1424 - *
944 + *
1425 945 * If triggerOnChange is set to true, the on change event is triggered if
1426 946 * and only if one value is passed.
1427 - *
947 + *
1428 948 * @param {Array} selectValues
1429 949 * @param {Boolean} triggerOnChange
1430 950 */
1431 - select: function (selectValues, triggerOnChange) {
1432 - if (!$.isArray(selectValues)) {
951 + select: function(selectValues, triggerOnChange) {
952 + if(!$.isArray(selectValues)) {
1433 953 selectValues = [selectValues];
1434 954 }
1435 955
1436 956 for (var i = 0; i < selectValues.length; i++) {
@@ -1442,29 +962,24 @@
1442 962
1443 963 var $option = this.getOptionByValue(value);
1444 964 var $checkbox = this.getInputByValue(value);
1445 965
1446 - if ($option === undefined || $checkbox === undefined) {
966 + if($option === undefined || $checkbox === undefined) {
1447 967 continue;
1448 968 }
1449 -
969 +
970 + if (!this.options.multiple) {
971 + this.deselectAll(false);
972 + }
973 +
1450 974 if (this.options.selectedClass) {
1451 - $checkbox.closest('.dropdown-item')
975 + $checkbox.closest('li')
1452 976 .addClass(this.options.selectedClass);
1453 977 }
1454 978
1455 979 $checkbox.prop('checked', true);
1456 980 $option.prop('selected', true);
1457 -
1458 - if (!this.options.multiple) {
1459 - var $checkboxesNotThis = $('input', this.$container).not($checkbox);
1460 - $($checkboxesNotThis).prop('checked', false);
1461 - $($checkboxesNotThis).closest('.multiselect-option').removeClass("active")
1462 -
1463 - var $optionsNotThis = $('option', this.$select).not($option);
1464 - $optionsNotThis.prop('selected', false);
1465 - }
1466 -
981 +
1467 982 if (triggerOnChange) {
1468 983 this.options.onChange($option, true);
1469 984 }
1470 985 }
@@ -1470,12 +985,8 @@
1470 985 }
1471 986
1472 987 this.updateButtonText();
1473 988 this.updateSelectAll();
1474 -
1475 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1476 - this.updateOptGroups();
1477 - }
1478 989 },
1479 990
1480 991 /**
1481 992 * Clears all selected items.
@@ -1483,30 +994,21 @@
1483 994 clearSelection: function () {
1484 995 this.deselectAll(false);
1485 996 this.updateButtonText();
1486 997 this.updateSelectAll();
1487 -
1488 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1489 - this.updateOptGroups();
1490 - }
1491 998 },
1492 999
1493 1000 /**
1494 1001 * Deselects all options of the given values.
1495 - *
1002 + *
1496 1003 * If triggerOnChange is set to true, the on change event is triggered, if
1497 1004 * and only if one value is passed.
1498 - *
1005 + *
1499 1006 * @param {Array} deselectValues
1500 1007 * @param {Boolean} triggerOnChange
1501 1008 */
1502 - deselect: function (deselectValues, triggerOnChange) {
1503 - if (!this.options.multiple) {
1504 - // In single selection mode at least on option needs to be selected
1505 - return;
1506 - }
1507 -
1508 - if (!$.isArray(deselectValues)) {
1009 + deselect: function(deselectValues, triggerOnChange) {
1010 + if(!$.isArray(deselectValues)) {
1509 1011 deselectValues = [deselectValues];
1510 1012 }
1511 1013
1512 1014 for (var i = 0; i < deselectValues.length; i++) {
@@ -1518,20 +1020,20 @@
1518 1020
1519 1021 var $option = this.getOptionByValue(value);
1520 1022 var $checkbox = this.getInputByValue(value);
1521 1023
1522 - if ($option === undefined || $checkbox === undefined) {
1024 + if($option === undefined || $checkbox === undefined) {
1523 1025 continue;
1524 1026 }
1525 1027
1526 1028 if (this.options.selectedClass) {
1527 - $checkbox.closest('.dropdown-item')
1029 + $checkbox.closest('li')
1528 1030 .removeClass(this.options.selectedClass);
1529 1031 }
1530 1032
1531 1033 $checkbox.prop('checked', false);
1532 1034 $option.prop('selected', false);
1533 -
1035 +
1534 1036 if (triggerOnChange) {
1535 1037 this.options.onChange($option, false);
1536 1038 }
1537 1039 }
@@ -1537,14 +1039,10 @@
1537 1039 }
1538 1040
1539 1041 this.updateButtonText();
1540 1042 this.updateSelectAll();
1541 -
1542 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1543 - this.updateOptGroups();
1544 - }
1545 1043 },
1546 -
1044 +
1547 1045 /**
1548 1046 * Selects all enabled & visible options.
1549 1047 *
1550 1048 * If justVisible is true or not specified, only visible options are selected.
@@ -1552,127 +1050,84 @@
1552 1050 * @param {Boolean} justVisible
1553 1051 * @param {Boolean} triggerOnSelectAll
1554 1052 */
1555 1053 selectAll: function (justVisible, triggerOnSelectAll) {
1556 - if (!this.options.multiple) {
1557 - // In single selection mode only one option can be selected at a time
1558 - return;
1559 - }
1560 -
1561 - // Record all changes, i.e., options selected that were not selected before.
1562 - var selected = [];
1563 1054 var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1564 -
1565 - if (justVisible) {
1566 - var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer);
1567 - $('input:enabled', visibleOptions).prop('checked', true);
1568 - visibleOptions.addClass(this.options.selectedClass);
1569 -
1570 - $('input:enabled', visibleOptions).each($.proxy(function (index, element) {
1571 - var value = $(element).val();
1572 - var option = this.getOptionByValue(value);
1573 - if (!$(option).prop('selected')) {
1574 - selected.push(option);
1575 - }
1576 - $(option).prop('selected', true);
1577 - }, this));
1055 + var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul);
1056 + var visibleCheckboxes = allCheckboxes.filter(":visible");
1057 + var allCheckboxesCount = allCheckboxes.length;
1058 + var visibleCheckboxesCount = visibleCheckboxes.length;
1059 +
1060 + if(justVisible) {
1061 + visibleCheckboxes.prop('checked', true);
1062 + $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass);
1578 1063 }
1579 1064 else {
1580 - var allOptions = $(".multiselect-option:not(.disabled)", this.$popupContainer);
1581 - $('input:enabled', allOptions).prop('checked', true);
1582 - allOptions.addClass(this.options.selectedClass);
1583 -
1584 - $('input:enabled', allOptions).each($.proxy(function (index, element) {
1585 - var value = $(element).val();
1586 - var option = this.getOptionByValue(value);
1587 - if (!$(option).prop('selected')) {
1588 - selected.push(option);
1589 - }
1590 - $(option).prop('selected', true);
1591 - }, this));
1065 + allCheckboxes.prop('checked', true);
1066 + $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass);
1592 1067 }
1593 -
1594 - $('.multiselect-option input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', true);
1595 -
1596 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1597 - this.updateOptGroups();
1068 +
1069 + if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) {
1070 + $("option:enabled", this.$select).prop('selected', true);
1598 1071 }
1599 -
1600 - this.updateButtonText();
1601 - this.updateSelectAll();
1602 -
1072 + else {
1073 + var values = visibleCheckboxes.map(function() {
1074 + return $(this).val();
1075 + }).get();
1076 +
1077 + $("option:enabled", this.$select).filter(function(index) {
1078 + return $.inArray($(this).val(), values) !== -1;
1079 + }).prop('selected', true);
1080 + }
1081 +
1603 1082 if (triggerOnSelectAll) {
1604 - this.options.onSelectAll(selected);
1083 + this.options.onSelectAll();
1605 1084 }
1606 1085 },
1607 1086
1608 1087 /**
1609 1088 * Deselects all options.
1610 - *
1089 + *
1611 1090 * If justVisible is true or not specified, only visible options are deselected.
1612 - *
1091 + *
1613 1092 * @param {Boolean} justVisible
1614 1093 */
1615 - deselectAll: function (justVisible, triggerOnDeselectAll) {
1616 - if (!this.options.multiple) {
1617 - // In single selection mode at least on option needs to be selected
1618 - return;
1619 - }
1620 -
1621 - // Record changes, i.e., those options that are deselected but were not deselected before.
1622 - var deselected = [];
1094 + deselectAll: function (justVisible) {
1623 1095 var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1624 -
1625 - if (justVisible) {
1626 - var visibleOptions = $(".multiselect-option:not(.disabled):not(.multiselect-filter-hidden)", this.$popupContainer);
1627 - $('input[type="checkbox"]:enabled', visibleOptions).prop('checked', false);
1628 - visibleOptions.removeClass(this.options.selectedClass);
1629 -
1630 - $('input[type="checkbox"]:enabled', visibleOptions).each($.proxy(function (index, element) {
1631 - var value = $(element).val();
1632 - var option = this.getOptionByValue(value);
1633 - if ($(option).prop('selected')) {
1634 - deselected.push(option);
1635 - }
1636 - $(option).prop('selected', false);
1637 - }, this));
1096 +
1097 + if(justVisible) {
1098 + var visibleCheckboxes = $("li input[type='checkbox']:not(:disabled)", this.$ul).filter(":visible");
1099 + visibleCheckboxes.prop('checked', false);
1100 +
1101 + var values = visibleCheckboxes.map(function() {
1102 + return $(this).val();
1103 + }).get();
1104 +
1105 + $("option:enabled", this.$select).filter(function(index) {
1106 + return $.inArray($(this).val(), values) !== -1;
1107 + }).prop('selected', false);
1108 +
1109 + if (this.options.selectedClass) {
1110 + $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass);
1111 + }
1638 1112 }
1639 1113 else {
1640 - var allOptions = $(".multiselect-option:not(.disabled):not(.multiselect-group)", this.$popupContainer);
1641 - $('input[type="checkbox"]:enabled', allOptions).prop('checked', false);
1642 - allOptions.removeClass(this.options.selectedClass);
1643 -
1644 - $('input[type="checkbox"]:enabled', allOptions).each($.proxy(function (index, element) {
1645 - var value = $(element).val();
1646 - var option = this.getOptionByValue(value);
1647 - if ($(option).prop('selected')) {
1648 - deselected.push(option);
1649 - }
1650 - $(option).prop('selected', false);
1651 - }, this));
1114 + $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false);
1115 + $("option:enabled", this.$select).prop('selected', false);
1116 +
1117 + if (this.options.selectedClass) {
1118 + $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass);
1119 + }
1652 1120 }
1653 -
1654 - $('.multiselect-all input[value="' + this.options.selectAllValue + '"]', this.$popupContainer).prop('checked', false);
1655 -
1656 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1657 - this.updateOptGroups();
1658 - }
1659 -
1660 - this.updateButtonText();
1661 - this.updateSelectAll();
1662 -
1663 - if (triggerOnDeselectAll) {
1664 - this.options.onDeselectAll(deselected);
1665 - }
1666 1121 },
1667 1122
1668 1123 /**
1669 1124 * Rebuild the plugin.
1670 - *
1125 + *
1671 1126 * Rebuilds the dropdown, the filter and the select all option.
1672 1127 */
1673 - rebuild: function () {
1674 - this.$popupContainer.html('');
1128 + rebuild: function() {
1129 + this.$ul.html('');
1675 1130
1676 1131 // Important to distinguish between radios and checkboxes.
1677 1132 this.options.multiple = this.$select.attr('multiple') === "multiple";
1678 1133
@@ -1678,100 +1133,66 @@
1678 1133
1679 1134 this.buildSelectAll();
1680 1135 this.buildDropdownOptions();
1681 1136 this.buildFilter();
1682 - this.buildButtons();
1683 1137
1684 1138 this.updateButtonText();
1685 - this.updateSelectAll(true);
1686 -
1687 - if (this.options.enableClickableOptGroups && this.options.multiple) {
1688 - this.updateOptGroups();
1139 + this.updateSelectAll();
1140 +
1141 + if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1142 + this.disable();
1689 1143 }
1690 -
1691 - if (this.options.disableIfEmpty) {
1692 - if ($('option', this.$select).length <= 0) {
1693 - if (!this.$select.prop('disabled')) {
1694 - this.disable(true);
1695 - }
1696 - }
1697 - else if (this.$select.data("disabled-by-option")) {
1698 - this.enable();
1699 - }
1144 + else {
1145 + this.enable();
1700 1146 }
1701 -
1147 +
1702 1148 if (this.options.dropRight) {
1703 - this.$container.addClass('dropright');
1149 + this.$ul.addClass('pull-right');
1704 1150 }
1705 - else if (this.options.dropUp) {
1706 - this.$container.addClass('dropup');
1707 - }
1708 -
1709 - if (this.options.widthSynchronizationMode !== 'never') {
1710 - this.synchronizeButtonAndPopupWidth();
1711 - }
1712 1151 },
1713 1152
1714 1153 /**
1715 1154 * The provided data will be used to build the dropdown.
1716 1155 */
1717 - dataprovider: function (dataprovider) {
1718 -
1156 + dataprovider: function(dataprovider) {
1157 +
1719 1158 var groupCounter = 0;
1720 1159 var $select = this.$select.empty();
1721 -
1160 +
1722 1161 $.each(dataprovider, function (index, option) {
1723 1162 var $tag;
1724 -
1163 +
1725 1164 if ($.isArray(option.children)) { // create optiongroup tag
1726 1165 groupCounter++;
1727 -
1166 +
1728 1167 $tag = $('<optgroup/>').attr({
1729 1168 label: option.label || 'Group ' + groupCounter,
1730 - disabled: !!option.disabled,
1731 - value: option.value
1169 + disabled: !!option.disabled
1732 1170 });
1733 -
1734 - forEach(option.children, function (subOption) { // add children option tags
1735 - var attributes = {
1171 +
1172 + forEach(option.children, function(subOption) { // add children option tags
1173 + $tag.append($('<option/>').attr({
1736 1174 value: subOption.value,
1737 1175 label: subOption.label || subOption.value,
1738 1176 title: subOption.title,
1739 1177 selected: !!subOption.selected,
1740 1178 disabled: !!subOption.disabled
1741 - };
1742 -
1743 - //Loop through attributes object and add key-value for each attribute
1744 - for (var key in subOption.attributes) {
1745 - attributes['data-' + key] = subOption.attributes[key];
1746 - }
1747 - //Append original attributes + new data attributes to option
1748 - $tag.append($('<option/>').attr(attributes));
1179 + }));
1749 1180 });
1750 1181 }
1751 1182 else {
1752 -
1753 - var attributes = {
1754 - 'value': option.value,
1755 - 'label': option.label || option.value,
1756 - 'title': option.title,
1757 - 'class': option['class'],
1758 - 'selected': !!option['selected'],
1759 - 'disabled': !!option['disabled']
1760 - };
1761 - //Loop through attributes object and add key-value for each attribute
1762 - for (var key in option.attributes) {
1763 - attributes['data-' + key] = option.attributes[key];
1764 - }
1765 - //Append original attributes + new data attributes to option
1766 - $tag = $('<option/>').attr(attributes);
1767 -
1768 - $tag.text(option.label || option.value);
1183 + $tag = $('<option/>').attr({
1184 + value: option.value,
1185 + label: option.label || option.value,
1186 + title: option.title,
1187 + selected: !!option.selected,
1188 + disabled: !!option.disabled
1189 + });
1769 1190 }
1770 -
1191 +
1771 1192 $select.append($tag);
1772 1193 });
1773 -
1194 +
1774 1195 this.rebuild();
1775 1196 },
1776 1197
1777 1198 /**
@@ -1776,32 +1197,21 @@
1776 1197
1777 1198 /**
1778 1199 * Enable the multiselect.
1779 1200 */
1780 - enable: function () {
1201 + enable: function() {
1781 1202 this.$select.prop('disabled', false);
1782 1203 this.$button.prop('disabled', false)
1783 1204 .removeClass('disabled');
1784 -
1785 - this.updateButtonText();
1786 1205 },
1787 1206
1788 1207 /**
1789 1208 * Disable the multiselect.
1790 1209 */
1791 - disable: function (disableByOption) {
1210 + disable: function() {
1792 1211 this.$select.prop('disabled', true);
1793 1212 this.$button.prop('disabled', true)
1794 1213 .addClass('disabled');
1795 -
1796 - if (disableByOption) {
1797 - this.$select.data("disabled-by-option", true);
1798 - }
1799 - else {
1800 - this.$select.data("disabled-by-option", null);
1801 - }
1802 -
1803 - this.updateButtonText();
1804 1214 },
1805 1215
1806 1216 /**
1807 1217 * Set the options.
@@ -1807,9 +1217,9 @@
1807 1217 * Set the options.
1808 1218 *
1809 1219 * @param {Array} options
1810 1220 */
1811 - setOptions: function (options) {
1221 + setOptions: function(options) {
1812 1222 this.options = this.mergeOptions(options);
1813 1223 },
1814 1224
1815 1225 /**
@@ -1817,9 +1227,9 @@
1817 1227 *
1818 1228 * @param {Array} options
1819 1229 * @returns {Array}
1820 1230 */
1821 - mergeOptions: function (options) {
1231 + mergeOptions: function(options) {
1822 1232 return $.extend(true, {}, this.defaults, this.options, options);
1823 1233 },
1824 1234
1825 1235 /**
@@ -1826,64 +1236,31 @@
1826 1236 * Checks whether a select all checkbox is present.
1827 1237 *
1828 1238 * @returns {Boolean}
1829 1239 */
1830 - hasSelectAll: function () {
1831 - return $('.multiselect-all', this.$popupContainer).length > 0;
1240 + hasSelectAll: function() {
1241 + return $('li.multiselect-all', this.$ul).length > 0;
1832 1242 },
1833 1243
1834 1244 /**
1835 - * Update opt groups.
1836 - */
1837 - updateOptGroups: function () {
1838 - var $groups = $('.multiselect-group', this.$popupContainer)
1839 - var selectedClass = this.options.selectedClass;
1840 -
1841 - $groups.each(function () {
1842 - var $options = $(this).nextUntil('.multiselect-group')
1843 - .not('.multiselect-filter-hidden')
1844 - .not('.disabled');
1845 -
1846 - var checked = true;
1847 - $options.each(function () {
1848 - var $input = $('input', this);
1849 -
1850 - if (!$input.prop('checked')) {
1851 - checked = false;
1852 - }
1853 - });
1854 -
1855 - if (selectedClass) {
1856 - if (checked) {
1857 - $(this).addClass(selectedClass);
1858 - }
1859 - else {
1860 - $(this).removeClass(selectedClass);
1861 - }
1862 - }
1863 -
1864 - $('input', this).prop('checked', checked);
1865 - });
1866 - },
1867 -
1868 - /**
1869 1245 * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1870 1246 */
1871 - updateSelectAll: function (notTriggerOnSelectAll) {
1247 + updateSelectAll: function() {
1872 1248 if (this.hasSelectAll()) {
1873 - var allBoxes = $(".multiselect-option:not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$popupContainer);
1249 + var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul);
1874 1250 var allBoxesLength = allBoxes.length;
1875 1251 var checkedBoxesLength = allBoxes.filter(":checked").length;
1876 - var selectAllItem = $(".multiselect-all", this.$popupContainer);
1877 - var selectAllInput = selectAllItem.find("input");
1878 -
1252 + var selectAllLi = $("li.multiselect-all", this.$ul);
1253 + var selectAllInput = selectAllLi.find("input");
1254 +
1879 1255 if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1880 1256 selectAllInput.prop("checked", true);
1881 - selectAllItem.addClass(this.options.selectedClass);
1257 + selectAllLi.addClass(this.options.selectedClass);
1258 + this.options.onSelectAll();
1882 1259 }
1883 1260 else {
1884 1261 selectAllInput.prop("checked", false);
1885 - selectAllItem.removeClass(this.options.selectedClass);
1262 + selectAllLi.removeClass(this.options.selectedClass);
1886 1263 }
1887 1264 }
1888 1265 },
1889 1266
@@ -1889,11 +1266,11 @@
1889 1266
1890 1267 /**
1891 1268 * Update the button text and its title based on the currently selected options.
1892 1269 */
1893 - updateButtonText: function () {
1270 + updateButtonText: function() {
1894 1271 var options = this.getSelected();
1895 -
1272 +
1896 1273 // First update the displayed button text.
1897 1274 if (this.options.enableHTML) {
1898 1275 $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select));
1899 1276 }
@@ -1899,12 +1276,11 @@
1899 1276 }
1900 1277 else {
1901 1278 $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select));
1902 1279 }
1903 -
1280 +
1904 1281 // Now update the title attribute of the button.
1905 1282 $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
1906 - this.$button.trigger('change');
1907 1283 },
1908 1284
1909 1285 /**
1910 1286 * Get all selected options.
@@ -1910,9 +1286,9 @@
1910 1286 * Get all selected options.
1911 1287 *
1912 1288 * @returns {jQUery}
1913 1289 */
1914 - getSelected: function () {
1290 + getSelected: function() {
1915 1291 return $('option', this.$select).filter(":selected");
1916 1292 },
1917 1293
1918 1294 /**
@@ -1941,9 +1317,9 @@
1941 1317 * @returns {jQuery}
1942 1318 */
1943 1319 getInputByValue: function (value) {
1944 1320
1945 - var checkboxes = $('.multiselect-option input:not(.multiselect-search)', this.$popupContainer);
1321 + var checkboxes = $('li input', this.$ul);
1946 1322 var valueToCompare = value.toString();
1947 1323
1948 1324 for (var i = 0; i < checkboxes.length; i = i + 1) {
1949 1325 var checkbox = checkboxes[i];
@@ -1955,38 +1331,27 @@
1955 1331
1956 1332 /**
1957 1333 * Used for knockout integration.
1958 1334 */
1959 - updateOriginalOptions: function () {
1335 + updateOriginalOptions: function() {
1960 1336 this.originalOptions = this.$select.clone()[0].options;
1961 1337 },
1962 1338
1963 - asyncFunction: function (callback, timeout, self) {
1339 + asyncFunction: function(callback, timeout, self) {
1964 1340 var args = Array.prototype.slice.call(arguments, 3);
1965 - return setTimeout(function () {
1341 + return setTimeout(function() {
1966 1342 callback.apply(self || window, args);
1967 1343 }, timeout);
1968 1344 },
1969 1345
1970 - setAllSelectedText: function (allSelectedText) {
1346 + setAllSelectedText: function(allSelectedText) {
1971 1347 this.options.allSelectedText = allSelectedText;
1972 1348 this.updateButtonText();
1973 - },
1974 -
1975 - isFirefox: function () {
1976 - var firefoxIdentifier = 'firefox';
1977 - var valueNotFoundIndex = -1;
1978 -
1979 - if (navigator && navigator.userAgent) {
1980 - return navigator.userAgent.toLocaleLowerCase().indexOf(firefoxIdentifier) > valueNotFoundIndex;
1981 - }
1982 -
1983 - return false;
1984 1349 }
1985 1350 };
1986 1351
1987 - $.fn.multiselect = function (option, parameter, extraOptions) {
1988 - return this.each(function () {
1352 + $.fn.multiselect = function(option, parameter, extraOptions) {
1353 + return this.each(function() {
1989 1354 var data = $(this).data('multiselect');
1990 1355 var options = typeof option === 'object' && option;
1991 1356
1992 1357 // Initialize the multiselect.
@@ -1997,9 +1362,9 @@
1997 1362
1998 1363 // Call multiselect method.
1999 1364 if (typeof option === 'string') {
2000 1365 data[option](parameter, extraOptions);
2001 -
1366 +
2002 1367 if (option === 'destroy') {
2003 1368 $(this).data('multiselect', false);
2004 1369 }
2005 1370 }
@@ -2007,9 +1372,9 @@
2007 1372 };
2008 1373
2009 1374 $.fn.multiselect.Constructor = Multiselect;
2010 1375
2011 - $(function () {
1376 + $(function() {
2012 1377 $("select[data-role=multiselect]").multiselect();
2013 1378 });
2014 1379
2015 -});
1380 +}(window.jQuery);