PluginProbe
Orderable – Restaurant & Food Ordering System / 0.2.0
Orderable – Restaurant & Food Ordering System v0.2.0
0.1.4 0.1.5 0.2.0 1.0.0 1.1.0 1.1.1 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.19.1 1.19.2 1.2.0 1.20.0 1.20.1 All 44 releases
orderable / assets / vendor / jquery-multi-select / jquery.multi-select.js

jquery.multi-select.js in Orderable – Restaurant & Food Ordering System 0.2.0, at assets/vendor/jquery-multi-select/jquery.multi-select.js

434 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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': '-- 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().replace(/\u2013|\u2014/g, ""));
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);
434