bootstrap-multiselect_rails 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Trevor Strieber
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # bootstrap-multiselect_rails
2
+
3
+ This gem packages the [bootstrap-multiselect](https://github.com/davidstutz/bootstrap-multiselect) library for the Rails 3.1+ asset pipeline.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+ ```
10
+ gem 'bootstrap-multiselect_rails', '~> 0.9.4'
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it yourself:
19
+ ```bash
20
+ $ gem install bootstrap-multiselect_rails
21
+ ```
22
+
23
+ To start using the bootstrap-multiselect library in your rails application, enable it via the asset pipeline.
24
+
25
+ Add the following to your Javascript manifest file (`application.js`):
26
+ ```js
27
+ //= require bootstrap-multiselect
28
+ ```
29
+
30
+ Add the following to your stylesheet file:
31
+
32
+ If you are using SCSS, modify your `application.css.scss`
33
+ ```scss
34
+ @import 'bootstrap-multiselect';
35
+ ```
36
+
37
+ If you're using just plain CSS, modify your `application.css`
38
+ ```css
39
+ *= require bootstrap-multiselect
40
+ ```
41
+ ## Usage
42
+
43
+ Check out the documentation at: http://davidstutz.github.io/bootstrap-multiselect/
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require 'bootstrap-multiselect_rails/version'
2
+
3
+ module BootstrapMultiselect
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module BootstrapMultiselect
2
+ module Rails
3
+ VERSION = '0.9.4'
4
+ end
5
+ end
@@ -0,0 +1,994 @@
1
+ /**
2
+ * bootstrap-multiselect.js
3
+ * https://github.com/davidstutz/bootstrap-multiselect
4
+ *
5
+ * Copyright 2012 - 2014 David Stutz
6
+ *
7
+ * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0.
8
+ */
9
+ !function($) {
10
+
11
+ "use strict";// jshint ;_;
12
+
13
+ if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
14
+ ko.bindingHandlers.multiselect = {
15
+
16
+ init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
17
+
18
+ var listOfSelectedItems = allBindingsAccessor().selectedOptions,
19
+ config = ko.utils.unwrapObservable(valueAccessor());
20
+
21
+ $(element).multiselect(config);
22
+
23
+ if (isObservableArray(listOfSelectedItems)) {
24
+ // Subscribe to the selectedOptions: ko.observableArray
25
+ listOfSelectedItems.subscribe(function (changes) {
26
+ var addedArray = [], deletedArray = [];
27
+ changes.forEach(function (change) {
28
+ switch (change.status) {
29
+ case 'added':
30
+ addedArray.push(change.value);
31
+ break;
32
+ case 'deleted':
33
+ deletedArray.push(change.value);
34
+ break;
35
+ }
36
+ });
37
+ if (addedArray.length > 0) {
38
+ $(element).multiselect('select', addedArray);
39
+ };
40
+ if (deletedArray.length > 0) {
41
+ $(element).multiselect('deselect', deletedArray);
42
+ };
43
+ }, null, "arrayChange");
44
+ }
45
+ },
46
+
47
+ update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
48
+
49
+ var listOfItems = allBindingsAccessor().options,
50
+ ms = $(element).data('multiselect'),
51
+ config = ko.utils.unwrapObservable(valueAccessor());
52
+
53
+ if (isObservableArray(listOfItems)) {
54
+ // Subscribe to the options: ko.observableArray incase it changes later
55
+ listOfItems.subscribe(function (theArray) {
56
+ $(element).multiselect('rebuild');
57
+ });
58
+ }
59
+
60
+ if (!ms) {
61
+ $(element).multiselect(config);
62
+ }
63
+ else {
64
+ ms.updateOriginalOptions();
65
+ }
66
+ }
67
+ };
68
+ }
69
+
70
+ function isObservableArray(obj) {
71
+ return ko.isObservable(obj) && !(obj.destroyAll === undefined);
72
+ }
73
+
74
+ /**
75
+ * Constructor to create a new multiselect using the given select.
76
+ *
77
+ * @param {jQuery} select
78
+ * @param {Object} options
79
+ * @returns {Multiselect}
80
+ */
81
+ function Multiselect(select, options) {
82
+
83
+ this.options = this.mergeOptions(options);
84
+ this.$select = $(select);
85
+
86
+ // Initialization.
87
+ // We have to clone to create a new reference.
88
+ this.originalOptions = this.$select.clone()[0].options;
89
+ this.query = '';
90
+ this.searchTimeout = null;
91
+
92
+ this.options.multiple = this.$select.attr('multiple') === "multiple";
93
+ this.options.onChange = $.proxy(this.options.onChange, this);
94
+ this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
95
+ this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
96
+
97
+ // Build select all if enabled.
98
+ this.buildContainer();
99
+ this.buildButton();
100
+ this.buildSelectAll();
101
+ this.buildDropdown();
102
+ this.buildDropdownOptions();
103
+ this.buildFilter();
104
+
105
+ this.updateButtonText();
106
+ this.updateSelectAll();
107
+
108
+ this.$select.hide().after(this.$container);
109
+ };
110
+
111
+ Multiselect.prototype = {
112
+
113
+ defaults: {
114
+ /**
115
+ * Default text function will either print 'None selected' in case no
116
+ * option is selected or a list of the selected options up to a length of 3 selected options.
117
+ *
118
+ * @param {jQuery} options
119
+ * @param {jQuery} select
120
+ * @returns {String}
121
+ */
122
+ buttonText: function(options, select) {
123
+ if (options.length === 0) {
124
+ return this.nonSelectedText + ' <b class="caret"></b>';
125
+ }
126
+ else {
127
+ if (options.length > this.numberDisplayed) {
128
+ return options.length + ' ' + this.nSelectedText + ' <b class="caret"></b>';
129
+ }
130
+ else {
131
+ var selected = '';
132
+ options.each(function() {
133
+ var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html();
134
+
135
+ selected += label + ', ';
136
+ });
137
+ return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>';
138
+ }
139
+ }
140
+ },
141
+ /**
142
+ * Updates the title of the button similar to the buttonText function.
143
+ * @param {jQuery} options
144
+ * @param {jQuery} select
145
+ * @returns {@exp;selected@call;substr}
146
+ */
147
+ buttonTitle: function(options, select) {
148
+ if (options.length === 0) {
149
+ return this.nonSelectedText;
150
+ }
151
+ else {
152
+ var selected = '';
153
+ options.each(function () {
154
+ selected += $(this).text() + ', ';
155
+ });
156
+ return selected.substr(0, selected.length - 2);
157
+ }
158
+ },
159
+ /**
160
+ * Create a label.
161
+ *
162
+ * @param {jQuery} element
163
+ * @returns {String}
164
+ */
165
+ label: function(element){
166
+ return $(element).attr('label') || $(element).html();
167
+ },
168
+ /**
169
+ * Triggered on change of the multiselect.
170
+ * Not triggered when selecting/deselecting options manually.
171
+ *
172
+ * @param {jQuery} option
173
+ * @param {Boolean} checked
174
+ */
175
+ onChange : function(option, checked) {
176
+
177
+ },
178
+ /**
179
+ * Triggered when the dropdown is shown.
180
+ *
181
+ * @param {jQuery} event
182
+ */
183
+ onDropdownShow: function(event) {
184
+
185
+ },
186
+ /**
187
+ * Triggered when the dropdown is hidden.
188
+ *
189
+ * @param {jQuery} event
190
+ */
191
+ onDropdownHide: function(event) {
192
+
193
+ },
194
+ buttonClass: 'btn btn-default',
195
+ dropRight: false,
196
+ selectedClass: 'active',
197
+ buttonWidth: 'auto',
198
+ buttonContainer: '<div class="btn-group" />',
199
+ // Maximum height of the dropdown menu.
200
+ // If maximum height is exceeded a scrollbar will be displayed.
201
+ maxHeight: false,
202
+ checkboxName: 'multiselect',
203
+ includeSelectAllOption: false,
204
+ includeSelectAllIfMoreThan: 0,
205
+ selectAllText: ' Select all',
206
+ selectAllValue: 'multiselect-all',
207
+ enableFiltering: false,
208
+ enableCaseInsensitiveFiltering: false,
209
+ filterPlaceholder: 'Search',
210
+ // possible options: 'text', 'value', 'both'
211
+ filterBehavior: 'text',
212
+ preventInputChangeEvent: false,
213
+ nonSelectedText: 'None selected',
214
+ nSelectedText: 'selected',
215
+ numberDisplayed: 3,
216
+ templates: {
217
+ button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"></button>',
218
+ ul: '<ul class="multiselect-container dropdown-menu"></ul>',
219
+ 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>',
220
+ li: '<li><a href="javascript:void(0);"><label></label></a></li>',
221
+ divider: '<li class="divider"></li>',
222
+ liGroup: '<li><label class="multiselect-group"></label></li>'
223
+ }
224
+ },
225
+
226
+ constructor: Multiselect,
227
+
228
+ /**
229
+ * Builds the container of the multiselect.
230
+ */
231
+ buildContainer: function() {
232
+ this.$container = $(this.options.buttonContainer);
233
+ this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
234
+ this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
235
+ },
236
+
237
+ /**
238
+ * Builds the button of the multiselect.
239
+ */
240
+ buildButton: function() {
241
+ this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
242
+
243
+ // Adopt active state.
244
+ if (this.$select.prop('disabled')) {
245
+ this.disable();
246
+ }
247
+ else {
248
+ this.enable();
249
+ }
250
+
251
+ // Manually add button width if set.
252
+ if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
253
+ this.$button.css({
254
+ 'width' : this.options.buttonWidth
255
+ });
256
+ }
257
+
258
+ // Keep the tab index from the select.
259
+ var tabindex = this.$select.attr('tabindex');
260
+ if (tabindex) {
261
+ this.$button.attr('tabindex', tabindex);
262
+ }
263
+
264
+ this.$container.prepend(this.$button);
265
+ },
266
+
267
+ /**
268
+ * Builds the ul representing the dropdown menu.
269
+ */
270
+ buildDropdown: function() {
271
+
272
+ // Build ul.
273
+ this.$ul = $(this.options.templates.ul);
274
+
275
+ if (this.options.dropRight) {
276
+ this.$ul.addClass('pull-right');
277
+ }
278
+
279
+ // Set max height of dropdown menu to activate auto scrollbar.
280
+ if (this.options.maxHeight) {
281
+ // TODO: Add a class for this option to move the css declarations.
282
+ this.$ul.css({
283
+ 'max-height': this.options.maxHeight + 'px',
284
+ 'overflow-y': 'auto',
285
+ 'overflow-x': 'hidden'
286
+ });
287
+ }
288
+
289
+ this.$container.append(this.$ul);
290
+ },
291
+
292
+ /**
293
+ * Build the dropdown options and binds all nessecary events.
294
+ * Uses createDivider and createOptionValue to create the necessary options.
295
+ */
296
+ buildDropdownOptions: function() {
297
+
298
+ this.$select.children().each($.proxy(function(index, element) {
299
+
300
+ // Support optgroups and options without a group simultaneously.
301
+ var tag = $(element).prop('tagName')
302
+ .toLowerCase();
303
+
304
+ if (tag === 'optgroup') {
305
+ this.createOptgroup(element);
306
+ }
307
+ else if (tag === 'option') {
308
+
309
+ if ($(element).data('role') === 'divider') {
310
+ this.createDivider();
311
+ }
312
+ else {
313
+ this.createOptionValue(element);
314
+ }
315
+
316
+ }
317
+
318
+ // Other illegal tags will be ignored.
319
+ }, this));
320
+
321
+ // Bind the change event on the dropdown elements.
322
+ $('li input', this.$ul).on('change', $.proxy(function(event) {
323
+ var $target = $(event.target);
324
+
325
+ var checked = $target.prop('checked') || false;
326
+ var isSelectAllOption = $target.val() === this.options.selectAllValue;
327
+
328
+ // Apply or unapply the configured selected class.
329
+ if (this.options.selectedClass) {
330
+ if (checked) {
331
+ $target.parents('li')
332
+ .addClass(this.options.selectedClass);
333
+ }
334
+ else {
335
+ $target.parents('li')
336
+ .removeClass(this.options.selectedClass);
337
+ }
338
+ }
339
+
340
+ // Get the corresponding option.
341
+ var value = $target.val();
342
+ var $option = this.getOptionByValue(value);
343
+
344
+ var $optionsNotThis = $('option', this.$select).not($option);
345
+ var $checkboxesNotThis = $('input', this.$container).not($target);
346
+
347
+ if (isSelectAllOption) {
348
+ var values = [];
349
+
350
+ // Select the visible checkboxes except the "select-all" and possible divider.
351
+ var availableInputs = $('li input[value!="' + this.options.selectAllValue + '"][data-role!="divider"]', this.$ul).filter(':visible');
352
+ for (var i = 0, j = availableInputs.length; i < j; i++) {
353
+ values.push(availableInputs[i].value);
354
+ }
355
+
356
+ if (checked) {
357
+ this.select(values);
358
+ }
359
+ else {
360
+ this.deselect(values);
361
+ }
362
+ }
363
+
364
+ if (checked) {
365
+ $option.prop('selected', true);
366
+
367
+ if (this.options.multiple) {
368
+ // Simply select additional option.
369
+ $option.prop('selected', true);
370
+ }
371
+ else {
372
+ // Unselect all other options and corresponding checkboxes.
373
+ if (this.options.selectedClass) {
374
+ $($checkboxesNotThis).parents('li').removeClass(this.options.selectedClass);
375
+ }
376
+
377
+ $($checkboxesNotThis).prop('checked', false);
378
+ $optionsNotThis.prop('selected', false);
379
+
380
+ // It's a single selection, so close.
381
+ this.$button.click();
382
+ }
383
+
384
+ if (this.options.selectedClass === "active") {
385
+ $optionsNotThis.parents("a").css("outline", "");
386
+ }
387
+ }
388
+ else {
389
+ // Unselect option.
390
+ $option.prop('selected', false);
391
+ }
392
+
393
+ this.$select.change();
394
+ this.options.onChange($option, checked);
395
+
396
+ this.updateButtonText();
397
+ this.updateSelectAll();
398
+
399
+ if(this.options.preventInputChangeEvent) {
400
+ return false;
401
+ }
402
+ }, this));
403
+
404
+ $('li a', this.$ul).on('touchstart click', function(event) {
405
+ event.stopPropagation();
406
+
407
+ var $target = $(event.target);
408
+
409
+ if (event.shiftKey) {
410
+ var checked = $target.prop('checked') || false;
411
+
412
+ if (checked) {
413
+ var prev = $target.parents('li:last')
414
+ .siblings('li[class="active"]:first');
415
+
416
+ var currentIdx = $target.parents('li')
417
+ .index();
418
+ var prevIdx = prev.index();
419
+
420
+ if (currentIdx > prevIdx) {
421
+ $target.parents("li:last").prevUntil(prev).each(
422
+ function() {
423
+ $(this).find("input:first").prop("checked", true)
424
+ .trigger("change");
425
+ }
426
+ );
427
+ }
428
+ else {
429
+ $target.parents("li:last").nextUntil(prev).each(
430
+ function() {
431
+ $(this).find("input:first").prop("checked", true)
432
+ .trigger("change");
433
+ }
434
+ );
435
+ }
436
+ }
437
+ }
438
+
439
+ $target.blur();
440
+ });
441
+
442
+ // Keyboard support.
443
+ this.$container.on('keydown', $.proxy(function(event) {
444
+ if ($('input[type="text"]', this.$container).is(':focus')) {
445
+ return;
446
+ }
447
+ if ((event.keyCode === 9 || event.keyCode === 27)
448
+ && this.$container.hasClass('open')) {
449
+
450
+ // Close on tab or escape.
451
+ this.$button.click();
452
+ }
453
+ else {
454
+ var $items = $(this.$container).find("li:not(.divider):visible a");
455
+
456
+ if (!$items.length) {
457
+ return;
458
+ }
459
+
460
+ var index = $items.index($items.filter(':focus'));
461
+
462
+ // Navigation up.
463
+ if (event.keyCode === 38 && index > 0) {
464
+ index--;
465
+ }
466
+ // Navigate down.
467
+ else if (event.keyCode === 40 && index < $items.length - 1) {
468
+ index++;
469
+ }
470
+ else if (!~index) {
471
+ index = 0;
472
+ }
473
+
474
+ var $current = $items.eq(index);
475
+ $current.focus();
476
+
477
+ if (event.keyCode === 32 || event.keyCode === 13) {
478
+ var $checkbox = $current.find('input');
479
+
480
+ $checkbox.prop("checked", !$checkbox.prop("checked"));
481
+ $checkbox.change();
482
+ }
483
+
484
+ event.stopPropagation();
485
+ event.preventDefault();
486
+ }
487
+ }, this));
488
+ },
489
+
490
+ /**
491
+ * Create an option using the given select option.
492
+ *
493
+ * @param {jQuery} element
494
+ */
495
+ createOptionValue: function(element) {
496
+ if ($(element).is(':selected')) {
497
+ $(element).prop('selected', true);
498
+ }
499
+
500
+ // Support the label attribute on options.
501
+ var label = this.options.label(element);
502
+ var value = $(element).val();
503
+ var inputType = this.options.multiple ? "checkbox" : "radio";
504
+
505
+ var $li = $(this.options.templates.li);
506
+ $('label', $li).addClass(inputType);
507
+ $('label', $li).append('<input type="' + inputType + '" name="' + this.options.checkboxName + '" />');
508
+
509
+ var selected = $(element).prop('selected') || false;
510
+ var $checkbox = $('input', $li);
511
+ $checkbox.val(value);
512
+
513
+ if (value === this.options.selectAllValue) {
514
+ $checkbox.parent().parent()
515
+ .addClass('multiselect-all');
516
+ }
517
+
518
+ $('label', $li).append(" " + label);
519
+
520
+ this.$ul.append($li);
521
+
522
+ if ($(element).is(':disabled')) {
523
+ $checkbox.attr('disabled', 'disabled')
524
+ .prop('disabled', true)
525
+ .parents('li')
526
+ .addClass('disabled');
527
+ }
528
+
529
+ $checkbox.prop('checked', selected);
530
+
531
+ if (selected && this.options.selectedClass) {
532
+ $checkbox.parents('li')
533
+ .addClass(this.options.selectedClass);
534
+ }
535
+ },
536
+
537
+ /**
538
+ * Creates a divider using the given select option.
539
+ *
540
+ * @param {jQuery} element
541
+ */
542
+ createDivider: function(element) {
543
+ var $divider = $(this.options.templates.divider);
544
+ this.$ul.append($divider);
545
+ },
546
+
547
+ /**
548
+ * Creates an optgroup.
549
+ *
550
+ * @param {jQuery} group
551
+ */
552
+ createOptgroup: function(group) {
553
+ var groupName = $(group).prop('label');
554
+
555
+ // Add a header for the group.
556
+ var $li = $(this.options.templates.liGroup);
557
+ $('label', $li).text(groupName);
558
+
559
+ this.$ul.append($li);
560
+
561
+ if ($(group).is(':disabled')) {
562
+ $li.addClass('disabled');
563
+ }
564
+
565
+ // Add the options of the group.
566
+ $('option', group).each($.proxy(function(index, element) {
567
+ this.createOptionValue(element);
568
+ }, this));
569
+ },
570
+
571
+ /**
572
+ * Build the selct all.
573
+ * Checks if a select all ahs already been created.
574
+ */
575
+ buildSelectAll: function() {
576
+ var alreadyHasSelectAll = this.hasSelectAll();
577
+
578
+ if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple
579
+ && $('option[data-role!="divider"]', this.$select).length > this.options.includeSelectAllIfMoreThan) {
580
+
581
+ // Check whether to add a divider after the select all.
582
+ if (this.options.includeSelectAllDivider) {
583
+ this.$select.prepend('<option value="" disabled="disabled" data-role="divider">');
584
+ }
585
+
586
+ this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>');
587
+ }
588
+ },
589
+
590
+ /**
591
+ * Builds the filter.
592
+ */
593
+ buildFilter: function() {
594
+
595
+ // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
596
+ if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
597
+ var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
598
+
599
+ if (this.$select.find('option').length >= enableFilterLength) {
600
+
601
+ this.$filter = $(this.options.templates.filter);
602
+ $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
603
+ this.$ul.prepend(this.$filter);
604
+
605
+ this.$filter.val(this.query).on('click', function(event) {
606
+ event.stopPropagation();
607
+ }).on('input keydown', $.proxy(function(event) {
608
+ // This is useful to catch "keydown" events after the browser has updated the control.
609
+ clearTimeout(this.searchTimeout);
610
+
611
+ this.searchTimeout = this.asyncFunction($.proxy(function() {
612
+
613
+ if (this.query !== event.target.value) {
614
+ this.query = event.target.value;
615
+
616
+ $.each($('li', this.$ul), $.proxy(function(index, element) {
617
+ var value = $('input', element).val();
618
+ var text = $('label', element).text();
619
+
620
+ var filterCandidate = '';
621
+ if ((this.options.filterBehavior === 'text')) {
622
+ filterCandidate = text;
623
+ }
624
+ else if ((this.options.filterBehavior === 'value')) {
625
+ filterCandidate = value;
626
+ }
627
+ else if (this.options.filterBehavior === 'both') {
628
+ filterCandidate = text + '\n' + value;
629
+ }
630
+
631
+ if (value !== this.options.selectAllValue && text) {
632
+ // by default lets assume that element is not
633
+ // interesting for this search
634
+ var showElement = false;
635
+
636
+ if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
637
+ showElement = true;
638
+ }
639
+ else if (filterCandidate.indexOf(this.query) > -1) {
640
+ showElement = true;
641
+ }
642
+
643
+ if (showElement) {
644
+ $(element).show();
645
+ }
646
+ else {
647
+ $(element).hide();
648
+ }
649
+ }
650
+ }, this));
651
+ }
652
+
653
+ // TODO: check whether select all option needs to be updated.
654
+ }, this), 300, this);
655
+ }, this));
656
+ }
657
+ }
658
+ },
659
+
660
+ /**
661
+ * Unbinds the whole plugin.
662
+ */
663
+ destroy: function() {
664
+ this.$container.remove();
665
+ this.$select.show();
666
+ this.$select.data('multiselect', null);
667
+ },
668
+
669
+ /**
670
+ * Refreshs the multiselect based on the selected options of the select.
671
+ */
672
+ refresh: function() {
673
+ $('option', this.$select).each($.proxy(function(index, element) {
674
+ var $input = $('li input', this.$ul).filter(function() {
675
+ return $(this).val() === $(element).val();
676
+ });
677
+
678
+ if ($(element).is(':selected')) {
679
+ $input.prop('checked', true);
680
+
681
+ if (this.options.selectedClass) {
682
+ $input.parents('li')
683
+ .addClass(this.options.selectedClass);
684
+ }
685
+ }
686
+ else {
687
+ $input.prop('checked', false);
688
+
689
+ if (this.options.selectedClass) {
690
+ $input.parents('li')
691
+ .removeClass(this.options.selectedClass);
692
+ }
693
+ }
694
+
695
+ if ($(element).is(":disabled")) {
696
+ $input.attr('disabled', 'disabled')
697
+ .prop('disabled', true)
698
+ .parents('li')
699
+ .addClass('disabled');
700
+ }
701
+ else {
702
+ $input.prop('disabled', false)
703
+ .parents('li')
704
+ .removeClass('disabled');
705
+ }
706
+ }, this));
707
+
708
+ this.updateButtonText();
709
+ this.updateSelectAll();
710
+ },
711
+
712
+ /**
713
+ * Select all options of the given values.
714
+ *
715
+ * @param {Array} selectValues
716
+ */
717
+ select: function(selectValues) {
718
+ if(!$.isArray(selectValues)) {
719
+ selectValues = [selectValues];
720
+ }
721
+
722
+ for (var i = 0; i < selectValues.length; i++) {
723
+ var value = selectValues[i];
724
+
725
+ var $option = this.getOptionByValue(value);
726
+ var $checkbox = this.getInputByValue(value);
727
+
728
+ if (this.options.selectedClass) {
729
+ $checkbox.parents('li')
730
+ .addClass(this.options.selectedClass);
731
+ }
732
+
733
+ $checkbox.prop('checked', true);
734
+ $option.prop('selected', true);
735
+ }
736
+
737
+ this.updateButtonText();
738
+ },
739
+
740
+ /**
741
+ * Clears all selected items
742
+ *
743
+ */
744
+ clearSelection: function () {
745
+
746
+ var selected = this.getSelected();
747
+
748
+ if (selected.length) {
749
+
750
+ var arry = [];
751
+
752
+ for (var i = 0; i < selected.length; i = i + 1) {
753
+ arry.push(selected[i].value);
754
+ }
755
+
756
+ this.deselect(arry);
757
+ this.$select.change();
758
+ }
759
+ },
760
+
761
+ /**
762
+ * Deselects all options of the given values.
763
+ *
764
+ * @param {Array} deselectValues
765
+ */
766
+ deselect: function(deselectValues) {
767
+ if(!$.isArray(deselectValues)) {
768
+ deselectValues = [deselectValues];
769
+ }
770
+
771
+ for (var i = 0; i < deselectValues.length; i++) {
772
+
773
+ var value = deselectValues[i];
774
+
775
+ var $option = this.getOptionByValue(value);
776
+ var $checkbox = this.getInputByValue(value);
777
+
778
+ if (this.options.selectedClass) {
779
+ $checkbox.parents('li')
780
+ .removeClass(this.options.selectedClass);
781
+ }
782
+
783
+ $checkbox.prop('checked', false);
784
+ $option.prop('selected', false);
785
+ }
786
+
787
+ this.updateButtonText();
788
+ },
789
+
790
+ /**
791
+ * Rebuild the plugin.
792
+ * Rebuilds the dropdown, the filter and the select all option.
793
+ */
794
+ rebuild: function() {
795
+ this.$ul.html('');
796
+
797
+ // Remove select all option in select.
798
+ $('option[value="' + this.options.selectAllValue + '"]', this.$select).remove();
799
+
800
+ // Important to distinguish between radios and checkboxes.
801
+ this.options.multiple = this.$select.attr('multiple') === "multiple";
802
+
803
+ this.buildSelectAll();
804
+ this.buildDropdownOptions();
805
+ this.buildFilter();
806
+
807
+ this.updateButtonText();
808
+ this.updateSelectAll();
809
+ },
810
+
811
+ /**
812
+ * The provided data will be used to build the dropdown.
813
+ *
814
+ * @param {Array} dataprovider
815
+ */
816
+ dataprovider: function(dataprovider) {
817
+ var optionDOM = "";
818
+ dataprovider.forEach(function (option) {
819
+ optionDOM += '<option value="' + option.value + '">' + option.label + '</option>';
820
+ });
821
+
822
+ this.$select.html(optionDOM);
823
+ this.rebuild();
824
+ },
825
+
826
+ /**
827
+ * Enable the multiselect.
828
+ */
829
+ enable: function() {
830
+ this.$select.prop('disabled', false);
831
+ this.$button.prop('disabled', false)
832
+ .removeClass('disabled');
833
+ },
834
+
835
+ /**
836
+ * Disable the multiselect.
837
+ */
838
+ disable: function() {
839
+ this.$select.prop('disabled', true);
840
+ this.$button.prop('disabled', true)
841
+ .addClass('disabled');
842
+ },
843
+
844
+ /**
845
+ * Set the options.
846
+ *
847
+ * @param {Array} options
848
+ */
849
+ setOptions: function(options) {
850
+ this.options = this.mergeOptions(options);
851
+ },
852
+
853
+ /**
854
+ * Merges the given options with the default options.
855
+ *
856
+ * @param {Array} options
857
+ * @returns {Array}
858
+ */
859
+ mergeOptions: function(options) {
860
+ return $.extend(true, {}, this.defaults, options);
861
+ },
862
+
863
+ /**
864
+ * Checks whether a select all option is present.
865
+ *
866
+ * @returns {Boolean}
867
+ */
868
+ hasSelectAll: function() {
869
+ return $('option[value="' + this.options.selectAllValue + '"]', this.$select).length > 0;
870
+ },
871
+
872
+ /**
873
+ * Updates the select all option based on the currently selected options.
874
+ */
875
+ updateSelectAll: function() {
876
+ if (this.hasSelectAll()) {
877
+ var selected = this.getSelected();
878
+
879
+ if (selected.length === $('option:not([data-role=divider])', this.$select).length - 1) {
880
+ this.select(this.options.selectAllValue);
881
+ }
882
+ else {
883
+ this.deselect(this.options.selectAllValue);
884
+ }
885
+ }
886
+ },
887
+
888
+ /**
889
+ * Update the button text and its title based on the currently selected options.
890
+ */
891
+ updateButtonText: function() {
892
+ var options = this.getSelected();
893
+
894
+ // First update the displayed button text.
895
+ $('button', this.$container).html(this.options.buttonText(options, this.$select));
896
+
897
+ // Now update the title attribute of the button.
898
+ $('button', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
899
+
900
+ },
901
+
902
+ /**
903
+ * Get all selected options.
904
+ *
905
+ * @returns {jQUery}
906
+ */
907
+ getSelected: function() {
908
+ return $('option[value!="' + this.options.selectAllValue + '"]:selected', this.$select).filter(function() {
909
+ return $(this).prop('selected');
910
+ });
911
+ },
912
+
913
+ /**
914
+ * Gets a select option by its value.
915
+ *
916
+ * @param {String} value
917
+ * @returns {jQuery}
918
+ */
919
+ getOptionByValue: function (value) {
920
+
921
+ var options = $('option', this.$select);
922
+ var valueToCompare = value.toString();
923
+
924
+ for (var i = 0; i < options.length; i = i + 1) {
925
+ var option = options[i];
926
+ if (option.value === valueToCompare) {
927
+ return $(option);
928
+ }
929
+ }
930
+ },
931
+
932
+ /**
933
+ * Get the input (radio/checkbox) by its value.
934
+ *
935
+ * @param {String} value
936
+ * @returns {jQuery}
937
+ */
938
+ getInputByValue: function (value) {
939
+
940
+ var checkboxes = $('li input', this.$ul);
941
+ var valueToCompare = value.toString();
942
+
943
+ for (var i = 0; i < checkboxes.length; i = i + 1) {
944
+ var checkbox = checkboxes[i];
945
+ if (checkbox.value === valueToCompare) {
946
+ return $(checkbox);
947
+ }
948
+ }
949
+ },
950
+
951
+ /**
952
+ * Used for knockout integration.
953
+ */
954
+ updateOriginalOptions: function() {
955
+ this.originalOptions = this.$select.clone()[0].options;
956
+ },
957
+
958
+ asyncFunction: function(callback, timeout, self) {
959
+ var args = Array.prototype.slice.call(arguments, 3);
960
+ return setTimeout(function() {
961
+ callback.apply(self || window, args);
962
+ }, timeout);
963
+ }
964
+ };
965
+
966
+ $.fn.multiselect = function(option, parameter) {
967
+ return this.each(function() {
968
+ var data = $(this).data('multiselect');
969
+ var options = typeof option === 'object' && option;
970
+
971
+ // Initialize the multiselect.
972
+ if (!data) {
973
+ data = new Multiselect(this, options);
974
+ $(this).data('multiselect', data);
975
+ }
976
+
977
+ // Call multiselect method.
978
+ if (typeof option === 'string') {
979
+ data[option](parameter);
980
+
981
+ if (option === 'destroy') {
982
+ $(this).data('multiselect', false);
983
+ }
984
+ }
985
+ });
986
+ };
987
+
988
+ $.fn.multiselect.Constructor = Multiselect;
989
+
990
+ $(function() {
991
+ $("select[data-role=multiselect]").multiselect();
992
+ });
993
+
994
+ }(window.jQuery);
@@ -0,0 +1 @@
1
+ .multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li>label.multiselect-group{margin:0;padding:3px 20px;height:100%;font-weight:700}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bootstrap-multiselect_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Trevor Strieber
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: This gem packages the Bootstrap multiselect (JS & CSS) for Rails 3.1+
47
+ asset pipeline.
48
+ email:
49
+ - trevor@strieber.org
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/bootstrap-multiselect_rails.rb
55
+ - lib/bootstrap-multiselect_rails/version.rb
56
+ - vendor/assets/stylesheets/bootstrap-multiselect.css
57
+ - vendor/assets/javascripts/bootstrap-multiselect.js
58
+ - LICENSE.txt
59
+ - README.md
60
+ homepage: http://github.com/TrevorS/bootstrap-multiselect_rails
61
+ licenses:
62
+ - MIT
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.23
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Bootstrap 2/3 multiselect's JS & CSS for Rails 3.1+ asset pipeline.
85
+ test_files: []
86
+ has_rdoc: