Projekt

Obecné

Profil

Stáhnout (12.6 KB) Statistiky
| Větev: | Revize:
1 10e26305 Anděl Ondřej
// jquery.multi-select.js
2
// by mySociety
3
// https://github.com/mysociety/jquery-multi-select
4
5
;(function($) {
6
7
  "use strict";
8
9
  var pluginName = "multiSelect",
10
    defaults = {
11
      'containerHTML': '<div class="multi-select-container">',
12
      'menuHTML': '<div class="multi-select-menu">',
13
      'buttonHTML': '<span class="multi-select-button">',
14
      'menuItemsHTML': '<div class="multi-select-menuitems">',
15
      'menuItemHTML': '<label class="multi-select-menuitem">',
16
      'presetsHTML': '<div class="multi-select-presets">',
17
      'modalHTML': undefined,
18
      'menuItemTitleClass': 'multi-select-menuitem--titled',
19
      'activeClass': 'multi-select-container--open',
20
      'noneText': '-- Select --',
21
      'allText': undefined,
22
      'presets': undefined,
23
      'positionedMenuClass': 'multi-select-container--positioned',
24
      'positionMenuWithin': undefined,
25
      'viewportBottomGutter': 20,
26
      'menuMinHeight': 200
27
    };
28
29
  /**
30
   * @constructor
31
   */
32
  function MultiSelect(element, options) {
33
    this.element = element;
34
    this.$element = $(element);
35
    this.settings = $.extend( {}, defaults, options );
36
    this._defaults = defaults;
37
    this._name = pluginName;
38
    this.init();
39
  }
40
41
  function arraysAreEqual(array1, array2) {
42
    if ( array1.length != array2.length ){
43
      return false;
44
    }
45
46
    array1.sort();
47
    array2.sort();
48
49
    for ( var i = 0; i < array1.length; i++ ){
50
      if ( array1[i] !== array2[i] ){
51
        return false;
52
      }
53
    }
54
55
    return true;
56
  }
57
58
  $.extend(MultiSelect.prototype, {
59
60
    init: function() {
61
      this.checkSuitableInput();
62
      this.findLabels();
63
      this.constructContainer();
64
      this.constructButton();
65
      this.constructMenu();
66
      this.constructModal();
67
68
      this.setUpBodyClickListener();
69
      this.setUpLabelsClickListener();
70
71
      this.$element.hide();
72
    },
73
74
    checkSuitableInput: function(text) {
75
      if ( this.$element.is('select[multiple]') === false ) {
76
        throw new Error('$.multiSelect only works on <select multiple> elements');
77
      }
78
    },
79
80
    findLabels: function() {
81
      this.$labels = $('label[for="' + this.$element.attr('id') + '"]');
82
    },
83
84
    constructContainer: function() {
85
      this.$container = $(this.settings['containerHTML']);
86
      this.$element.data('multi-select-container', this.$container);
87
      this.$container.insertAfter(this.$element);
88
    },
89
90
    constructButton: function() {
91
      var _this = this;
92
      this.$button = $(this.settings['buttonHTML']);
93
      this.$button.attr({
94
        'role': 'button',
95
        'aria-haspopup': 'true',
96
        'tabindex': 0,
97
        'aria-label': this.$labels.eq(0).text()
98
      })
99
      .on('keydown.multiselect', function(e) {
100
        var key = e.which;
101
        var returnKey = 13;
102
        var escapeKey = 27;
103
        var spaceKey = 32;
104
        var downArrow = 40;
105
        if ((key === returnKey) || (key === spaceKey)) {
106
          e.preventDefault();
107
          _this.$button.click();
108
        } else if (key === downArrow) {
109
          e.preventDefault();
110
          _this.menuShow();
111
          var group = _this.$presets || _this.$menuItems;
112
          group.children(":first").focus();
113
        } else if (key === escapeKey) {
114
          _this.menuHide();
115
        }
116
      }).on('click.multiselect', function(e) {
117
        _this.menuToggle();
118
      })
119
      .appendTo(this.$container);
120
121
      this.$element.on('change.multiselect', function() {
122
        _this.updateButtonContents();
123
      });
124
125
      this.updateButtonContents();
126
    },
127
128
    updateButtonContents: function() {
129
      var _this = this;
130
      var options = [];
131
      var selected = [];
132
133
      this.$element.find('option').each(function() {
134
        var text = /** @type string */ ($(this).text());
135
        options.push(text);
136
        if ($(this).is(':selected')) {
137
          selected.push( $.trim(text) );
138
        }
139
      });
140
141
      this.$button.empty();
142
143
      if (selected.length == 0) {
144
        this.$button.text( this.settings['noneText'] );
145
      } else if ( (selected.length === options.length) && this.settings['allText']) {
146
        this.$button.text( this.settings['allText'] );
147
      } else {
148
          if(selected.length === 1){
149
              this.$button.text( selected.length + " rukopis");
150
          } else if(selected.length < 5){
151
              this.$button.text( selected.length + " rukopisy");
152
          } else {
153
              this.$button.text( selected.length + " rukopisů");
154
          }
155
      }
156
    },
157
158
    constructMenu: function() {
159
      var _this = this;
160
161
      this.$menu = $(this.settings['menuHTML']);
162
      this.$menu.attr({
163
        'role': 'menu'
164
      }).on('keyup.multiselect', function(e){
165
        var key = e.which;
166
        var escapeKey = 27;
167
        if (key === escapeKey) {
168
          _this.menuHide();
169
          _this.$button.focus();
170
        }
171
      })
172
      .appendTo(this.$container);
173
174
      this.constructMenuItems();
175
176
      if ( this.settings['presets'] ) {
177
        this.constructPresets();
178
      }
179
    },
180
181
    constructMenuItems: function() {
182
      var _this = this;
183
184
      this.$menuItems = $(this.settings['menuItemsHTML']);
185
      this.$menu.append(this.$menuItems);
186
187
      this.$element.on('change.multiselect', function(e, internal) {
188
        // Don't need to update the menu items if this
189
        // change event was fired by our tickbox handler.
190
        if(internal !== true){
191
          _this.updateMenuItems();
192
        }
193
      });
194
195
      this.updateMenuItems();
196
    },
197
198
    updateMenuItems: function() {
199
      var _this = this;
200
      this.$menuItems.empty();
201
202
      this.$element.children('optgroup,option').each(function(index, element) {
203
        var $item;
204
        if (element.nodeName === 'OPTION') {
205
          $item = _this.constructMenuItem($(element), index);
206
          _this.$menuItems.append($item);
207
        } else {
208
          _this.constructMenuItemsGroup($(element), index);
209
        }
210
      });
211
    },
212
213
    upDown: function(type, e) {
214
    var key = e.which;
215
    var upArrow = 38;
216
    var downArrow = 40;
217
218
    if (key === upArrow) {
219
      e.preventDefault();
220
      var prev = $(e.currentTarget).prev();
221
      if (prev.length) {
222
        prev.focus();
223
      } else if (this.$presets && type === 'menuitem') {
224
        this.$presets.children(':last').focus();
225
      } else {
226
        this.$button.focus();
227
      }
228
    } else if (key === downArrow) {
229
      e.preventDefault();
230
      var next = $(e.currentTarget).next();
231
      if (next.length || type === 'menuitem') {
232
        next.focus();
233
      } else {
234
        this.$menuItems.children(':first').focus();
235
      }
236
    }
237
  },
238
239
    constructPresets: function() {
240
      var _this = this;
241
      this.$presets = $(this.settings['presetsHTML']);
242
      this.$menu.prepend(this.$presets);
243
244
      $.each(this.settings['presets'], function(i, preset){
245
        var unique_id = _this.$element.attr('name') + '_preset_' + i;
246
        var $item = $(_this.settings['menuItemHTML'])
247
          .attr({
248
            'for': unique_id,
249
            'role': 'menuitem'
250
          })
251
          .text(' ' + preset.name)
252
          .on('keydown.multiselect', _this.upDown.bind(_this, 'preset'))
253
          .appendTo(_this.$presets);
254
255
        var $input = $('<input>')
256
          .attr({
257
            'type': 'radio',
258
            'name': _this.$element.attr('name') + '_presets',
259
            'id': unique_id
260
          })
261
          .prependTo($item);
262
263
        if (preset.all) {
264
          preset.options = [];
265
          _this.$element.find('option').each(function() {
266
            var val = $(this).val();
267
            preset.options.push(val);
268
          });
269
        }
270
271
        $input.on('change.multiselect', function(){
272
          _this.$element.val(preset.options);
273
          _this.$element.trigger('change');
274
        });
275
      });
276
277
      this.$element.on('change.multiselect', function() {
278
        _this.updatePresets();
279
      });
280
281
      this.updatePresets();
282
    },
283
284
    updatePresets: function() {
285
      var _this = this;
286
287
      $.each(this.settings['presets'], function(i, preset){
288
        var unique_id = _this.$element.attr('name') + '_preset_' + i;
289
        var $input = _this.$presets.find('#' + unique_id);
290
291
        if ( arraysAreEqual(preset.options || [], _this.$element.val() || []) ){
292
          $input.prop('checked', true);
293
        } else {
294
          $input.prop('checked', false);
295
        }
296
      });
297
    },
298
299
    constructMenuItemsGroup: function($optgroup, optgroup_index) {
300
      var _this = this;
301
302
      $optgroup.children('option').each(function(option_index, option) {
303
        var $item = _this.constructMenuItem($(option), optgroup_index + '_' + option_index);
304
        var cls = _this.settings['menuItemTitleClass'];
305
        if (option_index !== 0) {
306
          cls += 'sr';
307
        }
308
        $item.addClass(cls).attr('data-group-title', $optgroup.attr('label'));
309
        _this.$menuItems.append($item);
310
      });
311
    },
312
313
    constructMenuItem: function($option, option_index) {
314
      var unique_id = this.$element.attr('name') + '_' + option_index;
315
      var $item = $(this.settings['menuItemHTML'])
316
        .attr({
317
          'for': unique_id,
318
          'role': 'menuitem'
319
        })
320
        .on('keydown.multiselect', this.upDown.bind(this, 'menuitem'))
321
        .text(' ' + $option.text());
322
323
      var $input = $('<input>')
324
        .attr({
325
          'type': 'checkbox',
326
          'id': unique_id,
327
          'value': $option.val()
328
        })
329
        .prependTo($item);
330
331
      if ( $option.is(':disabled') ) {
332
        $input.attr('disabled', 'disabled');
333
      }
334
      if ( $option.is(':selected') ) {
335
        $input.prop('checked', 'checked');
336
      }
337
338
      $input.on('change.multiselect', function() {
339
        if ($(this).prop('checked')) {
340
          $option.prop('selected', true);
341
        } else {
342
          $option.prop('selected', false);
343
        }
344
345
        // .prop() on its own doesn't generate a change event.
346
        // Other plugins might want to do stuff onChange.
347
        $option.trigger('change', [true]);
348
      });
349
350
      return $item;
351
    },
352
353
    constructModal: function() {
354
      var _this = this;
355
356
      if (this.settings['modalHTML']) {
357
        this.$modal = $(this.settings['modalHTML']);
358
        this.$modal.on('click.multiselect', function(){
359
          _this.menuHide();
360
        })
361
        this.$modal.insertBefore(this.$menu);
362
      }
363
    },
364
365
    setUpBodyClickListener: function() {
366
      var _this = this;
367
368
      // Hide the $menu when you click outside of it.
369
      $('html').on('click.multiselect', function(){
370
        _this.menuHide();
371
      });
372
373
      // Stop click events from inside the $button or $menu from
374
      // bubbling up to the body and closing the menu!
375
      this.$container.on('click.multiselect', function(e){
376
        e.stopPropagation();
377
      });
378
    },
379
380
    setUpLabelsClickListener: function() {
381
      var _this = this;
382
      this.$labels.on('click.multiselect', function(e) {
383
        e.preventDefault();
384
        e.stopPropagation();
385
        _this.menuToggle();
386
      });
387
    },
388
389
    menuShow: function() {
390
      $('html').trigger('click.multiselect'); // Close any other open menus
391
      this.$container.addClass(this.settings['activeClass']);
392
393
      if ( this.settings['positionMenuWithin'] && this.settings['positionMenuWithin'] instanceof $ ) {
394
        var menuLeftEdge = this.$menu.offset().left + this.$menu.outerWidth();
395
        var withinLeftEdge = this.settings['positionMenuWithin'].offset().left +
396
          this.settings['positionMenuWithin'].outerWidth();
397
398
        if ( menuLeftEdge > withinLeftEdge ) {
399
          this.$menu.css( 'width', (withinLeftEdge - this.$menu.offset().left) );
400
          this.$container.addClass(this.settings['positionedMenuClass']);
401
        }
402
      }
403
404
      var menuBottom = this.$menu.offset().top + this.$menu.outerHeight();
405
      var viewportBottom = $(window).scrollTop() + $(window).height();
406
      if ( menuBottom > viewportBottom - this.settings['viewportBottomGutter'] ) {
407
        this.$menu.css({
408
          'maxHeight': Math.max(
409
            viewportBottom - this.settings['viewportBottomGutter'] - this.$menu.offset().top,
410
            this.settings['menuMinHeight']
411
          ),
412
          'overflow': 'scroll'
413
        });
414
      } else {
415
        this.$menu.css({
416
          'maxHeight': '',
417
          'overflow': ''
418
        });
419
      }
420
    },
421
422
    menuHide: function() {
423
      this.$container.removeClass(this.settings['activeClass']);
424
      this.$container.removeClass(this.settings['positionedMenuClass']);
425
      this.$menu.css('width', 'auto');
426
    },
427
428
    menuToggle: function() {
429
      if ( this.$container.hasClass(this.settings['activeClass']) ) {
430
        this.menuHide();
431
      } else {
432
        this.menuShow();
433
      }
434
    }
435
436
  });
437
438
  $.fn[ pluginName ] = function(options) {
439
    return this.each(function() {
440
      if ( !$.data(this, "plugin_" + pluginName) ) {
441
        $.data(this, "plugin_" + pluginName,
442
          new MultiSelect(this, options) );
443
      }
444
    });
445
  };
446
447
})(jQuery);