PageSourceSearch

https://bhadanitech.com/wp-content/themes/Bhadani/multi-select.js

js bhadanitech.com collected 2026-10-09 03:44:34 UTC 12,456 bytes, 433 lines download raw bytes

1// 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': 'All',
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        this.$button.text( selected.join(', ') );
149      }
150    },
151
152    constructMenu: function() {
153      var _this = this;
154
155      this.$menu = $(this.settings['menuHTML']);
156      this.$menu.attr({
157        'role': 'menu'
158      }).on('keyup.multiselect', function(e){
159        var key = e.which;
160        var escapeKey = 27;
161        if (key === escapeKey) {
162          _this.menuHide();
163          _this.$button.focus();
164        }
165      })
166      .appendTo(this.$container);
167
168      this.constructMenuItems();
169
170      if ( this.settings['presets'] ) {
171        this.constructPresets();
172      }
173    },
174
175    constructMenuItems: function() {
176      var _this = this;
177
178      this.$menuItems = $(this.settings['menuItemsHTML']);
179      this.$menu.append(this.$menuItems);
180
181      this.$element.on('change.multiselect', function(e, internal) {
182        // Don't need to update the menu items if this
183        // change event was fired by our tickbox handler.
184        if(internal !== true){
185          _this.updateMenuItems();
186        }
187      });
188
189      this.updateMenuItems();
190    },
191
192    updateMenuItems: function() {
193      var _this = this;
194      this.$menuItems.empty();
195
196      this.$element.children('optgroup,option').each(function(index, element) {
197        var $item;
198        if (element.nodeName === 'OPTION') {
199          $item = _this.constructMenuItem($(element), index);
200          _this.$menuItems.append($item);
201        } else {
202          _this.constructMenuItemsGroup($(element), index);
203        }
204      });
205    },
206
207    upDown: function(type, e) {
208    var key = e.which;
209    var upArrow = 38;
210    var downArrow = 40;
211
212    if (key === upArrow) {
213      e.preventDefault();
214      var prev = $(e.currentTarget).prev();
215      if (prev.length) {
216        prev.focus();
217      } else if (this.$presets && type === 'menuitem') {
218        this.$presets.children(':last').focus();
219      } else {
220        this.$button.focus();
221      }
222    } else if (key === downArrow) {
223      e.preventDefault();
224      var next = $(e.currentTarget).next();
225      if (next.length || type === 'menuitem') {
226        next.focus();
227      } else {
228        this.$menuItems.children(':first').focus();
229      }
230    }
231  },
232
233    constructPresets: function() {
234      var _this = this;
235      this.$presets = $(this.settings['presetsHTML']);
236      this.$menu.prepend(this.$presets);
237
238      $.each(this.settings['presets'], function(i, preset){
239        var unique_id = _this.$element.attr('name') + '_preset_' + i;
240        var $item = $(_this.settings['menuItemHTML'])
241          .attr({
242            'for': unique_id,
243            'role': 'menuitem'
244          })
245          .text(' ' + preset.name)
246          .on('keydown.multiselect', _this.upDown.bind(_this, 'preset'))
247          .appendTo(_this.$presets);
248
249        var $input = $('<input>')
250          .attr({
251            'type': 'radio',
252            'name': _this.$element.attr('name') + '_presets',
253            'id': unique_id
254          })
255          .prependTo($item);
256
257        $input.on('change.multiselect', function(){
258          _this.$element.val(preset.options);
259          _this.$element.trigger('change');
260        });
261      });
262
263      this.$element.on('change.multiselect', function() {
264        _this.updatePresets();
265      });
266
267      this.updatePresets();
268    },
269
270    updatePresets: function() {
271      var _this = this;
272
273      $.each(this.settings['presets'], function(i, preset){
274        var unique_id = _this.$element.attr('name') + '_preset_' + i;
275        var $input = _this.$presets.find('#' + unique_id);
276
277        if ( arraysAreEqual(preset.options || [], _this.$element.val() || []) ){
278          $input.prop('checked', true);
279        } else {
280          $input.prop('checked', false);
281        }
282      });
283    },
284
285    constructMenuItemsGroup: function($optgroup, optgroup_index) {
286      var _this = this;
287
288      $optgroup.children('option').each(function(option_index, option) {
289        var $item = _this.constructMenuItem($(option), optgroup_index + '_' + option_index);
290        var cls = _this.settings['menuItemTitleClass'];
291        if (option_index !== 0) {
292          cls += 'sr';
293        }
294        $item.addClass(cls).attr('data-group-title', $optgroup.attr('label'));
295        _this.$menuItems.append($item);
296      });
297    },
298
299    constructMenuItem: function($option, option_index) {
300      var unique_id = this.$element.attr('name') + '_' + option_index;
301      var $item = $(this.settings['menuItemHTML'])
302        .attr({
303          'for': unique_id,
304          'role': 'menuitem'
305        })
306        .on('keydown.multiselect', this.upDown.bind(this, 'menuitem'))
307        .text(' ' + $option.text());
308
309      var $input = $('<input>')
310        .attr({
311          'type': 'checkbox',
312          'id': unique_id,
313          'value': $option.val()
314        })
315        .prependTo($item);
316
317      if ( $option.is(':disabled') ) {
318        $input.attr('disabled', 'disabled');
319      }
320      if ( $option.is(':selected') ) {
321        $input.prop('checked', 'checked');
322      }
323
324      $input.on('change.multiselect', function() {
325        if ($(this).prop('checked')) {
326          $option.prop('selected', true);
327        } else {
328          $option.prop('selected', false);
329        }
330
331        // .prop() on its own doesn't generate a change event.
332        // Other plugins might want to do stuff onChange.
333        $option.trigger('change', [true]);
334      });
335
336      return $item;
337    },
338
339    constructModal: function() {
340      var _this = this;
341
342      if (this.settings['modalHTML']) {
343        this.$modal = $(this.settings['modalHTML']);
344        this.$modal.on('click.multiselect', function(){
345          _this.menuHide();
346        })
347        this.$modal.insertBefore(this.$menu);
348      }
349    },
350
351    setUpBodyClickListener: function() {
352      var _this = this;
353
354      // Hide the $menu when you click outside of it.
355      $('html').on('click.multiselect', function(){
356        _this.menuHide();
357      });
358
359      // Stop click events from inside the $button or $menu from
360      // bubbling up to the body and closing the menu!
361      this.$container.on('click.multiselect', function(e){
362        e.stopPropagation();
363      });
364    },
365
366    setUpLabelsClickListener: function() {
367      var _this = this;
368      this.$labels.on('click.multiselect', function(e) {
369        e.preventDefault();
370        e.stopPropagation();
371        _this.menuToggle();
372      });
373    },
374
375    menuShow: function() {
376      $('html').trigger('click.multiselect'); // Close any other open menus
377      this.$container.addClass(this.settings['activeClass']);
378
379      if ( this.settings['positionMenuWithin'] && this.settings['positionMenuWithin'] instanceof $ ) {
380        var menuLeftEdge = this.$menu.offset().left + this.$menu.outerWidth();
381        var withinLeftEdge = this.settings['positionMenuWithin'].offset().left +
382          this.settings['positionMenuWithin'].outerWidth();
383
384        if ( menuLeftEdge > withinLeftEdge ) {
385          this.$menu.css( 'width', (withinLeftEdge - this.$menu.offset().left) );
386          this.$container.addClass(this.settings['positionedMenuClass']);
387        }
388      }
389
390      var menuBottom = this.$menu.offset().top + this.$menu.outerHeight();
391      var viewportBottom = $(window).scrollTop() + $(window).height();
392      if ( menuBottom > viewportBottom - this.settings['viewportBottomGutter'] ) {
393        this.$menu.css({
394          'maxHeight': Math.max(
395            viewportBottom - this.settings['viewportBottomGutter'] - this.$menu.offset().top,
396            this.settings['menuMinHeight']
397          ),
398          'overflow': 'scroll'
399        });
400      } else {
401        this.$menu.css({
402          'maxHeight': '',
403          'overflow': ''
404        });
405      }
406    },
407
408    menuHide: function() {
409      this.$container.removeClass(this.settings['activeClass']);
410      this.$container.removeClass(this.settings['positionedMenuClass']);
411      this.$menu.css('width', 'auto');
412    },
413
414    menuToggle: function() {
415      if ( this.$container.hasClass(this.settings['activeClass']) ) {
416        this.menuHide();
417      } else {
418        this.menuShow();
419      }
420    }
421
422  });
423
424  $.fn[ pluginName ] = function(options) {
425    return this.each(function() {
426      if ( !$.data(this, "plugin_" + pluginName) ) {
427        $.data(this, "plugin_" + pluginName,
428          new MultiSelect(this, options) );
429      }
430    });
431  };
432
433})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.