PageSourceSearch

https://jaisteel.in/assets/js/multiSelect.js

js jaisteel.in collected 2026-09-24 15:02:44 UTC 12,894 bytes, 388 lines download raw bytes

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