unforassets 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +41 -39
  4. data/lib/unforassets/version.rb +1 -1
  5. data/vendor/assets/images/unforassets/jcrop/jcrop.gif +0 -0
  6. data/vendor/assets/javascripts/unforassets/accounting.js +4 -0
  7. data/vendor/assets/javascripts/unforassets/autonumeric.js +4 -0
  8. data/vendor/assets/javascripts/{big.js → unforassets/big.js} +0 -0
  9. data/vendor/assets/javascripts/unforassets/bootstrap-datetimepicker.js +2 -0
  10. data/vendor/assets/javascripts/unforassets/bootstrap-multiselect/plugins/collapsible-groups.js +92 -0
  11. data/vendor/assets/javascripts/unforassets/bootstrap-multiselect.js +1416 -0
  12. data/vendor/assets/javascripts/unforassets/chart.js +14 -0
  13. data/vendor/assets/javascripts/unforassets/jcrop.js +6 -0
  14. data/vendor/assets/javascripts/{jquery.blockui.js → unforassets/jquery-blockui.js} +0 -0
  15. data/vendor/assets/javascripts/unforassets/moment.js +505 -0
  16. data/vendor/assets/javascripts/unforassets/pnotify.js +31 -0
  17. data/vendor/assets/javascripts/unforassets/select2/i18n/ar.js +3 -0
  18. data/vendor/assets/javascripts/unforassets/select2/i18n/az.js +3 -0
  19. data/vendor/assets/javascripts/unforassets/select2/i18n/bg.js +3 -0
  20. data/vendor/assets/javascripts/unforassets/select2/i18n/ca.js +3 -0
  21. data/vendor/assets/javascripts/unforassets/select2/i18n/cs.js +3 -0
  22. data/vendor/assets/javascripts/unforassets/select2/i18n/da.js +3 -0
  23. data/vendor/assets/javascripts/unforassets/select2/i18n/de.js +3 -0
  24. data/vendor/assets/javascripts/unforassets/select2/i18n/el.js +3 -0
  25. data/vendor/assets/javascripts/unforassets/select2/i18n/en.js +3 -0
  26. data/vendor/assets/javascripts/unforassets/select2/i18n/es.js +3 -0
  27. data/vendor/assets/javascripts/unforassets/select2/i18n/et.js +3 -0
  28. data/vendor/assets/javascripts/unforassets/select2/i18n/eu.js +3 -0
  29. data/vendor/assets/javascripts/unforassets/select2/i18n/fa.js +3 -0
  30. data/vendor/assets/javascripts/unforassets/select2/i18n/fi.js +3 -0
  31. data/vendor/assets/javascripts/unforassets/select2/i18n/fr.js +3 -0
  32. data/vendor/assets/javascripts/unforassets/select2/i18n/gl.js +3 -0
  33. data/vendor/assets/javascripts/unforassets/select2/i18n/he.js +3 -0
  34. data/vendor/assets/javascripts/unforassets/select2/i18n/hi.js +3 -0
  35. data/vendor/assets/javascripts/unforassets/select2/i18n/hr.js +3 -0
  36. data/vendor/assets/javascripts/unforassets/select2/i18n/hu.js +3 -0
  37. data/vendor/assets/javascripts/unforassets/select2/i18n/id.js +3 -0
  38. data/vendor/assets/javascripts/unforassets/select2/i18n/is.js +3 -0
  39. data/vendor/assets/javascripts/unforassets/select2/i18n/it.js +3 -0
  40. data/vendor/assets/javascripts/unforassets/select2/i18n/ja.js +3 -0
  41. data/vendor/assets/javascripts/unforassets/select2/i18n/km.js +3 -0
  42. data/vendor/assets/javascripts/unforassets/select2/i18n/ko.js +3 -0
  43. data/vendor/assets/javascripts/unforassets/select2/i18n/lt.js +3 -0
  44. data/vendor/assets/javascripts/unforassets/select2/i18n/lv.js +3 -0
  45. data/vendor/assets/javascripts/unforassets/select2/i18n/mk.js +3 -0
  46. data/vendor/assets/javascripts/unforassets/select2/i18n/ms.js +3 -0
  47. data/vendor/assets/javascripts/unforassets/select2/i18n/nb.js +3 -0
  48. data/vendor/assets/javascripts/unforassets/select2/i18n/nl.js +3 -0
  49. data/vendor/assets/javascripts/unforassets/select2/i18n/pl.js +3 -0
  50. data/vendor/assets/javascripts/unforassets/select2/i18n/pt-BR.js +3 -0
  51. data/vendor/assets/javascripts/unforassets/select2/i18n/pt.js +3 -0
  52. data/vendor/assets/javascripts/unforassets/select2/i18n/ro.js +3 -0
  53. data/vendor/assets/javascripts/unforassets/select2/i18n/ru.js +3 -0
  54. data/vendor/assets/javascripts/unforassets/select2/i18n/sk.js +3 -0
  55. data/vendor/assets/javascripts/unforassets/select2/i18n/sr-Cyrl.js +3 -0
  56. data/vendor/assets/javascripts/unforassets/select2/i18n/sr.js +3 -0
  57. data/vendor/assets/javascripts/unforassets/select2/i18n/sv.js +3 -0
  58. data/vendor/assets/javascripts/unforassets/select2/i18n/th.js +3 -0
  59. data/vendor/assets/javascripts/unforassets/select2/i18n/tr.js +3 -0
  60. data/vendor/assets/javascripts/unforassets/select2/i18n/uk.js +3 -0
  61. data/vendor/assets/javascripts/unforassets/select2/i18n/vi.js +3 -0
  62. data/vendor/assets/javascripts/unforassets/select2/i18n/zh-CN.js +3 -0
  63. data/vendor/assets/javascripts/unforassets/select2/i18n/zh-TW.js +3 -0
  64. data/vendor/assets/javascripts/unforassets/select2.js +3 -0
  65. data/vendor/assets/javascripts/unforassets/tooltipster.js +2 -0
  66. data/vendor/assets/javascripts/unforassets/typeahead.js +2451 -0
  67. data/vendor/assets/javascripts/unforassets/undescore.js +6 -0
  68. data/vendor/assets/javascripts/unforassets/uri.js +151 -0
  69. data/vendor/assets/javascripts/unforassets/vex.js +2 -0
  70. data/vendor/assets/stylesheets/unforassets/bootstrap-datetimepicker.css +5 -0
  71. data/vendor/assets/stylesheets/unforassets/bootstrap-multiselect.css +1 -0
  72. data/vendor/assets/stylesheets/unforassets/jcrop.css +6 -0
  73. data/vendor/assets/stylesheets/unforassets/pnotify.css +10 -0
  74. data/vendor/assets/stylesheets/unforassets/select2.css +1 -0
  75. data/vendor/assets/stylesheets/unforassets/typeahead.css +77 -0
  76. metadata +91 -24
  77. data/vendor/assets/javascripts/tooltipster.bundle.js +0 -4260
  78. data/vendor/assets/javascripts/vex.combined.js +0 -1621
  79. /data/vendor/assets/javascripts/{fancybox.js → unforassets/fancybox.js} +0 -0
  80. /data/vendor/assets/javascripts/{tooltipster-plugin-scrollable-tip.js → unforassets/tooltipster/plugins/scrollable-tip.js} +0 -0
  81. /data/vendor/assets/javascripts/{tooltipster-plugin-svg.js → unforassets/tooltipster/plugins/svg.js} +0 -0
  82. /data/vendor/assets/stylesheets/{fancybox.css → unforassets/fancybox.css} +0 -0
  83. /data/vendor/assets/stylesheets/{tooltipster-sidetip-borderless.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-borderless.css} +0 -0
  84. /data/vendor/assets/stylesheets/{tooltipster-sidetip-light.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-light.css} +0 -0
  85. /data/vendor/assets/stylesheets/{tooltipster-sidetip-noir.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-noir.css} +0 -0
  86. /data/vendor/assets/stylesheets/{tooltipster-sidetip-punk.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-punk.css} +0 -0
  87. /data/vendor/assets/stylesheets/{tooltipster-sidetip-shadow.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-shadow.css} +0 -0
  88. /data/vendor/assets/stylesheets/{tooltipster.bundle.css → unforassets/tooltipster.css} +0 -0
  89. /data/vendor/assets/stylesheets/{vex-theme-bottom-right-corner.css → unforassets/vex/themes/vex-theme-bottom-right-corner.css} +0 -0
  90. /data/vendor/assets/stylesheets/{vex-theme-default.css → unforassets/vex/themes/vex-theme-default.css} +0 -0
  91. /data/vendor/assets/stylesheets/{vex-theme-flat-attack.css → unforassets/vex/themes/vex-theme-flat-attack.css} +0 -0
  92. /data/vendor/assets/stylesheets/{vex-theme-os.css → unforassets/vex/themes/vex-theme-os.css} +0 -0
  93. /data/vendor/assets/stylesheets/{vex-theme-plain.css → unforassets/vex/themes/vex-theme-plain.css} +0 -0
  94. /data/vendor/assets/stylesheets/{vex-theme-top.css → unforassets/vex/themes/vex-theme-top.css} +0 -0
  95. /data/vendor/assets/stylesheets/{vex-theme-wireframe.css → unforassets/vex/themes/vex-theme-wireframe.css} +0 -0
  96. /data/vendor/assets/stylesheets/{vex.css → unforassets/vex.css} +0 -0
@@ -0,0 +1,1416 @@
1
+ /**
2
+ * Bootstrap Multiselect (https://github.com/davidstutz/bootstrap-multiselect)
3
+ *
4
+ * Apache License, Version 2.0:
5
+ * Copyright (c) 2012 - 2015 David Stutz
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ * use this file except in compliance with the License. You may obtain a
9
+ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ * License for the specific language governing permissions and limitations
15
+ * under the License.
16
+ *
17
+ * BSD 3-Clause License:
18
+ * Copyright (c) 2012 - 2015 David Stutz
19
+ * All rights reserved.
20
+ *
21
+ * Redistribution and use in source and binary forms, with or without
22
+ * modification, are permitted provided that the following conditions are met:
23
+ * - Redistributions of source code must retain the above copyright notice,
24
+ * this list of conditions and the following disclaimer.
25
+ * - Redistributions in binary form must reproduce the above copyright notice,
26
+ * this list of conditions and the following disclaimer in the documentation
27
+ * and/or other materials provided with the distribution.
28
+ * - Neither the name of David Stutz nor the names of its contributors may be
29
+ * used to endorse or promote products derived from this software without
30
+ * specific prior written permission.
31
+ *
32
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
34
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
35
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
36
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
37
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
38
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
39
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
40
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
41
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43
+ */
44
+ !function ($) {
45
+ "use strict";// jshint ;_;
46
+
47
+ if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
48
+ ko.bindingHandlers.multiselect = {
49
+ after: ['options', 'value', 'selectedOptions'],
50
+
51
+ init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
52
+ var $element = $(element);
53
+ var config = ko.toJS(valueAccessor());
54
+
55
+ $element.multiselect(config);
56
+
57
+ if (allBindings.has('options')) {
58
+ var options = allBindings.get('options');
59
+ if (ko.isObservable(options)) {
60
+ ko.computed({
61
+ read: function() {
62
+ options();
63
+ setTimeout(function() {
64
+ var ms = $element.data('multiselect');
65
+ if (ms)
66
+ ms.updateOriginalOptions();//Not sure how beneficial this is.
67
+ $element.multiselect('rebuild');
68
+ }, 1);
69
+ },
70
+ disposeWhenNodeIsRemoved: element
71
+ });
72
+ }
73
+ }
74
+
75
+ //value and selectedOptions are two-way, so these will be triggered even by our own actions.
76
+ //It needs some way to tell if they are triggered because of us or because of outside change.
77
+ //It doesn't loop but it's a waste of processing.
78
+ if (allBindings.has('value')) {
79
+ var value = allBindings.get('value');
80
+ if (ko.isObservable(value)) {
81
+ ko.computed({
82
+ read: function() {
83
+ value();
84
+ setTimeout(function() {
85
+ $element.multiselect('refresh');
86
+ }, 1);
87
+ },
88
+ disposeWhenNodeIsRemoved: element
89
+ }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
90
+ }
91
+ }
92
+
93
+ //Switched from arrayChange subscription to general subscription using 'refresh'.
94
+ //Not sure performance is any better using 'select' and 'deselect'.
95
+ if (allBindings.has('selectedOptions')) {
96
+ var selectedOptions = allBindings.get('selectedOptions');
97
+ if (ko.isObservable(selectedOptions)) {
98
+ ko.computed({
99
+ read: function() {
100
+ selectedOptions();
101
+ setTimeout(function() {
102
+ $element.multiselect('refresh');
103
+ }, 1);
104
+ },
105
+ disposeWhenNodeIsRemoved: element
106
+ }).extend({ rateLimit: 100, notifyWhenChangesStop: true });
107
+ }
108
+ }
109
+
110
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
111
+ $element.multiselect('destroy');
112
+ });
113
+ },
114
+
115
+ update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
116
+ var $element = $(element);
117
+ var config = ko.toJS(valueAccessor());
118
+
119
+ $element.multiselect('setOptions', config);
120
+ $element.multiselect('rebuild');
121
+ }
122
+ };
123
+ }
124
+
125
+ function forEach(array, callback) {
126
+ for (var index = 0; index < array.length; ++index) {
127
+ callback(array[index], index);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Constructor to create a new multiselect using the given select.
133
+ *
134
+ * @param {jQuery} select
135
+ * @param {Object} options
136
+ * @returns {Multiselect}
137
+ */
138
+ function Multiselect(select, options) {
139
+
140
+ this.$select = $(select);
141
+
142
+ // Placeholder via data attributes
143
+ if (this.$select.attr("data-placeholder")) {
144
+ options.nonSelectedText = this.$select.data("placeholder");
145
+ }
146
+
147
+ this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
148
+
149
+ // Initialization.
150
+ // We have to clone to create a new reference.
151
+ this.originalOptions = this.$select.clone()[0].options;
152
+ this.query = '';
153
+ this.searchTimeout = null;
154
+ this.lastToggledInput = null
155
+
156
+ this.options.multiple = this.$select.attr('multiple') === "multiple";
157
+ this.options.onChange = $.proxy(this.options.onChange, this);
158
+ this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
159
+ this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
160
+ this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
161
+ this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);
162
+
163
+ // Build select all if enabled.
164
+ this.buildContainer();
165
+ this.buildButton();
166
+ this.buildDropdown();
167
+ this.buildSelectAll();
168
+ this.buildDropdownOptions();
169
+ this.buildFilter();
170
+
171
+ this.updateButtonText();
172
+ this.updateSelectAll();
173
+
174
+ if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
175
+ this.disable();
176
+ }
177
+
178
+ this.$select.hide().after(this.$container);
179
+ };
180
+
181
+ Multiselect.prototype = {
182
+
183
+ defaults: {
184
+ /**
185
+ * Default text function will either print 'None selected' in case no
186
+ * option is selected or a list of the selected options up to a length
187
+ * of 3 selected options.
188
+ *
189
+ * @param {jQuery} options
190
+ * @param {jQuery} select
191
+ * @returns {String}
192
+ */
193
+ buttonText: function(options, select) {
194
+ if (options.length === 0) {
195
+ return this.nonSelectedText;
196
+ }
197
+ else if (this.allSelectedText
198
+ && options.length === $('option', $(select)).length
199
+ && $('option', $(select)).length !== 1
200
+ && this.multiple) {
201
+
202
+ if (this.selectAllNumber) {
203
+ return this.allSelectedText + ' (' + options.length + ')';
204
+ }
205
+ else {
206
+ return this.allSelectedText;
207
+ }
208
+ }
209
+ else if (options.length > this.numberDisplayed) {
210
+ return options.length + ' ' + this.nSelectedText;
211
+ }
212
+ else {
213
+ var selected = '';
214
+ var delimiter = this.delimiterText;
215
+
216
+ options.each(function() {
217
+ var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
218
+ selected += label + delimiter;
219
+ });
220
+
221
+ return selected.substr(0, selected.length - 2);
222
+ }
223
+ },
224
+ /**
225
+ * Updates the title of the button similar to the buttonText function.
226
+ *
227
+ * @param {jQuery} options
228
+ * @param {jQuery} select
229
+ * @returns {@exp;selected@call;substr}
230
+ */
231
+ buttonTitle: function(options, select) {
232
+ if (options.length === 0) {
233
+ return this.nonSelectedText;
234
+ }
235
+ else {
236
+ var selected = '';
237
+ var delimiter = this.delimiterText;
238
+
239
+ options.each(function () {
240
+ var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
241
+ selected += label + delimiter;
242
+ });
243
+ return selected.substr(0, selected.length - 2);
244
+ }
245
+ },
246
+ /**
247
+ * Create a label.
248
+ *
249
+ * @param {jQuery} element
250
+ * @returns {String}
251
+ */
252
+ optionLabel: function(element){
253
+ return $(element).attr('label') || $(element).text();
254
+ },
255
+ /**
256
+ * Triggered on change of the multiselect.
257
+ *
258
+ * Not triggered when selecting/deselecting options manually.
259
+ *
260
+ * @param {jQuery} option
261
+ * @param {Boolean} checked
262
+ */
263
+ onChange : function(option, checked) {
264
+
265
+ },
266
+ /**
267
+ * Triggered when the dropdown is shown.
268
+ *
269
+ * @param {jQuery} event
270
+ */
271
+ onDropdownShow: function(event) {
272
+
273
+ },
274
+ /**
275
+ * Triggered when the dropdown is hidden.
276
+ *
277
+ * @param {jQuery} event
278
+ */
279
+ onDropdownHide: function(event) {
280
+
281
+ },
282
+ /**
283
+ * Triggered after the dropdown is shown.
284
+ *
285
+ * @param {jQuery} event
286
+ */
287
+ onDropdownShown: function(event) {
288
+
289
+ },
290
+ /**
291
+ * Triggered after the dropdown is hidden.
292
+ *
293
+ * @param {jQuery} event
294
+ */
295
+ onDropdownHidden: function(event) {
296
+
297
+ },
298
+ /**
299
+ * Triggered on select all.
300
+ */
301
+ onSelectAll: function() {
302
+
303
+ },
304
+ enableHTML: false,
305
+ buttonClass: 'btn btn-default',
306
+ inheritClass: false,
307
+ buttonWidth: 'auto',
308
+ buttonContainer: '<div class="btn-group" />',
309
+ dropRight: false,
310
+ selectedClass: 'active',
311
+ // Maximum height of the dropdown menu.
312
+ // If maximum height is exceeded a scrollbar will be displayed.
313
+ maxHeight: false,
314
+ checkboxName: false,
315
+ includeSelectAllOption: false,
316
+ includeSelectAllIfMoreThan: 0,
317
+ selectAllText: ' Select all',
318
+ selectAllValue: 'multiselect-all',
319
+ selectAllName: false,
320
+ selectAllNumber: true,
321
+ enableFiltering: false,
322
+ enableCaseInsensitiveFiltering: false,
323
+ enableClickableOptGroups: false,
324
+ filterPlaceholder: 'Search',
325
+ // possible options: 'text', 'value', 'both'
326
+ filterBehavior: 'text',
327
+ includeFilterClearBtn: true,
328
+ preventInputChangeEvent: false,
329
+ nonSelectedText: 'None selected',
330
+ nSelectedText: 'selected',
331
+ allSelectedText: 'All selected',
332
+ numberDisplayed: 3,
333
+ disableIfEmpty: false,
334
+ delimiterText: ', ',
335
+ templates: {
336
+ button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"><span class="multiselect-selected-text"></span> <b class="caret"></b></button>',
337
+ ul: '<ul class="multiselect-container dropdown-menu"></ul>',
338
+ 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>',
339
+ 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>',
340
+ li: '<li><a tabindex="0"><label></label></a></li>',
341
+ divider: '<li class="multiselect-item divider"></li>',
342
+ liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>'
343
+ }
344
+ },
345
+
346
+ constructor: Multiselect,
347
+
348
+ /**
349
+ * Builds the container of the multiselect.
350
+ */
351
+ buildContainer: function() {
352
+ this.$container = $(this.options.buttonContainer);
353
+ this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
354
+ this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
355
+ this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
356
+ this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
357
+ },
358
+
359
+ /**
360
+ * Builds the button of the multiselect.
361
+ */
362
+ buildButton: function() {
363
+ this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
364
+ if (this.$select.attr('class') && this.options.inheritClass) {
365
+ this.$button.addClass(this.$select.attr('class'));
366
+ }
367
+ // Adopt active state.
368
+ if (this.$select.prop('disabled')) {
369
+ this.disable();
370
+ }
371
+ else {
372
+ this.enable();
373
+ }
374
+
375
+ // Manually add button width if set.
376
+ if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
377
+ this.$button.css({
378
+ 'width' : this.options.buttonWidth,
379
+ 'overflow' : 'hidden',
380
+ 'text-overflow' : 'ellipsis'
381
+ });
382
+ this.$container.css({
383
+ 'width': this.options.buttonWidth
384
+ });
385
+ }
386
+
387
+ // Keep the tab index from the select.
388
+ var tabindex = this.$select.attr('tabindex');
389
+ if (tabindex) {
390
+ this.$button.attr('tabindex', tabindex);
391
+ }
392
+
393
+ this.$container.prepend(this.$button);
394
+ },
395
+
396
+ /**
397
+ * Builds the ul representing the dropdown menu.
398
+ */
399
+ buildDropdown: function() {
400
+
401
+ // Build ul.
402
+ this.$ul = $(this.options.templates.ul);
403
+
404
+ if (this.options.dropRight) {
405
+ this.$ul.addClass('pull-right');
406
+ }
407
+
408
+ // Set max height of dropdown menu to activate auto scrollbar.
409
+ if (this.options.maxHeight) {
410
+ // TODO: Add a class for this option to move the css declarations.
411
+ this.$ul.css({
412
+ 'max-height': this.options.maxHeight + 'px',
413
+ 'overflow-y': 'auto',
414
+ 'overflow-x': 'hidden'
415
+ });
416
+ }
417
+
418
+ this.$container.append(this.$ul);
419
+ },
420
+
421
+ /**
422
+ * Build the dropdown options and binds all nessecary events.
423
+ *
424
+ * Uses createDivider and createOptionValue to create the necessary options.
425
+ */
426
+ buildDropdownOptions: function() {
427
+
428
+ this.$select.children().each($.proxy(function(index, element) {
429
+
430
+ var $element = $(element);
431
+ // Support optgroups and options without a group simultaneously.
432
+ var tag = $element.prop('tagName')
433
+ .toLowerCase();
434
+
435
+ if ($element.prop('value') === this.options.selectAllValue) {
436
+ return;
437
+ }
438
+
439
+ if (tag === 'optgroup') {
440
+ this.createOptgroup(element);
441
+ }
442
+ else if (tag === 'option') {
443
+
444
+ if ($element.data('role') === 'divider') {
445
+ this.createDivider();
446
+ }
447
+ else {
448
+ this.createOptionValue(element);
449
+ }
450
+
451
+ }
452
+
453
+ // Other illegal tags will be ignored.
454
+ }, this));
455
+
456
+ // Bind the change event on the dropdown elements.
457
+ $('li input', this.$ul).on('change', $.proxy(function(event) {
458
+ var $target = $(event.target);
459
+
460
+ var checked = $target.prop('checked') || false;
461
+ var isSelectAllOption = $target.val() === this.options.selectAllValue;
462
+
463
+ // Apply or unapply the configured selected class.
464
+ if (this.options.selectedClass) {
465
+ if (checked) {
466
+ $target.closest('li')
467
+ .addClass(this.options.selectedClass);
468
+ }
469
+ else {
470
+ $target.closest('li')
471
+ .removeClass(this.options.selectedClass);
472
+ }
473
+ }
474
+
475
+ // Get the corresponding option.
476
+ var value = $target.val();
477
+ var $option = this.getOptionByValue(value);
478
+
479
+ var $optionsNotThis = $('option', this.$select).not($option);
480
+ var $checkboxesNotThis = $('input', this.$container).not($target);
481
+
482
+ if (isSelectAllOption) {
483
+ if (checked) {
484
+ this.selectAll();
485
+ }
486
+ else {
487
+ this.deselectAll();
488
+ }
489
+ }
490
+
491
+ if(!isSelectAllOption){
492
+ if (checked) {
493
+ $option.prop('selected', true);
494
+
495
+ if (this.options.multiple) {
496
+ // Simply select additional option.
497
+ $option.prop('selected', true);
498
+ }
499
+ else {
500
+ // Unselect all other options and corresponding checkboxes.
501
+ if (this.options.selectedClass) {
502
+ $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
503
+ }
504
+
505
+ $($checkboxesNotThis).prop('checked', false);
506
+ $optionsNotThis.prop('selected', false);
507
+
508
+ // It's a single selection, so close.
509
+ this.$button.click();
510
+ }
511
+
512
+ if (this.options.selectedClass === "active") {
513
+ $optionsNotThis.closest("a").css("outline", "");
514
+ }
515
+ }
516
+ else {
517
+ // Unselect option.
518
+ $option.prop('selected', false);
519
+ }
520
+ }
521
+
522
+ this.$select.change();
523
+
524
+ this.updateButtonText();
525
+ this.updateSelectAll();
526
+
527
+ this.options.onChange($option, checked);
528
+
529
+ if(this.options.preventInputChangeEvent) {
530
+ return false;
531
+ }
532
+ }, this));
533
+
534
+ $('li a', this.$ul).on('mousedown', function(e) {
535
+ if (e.shiftKey) {
536
+ // Prevent selecting text by Shift+click
537
+ return false;
538
+ }
539
+ });
540
+
541
+ $('li a', this.$ul).on('touchstart click', $.proxy(function(event) {
542
+ event.stopPropagation();
543
+
544
+ var $target = $(event.target);
545
+
546
+ if (event.shiftKey && this.options.multiple) {
547
+ if($target.is("label")){ // Handles checkbox selection manually (see https://github.com/davidstutz/bootstrap-multiselect/issues/431)
548
+ event.preventDefault();
549
+ $target = $target.find("input");
550
+ $target.prop("checked", !$target.prop("checked"));
551
+ }
552
+ var checked = $target.prop('checked') || false;
553
+
554
+ if (this.lastToggledInput !== null && this.lastToggledInput !== $target) { // Make sure we actually have a range
555
+ var from = $target.closest("li").index();
556
+ var to = this.lastToggledInput.closest("li").index();
557
+
558
+ if (from > to) { // Swap the indices
559
+ var tmp = to;
560
+ to = from;
561
+ from = tmp;
562
+ }
563
+
564
+ // Make sure we grab all elements since slice excludes the last index
565
+ ++to;
566
+
567
+ // Change the checkboxes and underlying options
568
+ var range = this.$ul.find("li").slice(from, to).find("input");
569
+
570
+ range.prop('checked', checked);
571
+
572
+ if (this.options.selectedClass) {
573
+ range.closest('li')
574
+ .toggleClass(this.options.selectedClass, checked);
575
+ }
576
+
577
+ for (var i = 0, j = range.length; i < j; i++) {
578
+ var $checkbox = $(range[i]);
579
+
580
+ var $option = this.getOptionByValue($checkbox.val());
581
+
582
+ $option.prop('selected', checked);
583
+ }
584
+ }
585
+
586
+ // Trigger the select "change" event
587
+ $target.trigger("change");
588
+ }
589
+
590
+ // Remembers last clicked option
591
+ if($target.is("input") && !$target.closest("li").is(".multiselect-item")){
592
+ this.lastToggledInput = $target;
593
+ }
594
+
595
+ $target.blur();
596
+ }, this));
597
+
598
+ // Keyboard support.
599
+ this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
600
+ if ($('input[type="text"]', this.$container).is(':focus')) {
601
+ return;
602
+ }
603
+
604
+ if (event.keyCode === 9 && this.$container.hasClass('open')) {
605
+ this.$button.click();
606
+ }
607
+ else {
608
+ var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
609
+
610
+ if (!$items.length) {
611
+ return;
612
+ }
613
+
614
+ var index = $items.index($items.filter(':focus'));
615
+
616
+ // Navigation up.
617
+ if (event.keyCode === 38 && index > 0) {
618
+ index--;
619
+ }
620
+ // Navigate down.
621
+ else if (event.keyCode === 40 && index < $items.length - 1) {
622
+ index++;
623
+ }
624
+ else if (!~index) {
625
+ index = 0;
626
+ }
627
+
628
+ var $current = $items.eq(index);
629
+ $current.focus();
630
+
631
+ if (event.keyCode === 32 || event.keyCode === 13) {
632
+ var $checkbox = $current.find('input');
633
+
634
+ $checkbox.prop("checked", !$checkbox.prop("checked"));
635
+ $checkbox.change();
636
+ }
637
+
638
+ event.stopPropagation();
639
+ event.preventDefault();
640
+ }
641
+ }, this));
642
+
643
+ if(this.options.enableClickableOptGroups && this.options.multiple) {
644
+ $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) {
645
+ event.stopPropagation();
646
+
647
+ var group = $(event.target).parent();
648
+
649
+ // Search all option in optgroup
650
+ var $options = group.nextUntil('li.multiselect-group');
651
+ var $visibleOptions = $options.filter(":visible:not(.disabled)");
652
+
653
+ // check or uncheck items
654
+ var allChecked = true;
655
+ var optionInputs = $visibleOptions.find('input');
656
+ optionInputs.each(function() {
657
+ allChecked = allChecked && $(this).prop('checked');
658
+ });
659
+
660
+ optionInputs.prop('checked', !allChecked).trigger('change');
661
+ }, this));
662
+ }
663
+ },
664
+
665
+ /**
666
+ * Create an option using the given select option.
667
+ *
668
+ * @param {jQuery} element
669
+ */
670
+ createOptionValue: function(element) {
671
+ var $element = $(element);
672
+ if ($element.is(':selected')) {
673
+ $element.prop('selected', true);
674
+ }
675
+
676
+ // Support the label attribute on options.
677
+ var label = this.options.optionLabel(element);
678
+ var value = $element.val();
679
+ var inputType = this.options.multiple ? "checkbox" : "radio";
680
+
681
+ var $li = $(this.options.templates.li);
682
+ var $label = $('label', $li);
683
+ $label.addClass(inputType);
684
+
685
+ if (this.options.enableHTML) {
686
+ $label.html(" " + label);
687
+ }
688
+ else {
689
+ $label.text(" " + label);
690
+ }
691
+
692
+ var $checkbox = $('<input/>').attr('type', inputType);
693
+
694
+ if (this.options.checkboxName) {
695
+ $checkbox.attr('name', this.options.checkboxName);
696
+ }
697
+ $label.prepend($checkbox);
698
+
699
+ var selected = $element.prop('selected') || false;
700
+ $checkbox.val(value);
701
+
702
+ if (value === this.options.selectAllValue) {
703
+ $li.addClass("multiselect-item multiselect-all");
704
+ $checkbox.parent().parent()
705
+ .addClass('multiselect-all');
706
+ }
707
+
708
+ $label.attr('title', $element.attr('title'));
709
+
710
+ this.$ul.append($li);
711
+
712
+ if ($element.is(':disabled')) {
713
+ $checkbox.attr('disabled', 'disabled')
714
+ .prop('disabled', true)
715
+ .closest('a')
716
+ .attr("tabindex", "-1")
717
+ .closest('li')
718
+ .addClass('disabled');
719
+ }
720
+
721
+ $checkbox.prop('checked', selected);
722
+
723
+ if (selected && this.options.selectedClass) {
724
+ $checkbox.closest('li')
725
+ .addClass(this.options.selectedClass);
726
+ }
727
+ },
728
+
729
+ /**
730
+ * Creates a divider using the given select option.
731
+ *
732
+ * @param {jQuery} element
733
+ */
734
+ createDivider: function(element) {
735
+ var $divider = $(this.options.templates.divider);
736
+ this.$ul.append($divider);
737
+ },
738
+
739
+ /**
740
+ * Creates an optgroup.
741
+ *
742
+ * @param {jQuery} group
743
+ */
744
+ createOptgroup: function(group) {
745
+ var groupName = $(group).prop('label');
746
+
747
+ // Add a header for the group.
748
+ var $li = $(this.options.templates.liGroup);
749
+
750
+ if (this.options.enableHTML) {
751
+ $('label', $li).html(groupName);
752
+ }
753
+ else {
754
+ $('label', $li).text(groupName);
755
+ }
756
+
757
+ if (this.options.enableClickableOptGroups) {
758
+ $li.addClass('multiselect-group-clickable');
759
+ }
760
+
761
+ this.$ul.append($li);
762
+
763
+ if ($(group).is(':disabled')) {
764
+ $li.addClass('disabled');
765
+ }
766
+
767
+ // Add the options of the group.
768
+ $('option', group).each($.proxy(function(index, element) {
769
+ this.createOptionValue(element);
770
+ }, this));
771
+ },
772
+
773
+ /**
774
+ * Build the selct all.
775
+ *
776
+ * Checks if a select all has already been created.
777
+ */
778
+ buildSelectAll: function() {
779
+ if (typeof this.options.selectAllValue === 'number') {
780
+ this.options.selectAllValue = this.options.selectAllValue.toString();
781
+ }
782
+
783
+ var alreadyHasSelectAll = this.hasSelectAll();
784
+
785
+ if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
786
+ && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
787
+
788
+ // Check whether to add a divider after the select all.
789
+ if (this.options.includeSelectAllDivider) {
790
+ this.$ul.prepend($(this.options.templates.divider));
791
+ }
792
+
793
+ var $li = $(this.options.templates.li);
794
+ $('label', $li).addClass("checkbox");
795
+
796
+ if (this.options.enableHTML) {
797
+ $('label', $li).html(" " + this.options.selectAllText);
798
+ }
799
+ else {
800
+ $('label', $li).text(" " + this.options.selectAllText);
801
+ }
802
+
803
+ if (this.options.selectAllName) {
804
+ $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />');
805
+ }
806
+ else {
807
+ $('label', $li).prepend('<input type="checkbox" />');
808
+ }
809
+
810
+ var $checkbox = $('input', $li);
811
+ $checkbox.val(this.options.selectAllValue);
812
+
813
+ $li.addClass("multiselect-item multiselect-all");
814
+ $checkbox.parent().parent()
815
+ .addClass('multiselect-all');
816
+
817
+ this.$ul.prepend($li);
818
+
819
+ $checkbox.prop('checked', false);
820
+ }
821
+ },
822
+
823
+ /**
824
+ * Builds the filter.
825
+ */
826
+ buildFilter: function() {
827
+
828
+ // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
829
+ if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
830
+ var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
831
+
832
+ if (this.$select.find('option').length >= enableFilterLength) {
833
+
834
+ this.$filter = $(this.options.templates.filter);
835
+ $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
836
+
837
+ // Adds optional filter clear button
838
+ if(this.options.includeFilterClearBtn){
839
+ var clearBtn = $(this.options.templates.filterClearBtn);
840
+ clearBtn.on('click', $.proxy(function(event){
841
+ clearTimeout(this.searchTimeout);
842
+ this.$filter.find('.multiselect-search').val('');
843
+ $('li', this.$ul).show().removeClass("filter-hidden");
844
+ this.updateSelectAll();
845
+ }, this));
846
+ this.$filter.find('.input-group').append(clearBtn);
847
+ }
848
+
849
+ this.$ul.prepend(this.$filter);
850
+
851
+ this.$filter.val(this.query).on('click', function(event) {
852
+ event.stopPropagation();
853
+ }).on('input keydown', $.proxy(function(event) {
854
+ // Cancel enter key default behaviour
855
+ if (event.which === 13) {
856
+ event.preventDefault();
857
+ }
858
+
859
+ // This is useful to catch "keydown" events after the browser has updated the control.
860
+ clearTimeout(this.searchTimeout);
861
+
862
+ this.searchTimeout = this.asyncFunction($.proxy(function() {
863
+
864
+ if (this.query !== event.target.value) {
865
+ this.query = event.target.value;
866
+
867
+ var currentGroup, currentGroupVisible;
868
+ $.each($('li', this.$ul), $.proxy(function(index, element) {
869
+ var value = $('input', element).length > 0 ? $('input', element).val() : "";
870
+ var text = $('label', element).text();
871
+
872
+ var filterCandidate = '';
873
+ if ((this.options.filterBehavior === 'text')) {
874
+ filterCandidate = text;
875
+ }
876
+ else if ((this.options.filterBehavior === 'value')) {
877
+ filterCandidate = value;
878
+ }
879
+ else if (this.options.filterBehavior === 'both') {
880
+ filterCandidate = text + '\n' + value;
881
+ }
882
+
883
+ if (value !== this.options.selectAllValue && text) {
884
+ // By default lets assume that element is not
885
+ // interesting for this search.
886
+ var showElement = false;
887
+
888
+ if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
889
+ showElement = true;
890
+ }
891
+ else if (filterCandidate.indexOf(this.query) > -1) {
892
+ showElement = true;
893
+ }
894
+
895
+ // Toggle current element (group or group item) according to showElement boolean.
896
+ $(element).toggle(showElement).toggleClass('filter-hidden', !showElement);
897
+
898
+ // Differentiate groups and group items.
899
+ if ($(element).hasClass('multiselect-group')) {
900
+ // Remember group status.
901
+ currentGroup = element;
902
+ currentGroupVisible = showElement;
903
+ }
904
+ else {
905
+ // Show group name when at least one of its items is visible.
906
+ if (showElement) {
907
+ $(currentGroup).show().removeClass('filter-hidden');
908
+ }
909
+
910
+ // Show all group items when group name satisfies filter.
911
+ if (!showElement && currentGroupVisible) {
912
+ $(element).show().removeClass('filter-hidden');
913
+ }
914
+ }
915
+ }
916
+ }, this));
917
+ }
918
+
919
+ this.updateSelectAll();
920
+ }, this), 300, this);
921
+ }, this));
922
+ }
923
+ }
924
+ },
925
+
926
+ /**
927
+ * Unbinds the whole plugin.
928
+ */
929
+ destroy: function() {
930
+ this.$container.remove();
931
+ this.$select.show();
932
+ this.$select.data('multiselect', null);
933
+ },
934
+
935
+ /**
936
+ * Refreshs the multiselect based on the selected options of the select.
937
+ */
938
+ refresh: function() {
939
+ $('option', this.$select).each($.proxy(function(index, element) {
940
+ var $input = $('li input', this.$ul).filter(function() {
941
+ return $(this).val() === $(element).val();
942
+ });
943
+
944
+ if ($(element).is(':selected')) {
945
+ $input.prop('checked', true);
946
+
947
+ if (this.options.selectedClass) {
948
+ $input.closest('li')
949
+ .addClass(this.options.selectedClass);
950
+ }
951
+ }
952
+ else {
953
+ $input.prop('checked', false);
954
+
955
+ if (this.options.selectedClass) {
956
+ $input.closest('li')
957
+ .removeClass(this.options.selectedClass);
958
+ }
959
+ }
960
+
961
+ if ($(element).is(":disabled")) {
962
+ $input.attr('disabled', 'disabled')
963
+ .prop('disabled', true)
964
+ .closest('li')
965
+ .addClass('disabled');
966
+ }
967
+ else {
968
+ $input.prop('disabled', false)
969
+ .closest('li')
970
+ .removeClass('disabled');
971
+ }
972
+ }, this));
973
+
974
+ this.updateButtonText();
975
+ this.updateSelectAll();
976
+ },
977
+
978
+ /**
979
+ * Select all options of the given values.
980
+ *
981
+ * If triggerOnChange is set to true, the on change event is triggered if
982
+ * and only if one value is passed.
983
+ *
984
+ * @param {Array} selectValues
985
+ * @param {Boolean} triggerOnChange
986
+ */
987
+ select: function(selectValues, triggerOnChange) {
988
+ if(!$.isArray(selectValues)) {
989
+ selectValues = [selectValues];
990
+ }
991
+
992
+ for (var i = 0; i < selectValues.length; i++) {
993
+ var value = selectValues[i];
994
+
995
+ if (value === null || value === undefined) {
996
+ continue;
997
+ }
998
+
999
+ var $option = this.getOptionByValue(value);
1000
+ var $checkbox = this.getInputByValue(value);
1001
+
1002
+ if($option === undefined || $checkbox === undefined) {
1003
+ continue;
1004
+ }
1005
+
1006
+ if (!this.options.multiple) {
1007
+ this.deselectAll(false);
1008
+ }
1009
+
1010
+ if (this.options.selectedClass) {
1011
+ $checkbox.closest('li')
1012
+ .addClass(this.options.selectedClass);
1013
+ }
1014
+
1015
+ $checkbox.prop('checked', true);
1016
+ $option.prop('selected', true);
1017
+
1018
+ if (triggerOnChange) {
1019
+ this.options.onChange($option, true);
1020
+ }
1021
+ }
1022
+
1023
+ this.updateButtonText();
1024
+ this.updateSelectAll();
1025
+ },
1026
+
1027
+ /**
1028
+ * Clears all selected items.
1029
+ */
1030
+ clearSelection: function () {
1031
+ this.deselectAll(false);
1032
+ this.updateButtonText();
1033
+ this.updateSelectAll();
1034
+ },
1035
+
1036
+ /**
1037
+ * Deselects all options of the given values.
1038
+ *
1039
+ * If triggerOnChange is set to true, the on change event is triggered, if
1040
+ * and only if one value is passed.
1041
+ *
1042
+ * @param {Array} deselectValues
1043
+ * @param {Boolean} triggerOnChange
1044
+ */
1045
+ deselect: function(deselectValues, triggerOnChange) {
1046
+ if(!$.isArray(deselectValues)) {
1047
+ deselectValues = [deselectValues];
1048
+ }
1049
+
1050
+ for (var i = 0; i < deselectValues.length; i++) {
1051
+ var value = deselectValues[i];
1052
+
1053
+ if (value === null || value === undefined) {
1054
+ continue;
1055
+ }
1056
+
1057
+ var $option = this.getOptionByValue(value);
1058
+ var $checkbox = this.getInputByValue(value);
1059
+
1060
+ if($option === undefined || $checkbox === undefined) {
1061
+ continue;
1062
+ }
1063
+
1064
+ if (this.options.selectedClass) {
1065
+ $checkbox.closest('li')
1066
+ .removeClass(this.options.selectedClass);
1067
+ }
1068
+
1069
+ $checkbox.prop('checked', false);
1070
+ $option.prop('selected', false);
1071
+
1072
+ if (triggerOnChange) {
1073
+ this.options.onChange($option, false);
1074
+ }
1075
+ }
1076
+
1077
+ this.updateButtonText();
1078
+ this.updateSelectAll();
1079
+ },
1080
+
1081
+ /**
1082
+ * Selects all enabled & visible options.
1083
+ *
1084
+ * If justVisible is true or not specified, only visible options are selected.
1085
+ *
1086
+ * @param {Boolean} justVisible
1087
+ * @param {Boolean} triggerOnSelectAll
1088
+ */
1089
+ selectAll: function (justVisible, triggerOnSelectAll) {
1090
+ var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1091
+ var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul);
1092
+ var visibleCheckboxes = allCheckboxes.filter(":visible");
1093
+ var allCheckboxesCount = allCheckboxes.length;
1094
+ var visibleCheckboxesCount = visibleCheckboxes.length;
1095
+
1096
+ if(justVisible) {
1097
+ visibleCheckboxes.prop('checked', true);
1098
+ $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass);
1099
+ }
1100
+ else {
1101
+ allCheckboxes.prop('checked', true);
1102
+ $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass);
1103
+ }
1104
+
1105
+ if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) {
1106
+ $("option:enabled", this.$select).prop('selected', true);
1107
+ }
1108
+ else {
1109
+ var values = visibleCheckboxes.map(function() {
1110
+ return $(this).val();
1111
+ }).get();
1112
+
1113
+ $("option:enabled", this.$select).filter(function(index) {
1114
+ return $.inArray($(this).val(), values) !== -1;
1115
+ }).prop('selected', true);
1116
+ }
1117
+
1118
+ if (triggerOnSelectAll) {
1119
+ this.options.onSelectAll();
1120
+ }
1121
+ },
1122
+
1123
+ /**
1124
+ * Deselects all options.
1125
+ *
1126
+ * If justVisible is true or not specified, only visible options are deselected.
1127
+ *
1128
+ * @param {Boolean} justVisible
1129
+ */
1130
+ deselectAll: function (justVisible) {
1131
+ var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
1132
+
1133
+ if(justVisible) {
1134
+ var visibleCheckboxes = $("li input[type='checkbox']:not(:disabled)", this.$ul).filter(":visible");
1135
+ visibleCheckboxes.prop('checked', false);
1136
+
1137
+ var values = visibleCheckboxes.map(function() {
1138
+ return $(this).val();
1139
+ }).get();
1140
+
1141
+ $("option:enabled", this.$select).filter(function(index) {
1142
+ return $.inArray($(this).val(), values) !== -1;
1143
+ }).prop('selected', false);
1144
+
1145
+ if (this.options.selectedClass) {
1146
+ $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass);
1147
+ }
1148
+ }
1149
+ else {
1150
+ $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false);
1151
+ $("option:enabled", this.$select).prop('selected', false);
1152
+
1153
+ if (this.options.selectedClass) {
1154
+ $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass);
1155
+ }
1156
+ }
1157
+ },
1158
+
1159
+ /**
1160
+ * Rebuild the plugin.
1161
+ *
1162
+ * Rebuilds the dropdown, the filter and the select all option.
1163
+ */
1164
+ rebuild: function() {
1165
+ this.$ul.html('');
1166
+
1167
+ // Important to distinguish between radios and checkboxes.
1168
+ this.options.multiple = this.$select.attr('multiple') === "multiple";
1169
+
1170
+ this.buildSelectAll();
1171
+ this.buildDropdownOptions();
1172
+ this.buildFilter();
1173
+
1174
+ this.updateButtonText();
1175
+ this.updateSelectAll();
1176
+
1177
+ if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
1178
+ this.disable();
1179
+ }
1180
+ else {
1181
+ this.enable();
1182
+ }
1183
+
1184
+ if (this.options.dropRight) {
1185
+ this.$ul.addClass('pull-right');
1186
+ }
1187
+ },
1188
+
1189
+ /**
1190
+ * The provided data will be used to build the dropdown.
1191
+ */
1192
+ dataprovider: function(dataprovider) {
1193
+
1194
+ var groupCounter = 0;
1195
+ var $select = this.$select.empty();
1196
+
1197
+ $.each(dataprovider, function (index, option) {
1198
+ var $tag;
1199
+
1200
+ if ($.isArray(option.children)) { // create optiongroup tag
1201
+ groupCounter++;
1202
+
1203
+ $tag = $('<optgroup/>').attr({
1204
+ label: option.label || 'Group ' + groupCounter,
1205
+ disabled: !!option.disabled
1206
+ });
1207
+
1208
+ forEach(option.children, function(subOption) { // add children option tags
1209
+ $tag.append($('<option/>').attr({
1210
+ value: subOption.value,
1211
+ label: subOption.label || subOption.value,
1212
+ title: subOption.title,
1213
+ selected: !!subOption.selected,
1214
+ disabled: !!subOption.disabled
1215
+ }));
1216
+ });
1217
+ }
1218
+ else {
1219
+ $tag = $('<option/>').attr({
1220
+ value: option.value,
1221
+ label: option.label || option.value,
1222
+ title: option.title,
1223
+ selected: !!option.selected,
1224
+ disabled: !!option.disabled
1225
+ });
1226
+ }
1227
+
1228
+ $select.append($tag);
1229
+ });
1230
+
1231
+ this.rebuild();
1232
+ },
1233
+
1234
+ /**
1235
+ * Enable the multiselect.
1236
+ */
1237
+ enable: function() {
1238
+ this.$select.prop('disabled', false);
1239
+ this.$button.prop('disabled', false)
1240
+ .removeClass('disabled');
1241
+ },
1242
+
1243
+ /**
1244
+ * Disable the multiselect.
1245
+ */
1246
+ disable: function() {
1247
+ this.$select.prop('disabled', true);
1248
+ this.$button.prop('disabled', true)
1249
+ .addClass('disabled');
1250
+ },
1251
+
1252
+ /**
1253
+ * Set the options.
1254
+ *
1255
+ * @param {Array} options
1256
+ */
1257
+ setOptions: function(options) {
1258
+ this.options = this.mergeOptions(options);
1259
+ },
1260
+
1261
+ /**
1262
+ * Merges the given options with the default options.
1263
+ *
1264
+ * @param {Array} options
1265
+ * @returns {Array}
1266
+ */
1267
+ mergeOptions: function(options) {
1268
+ return $.extend(true, {}, this.defaults, this.options, options);
1269
+ },
1270
+
1271
+ /**
1272
+ * Checks whether a select all checkbox is present.
1273
+ *
1274
+ * @returns {Boolean}
1275
+ */
1276
+ hasSelectAll: function() {
1277
+ return $('li.multiselect-all', this.$ul).length > 0;
1278
+ },
1279
+
1280
+ /**
1281
+ * Updates the select all checkbox based on the currently displayed and selected checkboxes.
1282
+ */
1283
+ updateSelectAll: function() {
1284
+ if (this.hasSelectAll()) {
1285
+ var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul);
1286
+ var allBoxesLength = allBoxes.length;
1287
+ var checkedBoxesLength = allBoxes.filter(":checked").length;
1288
+ var selectAllLi = $("li.multiselect-all", this.$ul);
1289
+ var selectAllInput = selectAllLi.find("input");
1290
+
1291
+ if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
1292
+ selectAllInput.prop("checked", true);
1293
+ selectAllLi.addClass(this.options.selectedClass);
1294
+ this.options.onSelectAll();
1295
+ }
1296
+ else {
1297
+ selectAllInput.prop("checked", false);
1298
+ selectAllLi.removeClass(this.options.selectedClass);
1299
+ }
1300
+ }
1301
+ },
1302
+
1303
+ /**
1304
+ * Update the button text and its title based on the currently selected options.
1305
+ */
1306
+ updateButtonText: function() {
1307
+ var options = this.getSelected();
1308
+
1309
+ // First update the displayed button text.
1310
+ if (this.options.enableHTML) {
1311
+ $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select));
1312
+ }
1313
+ else {
1314
+ $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select));
1315
+ }
1316
+
1317
+ // Now update the title attribute of the button.
1318
+ $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
1319
+ },
1320
+
1321
+ /**
1322
+ * Get all selected options.
1323
+ *
1324
+ * @returns {jQUery}
1325
+ */
1326
+ getSelected: function() {
1327
+ return $('option', this.$select).filter(":selected");
1328
+ },
1329
+
1330
+ /**
1331
+ * Gets a select option by its value.
1332
+ *
1333
+ * @param {String} value
1334
+ * @returns {jQuery}
1335
+ */
1336
+ getOptionByValue: function (value) {
1337
+
1338
+ var options = $('option', this.$select);
1339
+ var valueToCompare = value.toString();
1340
+
1341
+ for (var i = 0; i < options.length; i = i + 1) {
1342
+ var option = options[i];
1343
+ if (option.value === valueToCompare) {
1344
+ return $(option);
1345
+ }
1346
+ }
1347
+ },
1348
+
1349
+ /**
1350
+ * Get the input (radio/checkbox) by its value.
1351
+ *
1352
+ * @param {String} value
1353
+ * @returns {jQuery}
1354
+ */
1355
+ getInputByValue: function (value) {
1356
+
1357
+ var checkboxes = $('li input', this.$ul);
1358
+ var valueToCompare = value.toString();
1359
+
1360
+ for (var i = 0; i < checkboxes.length; i = i + 1) {
1361
+ var checkbox = checkboxes[i];
1362
+ if (checkbox.value === valueToCompare) {
1363
+ return $(checkbox);
1364
+ }
1365
+ }
1366
+ },
1367
+
1368
+ /**
1369
+ * Used for knockout integration.
1370
+ */
1371
+ updateOriginalOptions: function() {
1372
+ this.originalOptions = this.$select.clone()[0].options;
1373
+ },
1374
+
1375
+ asyncFunction: function(callback, timeout, self) {
1376
+ var args = Array.prototype.slice.call(arguments, 3);
1377
+ return setTimeout(function() {
1378
+ callback.apply(self || window, args);
1379
+ }, timeout);
1380
+ },
1381
+
1382
+ setAllSelectedText: function(allSelectedText) {
1383
+ this.options.allSelectedText = allSelectedText;
1384
+ this.updateButtonText();
1385
+ }
1386
+ };
1387
+
1388
+ $.fn.multiselect = function(option, parameter, extraOptions) {
1389
+ return this.each(function() {
1390
+ var data = $(this).data('multiselect');
1391
+ var options = typeof option === 'object' && option;
1392
+
1393
+ // Initialize the multiselect.
1394
+ if (!data) {
1395
+ data = new Multiselect(this, options);
1396
+ $(this).data('multiselect', data);
1397
+ }
1398
+
1399
+ // Call multiselect method.
1400
+ if (typeof option === 'string') {
1401
+ data[option](parameter, extraOptions);
1402
+
1403
+ if (option === 'destroy') {
1404
+ $(this).data('multiselect', false);
1405
+ }
1406
+ }
1407
+ });
1408
+ };
1409
+
1410
+ $.fn.multiselect.Constructor = Multiselect;
1411
+
1412
+ $(function() {
1413
+ $("select[data-role=multiselect]").multiselect();
1414
+ });
1415
+
1416
+ }(window.jQuery);