PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / assets / js / contextmenu.js
vikappointments / site / assets / js Last commit date
charts-framework 2 days ago colorpicker 2 days ago select2 2 days ago tel 2 days ago contextmenu.js 2 days ago currency.js 2 days ago index.html 2 days ago jquery-ui.draggable.min.js 2 days ago jquery-ui.min.js 2 days ago jquery-ui.sortable.min.js 2 days ago jquery.fancybox.js 2 days ago jquery.min.js 2 days ago sortablelist.js 2 days ago statuscodes.js 2 days ago toast.js 2 days ago utils.js 2 days ago vap-emparea.js 2 days ago vikappointments.js 2 days ago
contextmenu.js
809 lines
1 /**
2 * jQuery add-on used to support context menus.
3 * Here's a list of supported options.
4 *
5 * - trigger string The command that should trigger the popup menu. Accepts the
6 * following values: click|doubleclick|rightclick|hover.
7 * Click will be used by default.
8 * - placement string Where the popup should be displayed in relation to the target.
9 * Accepts the following values: auto|top|right|bottom|left.
10 * Auto will be used by default (at the mouse coordinates).
11 * - class string An optional class to use for individual styling.
12 * - buttons object[] A list of buttons to include within the popup menu. See the options
13 * of the buttons for further details.
14 * - onShow function An optional callback to invoke when the popup menu is displayed.
15 * - onHide function An optional callback to invoke when the popup menu is dismissed.
16 * - darkMode mixed Flag for dark mode layout, which accepts 3 possible values:
17 * true|false|null. Pass true to always force the dark mode, false to
18 * always use the light mode, null to auto-detect the proper mode
19 * according to the preferred theme of the browser.
20 * - clickable boolean Flag used to check whether the root element should prevent the
21 * browser selection by applying specific CSS rules. False by default.
22 * - lockScroll boolean Flag used to prevent the document scroll when the context menu
23 * pops up. True by default.
24 * - hideOnEsc boolean Choose whether the context menu should be closed when ESC key is
25 * pressed. Always true by default.
26 * - formatShortcut mixed An optional callback that can be used to format the shortcut symbols.
27 *
28 * Here's a list of options supported by the buttons. Any other property of the button will
29 * be accessible by the internal methods.
30 *
31 * - icon mixed Either a function, an image URL, an image instance or a font icon
32 * to display before the button text. In case of a function, it will be
33 * used as callback to define an image/icon at runtime.
34 * - text string The text to display for the popup menu button.
35 * - action function The callback to dispatch when the button gets clicked.
36 * - class string An optional class to use for individual styling.
37 * - disabled mixed Either a function or a boolean to check whether the button should
38 * be clicked or not. The button is never disabled by default.
39 * - visible mixed Either a function or a boolean to check whether the button should
40 * be displayed or not. The button is always visible by default.
41 * - separator boolean Flag used to check whether the popup should include a separator after the
42 * button. False by default.
43 * - shortcut mixed An array of commands to represent the shortcut that will trigger the action
44 * via keyboard. The array must contain one and only one character or symbol.
45 * The array may contain one ore more modifiers, which must be specified first.
46 *
47 * List of methods supported by the add-on.
48 *
49 * - show Manually displays the popup menu.
50 * - hide Manually disposes the popup menu.
51 * - destroy Destroys the popup attached to the element.
52 * - config Returns the configuration of the popup.
53 * - buttons Getter/setter of the popup buttons.
54 *
55 * It is possible to update each setting configuration by using the same
56 * name of the property and the related value to set. Leave the set argument
57 * empty to simply access the current property value. In example:
58 *
59 * jQuery(target).vikContextMenu( 'trigger', 'click');
60 * jQuery(target).vikContextMenu('placement', 'auto');
61 */
62 (function($) {
63 /**
64 * Popup menu trigger setup.
65 *
66 * @param object root The selector element.
67 * @param string trigger The trigger to use.
68 * @param mixed prev The previous trigger.
69 *
70 * @return string The trigger event.
71 */
72 var vikPopupMenuTrigger = function(root, trigger, prev) {
73 // check if the trigger was already registered
74 if (prev) {
75 // detach previous trigger
76 $(root).off(prev.toLowerCase());
77 }
78
79 if (!trigger) {
80 // abort in case of missing trigger
81 return false;
82 }
83
84 // normalize trigger event
85 switch (trigger.toLowerCase()) {
86 case 'mouseover':
87 case 'hover':
88 trigger = 'mouseover';
89 break;
90
91 case 'dblclick':
92 case 'doubleclick':
93 case 'double-click':
94 trigger = 'dblclick';
95 break;
96
97 case 'contextmenu':
98 case 'rightclick':
99 case 'right-click':
100 trigger = 'contextmenu';
101 break;
102
103 default:
104 trigger = 'click';
105 };
106
107 // scan all the registered elements
108 $(root).each(function() {
109 // register new trigger
110 $(this).on(trigger, function(event) {
111 // always prevent default event
112 event.preventDefault();
113
114 // open popup
115 vikPopupMenuShow(this, event);
116 });
117 });
118
119 return trigger;
120 };
121
122 /**
123 * Popup menu clickable setup.
124 *
125 * @param object root The selector element.
126 * @param boolean flag True to make the root clickable.
127 * @param mixed prev The flag previously set, if any.
128 *
129 * @return self
130 */
131 var vikPopupMenuClickable = function(root, flag, prev) {
132 if (prev) {
133 // remove CSS class used to disable the selection from root element
134 $(root).removeClass('vik-context-menu-disable-selection');
135 }
136
137 if (flag) {
138 // add CSS class to root element to disable the selection
139 $(root).addClass('vik-context-menu-disable-selection');
140 }
141
142 return root;
143 };
144
145 /**
146 * Initializes the popup menu.
147 *
148 * @param object root The selector element.
149 * @param object options A configuration object.
150 *
151 * @return self
152 */
153 var vikPopupMenuInit = function(root, options) {
154 // create default configuration
155 options = $.extend({
156 trigger: 'click',
157 placement: 'auto',
158 class: '',
159 buttons: [],
160 onShow: null,
161 onHide: null,
162 clickable: false,
163 lockScroll: true,
164 darkMode: null,
165 hideOnEsc: true,
166 formatShortcut: null,
167 }, options);
168
169 // register the popup configuration for being used later
170 vikPopupMenuConfig(root, options);
171
172 // register trigger to show the popup menu
173 options.trigger = vikPopupMenuTrigger(root, options.trigger);
174
175 // normalize buttons
176 vikPopupMenuButtons(root, options.buttons);
177
178 // handle clickable property
179 vikPopupMenuClickable(root, options.clickable);
180
181 // register callback to dispatch the action of a button when its shortcut is pressed
182 $(document).on('keydown.contextmenu.vikappointments', function(event) {
183 // ignore the event with this namespace because it will end up
184 // to catch also the plain keydown event
185 if (event.namespace == 'contextmenu.vikappointments') {
186 return true;
187 }
188
189 // go ahead only in case the focus is not help by a text field
190 if ($(document.activeElement).is('input,textarea') == true) {
191 // prevent shortcuts from catching typed characters
192 return true;
193 }
194
195 // retrieve popup configuration
196 var config = vikPopupMenuConfig(root);
197
198 // in case ESC was pressed, check if we should hide the popup
199 if (config.hideOnEsc && event.keyCode == 27) {
200 // auto-close the context menu
201 vikPopupMenuHide(root);
202 return true;
203 }
204
205 // iterate all registered buttons
206 $.each(config.buttons, (i, btn) => {
207 // make sure we have a shortcut and an action to execute
208 if (!btn.shortcut || !btn.action) {
209 // nothing to do here, go ahead
210 return true;
211 }
212
213 // check whether the shortcut is pressed
214 if (event.originalEvent.shortcut(btn.shortcut)) {
215 // launch callback to check whether the button is disabled
216 // or simply rely on the specified boolean
217 var disabled = typeof btn.disabled === 'function' ? btn.disabled(root, config) : btn.disabled;
218
219 // trigger action only in case the button is not disabled
220 if (!disabled) {
221 // stop event propagation
222 event.preventDefault();
223 event.stopPropagation();
224
225 // dispatch button action
226 btn.action(root, event);
227 }
228
229 return false;
230 }
231 });
232 });
233
234 return root;
235 };
236
237 /**
238 * Getter and setter of the popup configuration.
239 *
240 * @param object root The selector element.
241 * @param mixed data The popup configuration to set. When omitted,
242 * the method will act as a getter.
243 *
244 * @param mixed Returns the configuration when the data argument is
245 * missing. Otherwise itself will be returned.
246 */
247 var vikPopupMenuConfig = function(root, data) {
248 if (typeof data === 'undefined') {
249 // GETTER: return popup configuration.
250 // Clone the object in order to prevent manual edits to
251 // the configuration properties.
252 return Object.assign({}, $(root).data('popupConfiguration'));
253 }
254
255 // SETTER: update popup configuration
256 return $(root).data('popupConfiguration', data);
257 };
258
259 /**
260 * Creates and shows the popup menu.
261 *
262 * @param object root The selector element.
263 * @param Event event The dispatcher DOM event.
264 *
265 * @return self
266 */
267 var vikPopupMenuShow = function(root, event) {
268 if ($('.vik-context-menu').length) {
269 // do not go ahead in case a context menu is visible
270 return root;
271 }
272
273 // retrieve configuration
274 var config = vikPopupMenuConfig(root);
275
276 // prepare context menu structure
277 var popup = $('<div class="vik-context-menu"><ul></ul></div>');
278
279 // in case of a custom class, add it
280 if (config.class) {
281 popup.addClass(config.class);
282 }
283
284 // look for dark mode
285 if (config.darkMode === true) {
286 // turn dark mode on
287 popup.addClass('dark-mode');
288 } else if (config.darkMode === false) {
289 // suppress dark mode
290 popup.addClass('light-mode');
291 }
292
293 // iterate registered buttons and append them one by one
294 $.each(config.buttons, function(i, btn) {
295 // launch callback to check whether the button should be displayed
296 // or simply rely on the specified boolean
297 var visible = typeof btn.visible === 'function' ? btn.visible(root, config) : btn.visible;
298
299 if (!visible) {
300 // skip button and go ahead
301 return true;
302 }
303
304 // prepare button structure
305 var popupBtn = $('<a></a>');
306
307 if (btn.icon) {
308 var icon;
309
310 if (typeof btn.icon === 'function') {
311 // we have a function, launch the callback
312 // to extract the image at runtime
313 icon = btn.icon(root, config);
314 } else {
315 // use it plain
316 icon = btn.icon;
317 }
318
319 if (icon instanceof Image) {
320 // we have an image instance
321 icon = $(icon);
322 } else if (typeof icon === 'string') {
323 if (icon.indexOf('/') !== -1) {
324 // we have an image URL
325 icon = $('<img>').attr('src', icon);
326 } else {
327 // we probably have a font icon
328 icon = $('<i></i>').addClass(icon);
329 }
330 }
331
332 // leave as is in case a jQuery instance was passed
333
334 // wrap icon in a parent and append all to button
335 popupBtn.append($('<span class="button-icon"></span>').append(icon));
336 }
337
338 // insert text button
339 popupBtn.append($('<span class="button-text"></span>').html(btn.text));
340
341 // check if the button specified a shortcut
342 if (btn.shortcut) {
343 // map shortcut elements
344 var cmd = btn.shortcut.map(function(k) {
345 var keyCode = k;
346
347 switch (k) {
348 case 'alt': k = "&#8997;"; break;
349 case 'ctrl': k = "&#8963;"; break;
350 case 'shift': k = "&#8679;"; break;
351 case 'meta': k = "&#8984;"; break;
352 // backspace
353 case 8: k = '<i class="fas fa-backspace"></i>'; break;
354 // enter
355 case 13: k = '&#9166;'; break;
356 // space
357 case 32: k = 'Space'; break;
358 // arrow up
359 case 37: k = '<i class="fas fa-long-arrow-alt-left"></i>'; break;
360 // arrow up
361 case 38: k = '<i class="fas fa-long-arrow-alt-up"></i>'; break;
362 // arrow right
363 case 39: k = '<i class="fas fa-long-arrow-alt-right"></i>'; break;
364 // arrow down
365 case 40: k = '<i class="fas fa-long-arrow-alt-down"></i>'; break;
366 // character
367 default: k = typeof k === 'string' ? k.toUpperCase() : '';
368 }
369
370 // look for a custom function used to format shortcuts
371 if (typeof config.formatShortcut === 'function') {
372 // launch the callback
373 k = config.formatShortcut(keyCode, k);
374 }
375
376 return k;
377 });
378
379 cmd = cmd.join('');
380
381 // wrap the shortcut between parenthesis in case of no modifiers
382 if (cmd.length == 1) {
383 cmd = '(' + cmd + ')';
384 }
385
386 // insert shortcut button
387 popupBtn.append($('<span class="button-shortcut"></span>').html(cmd));
388 }
389
390 // launch callback to check whether the button should be disabled
391 // or simply rely on the specified boolean
392 var disabled = typeof btn.disabled === 'function' ? btn.disabled(root, config) : btn.disabled;
393
394 // check whether the button is disabled
395 if (disabled) {
396 popupBtn.addClass('disabled');
397 } else {
398 // register button click event
399 popupBtn.on('click', function(event) {
400 // look for an action callback
401 if (btn.action) {
402 // dispatch callback
403 btn.action(root, event);
404 }
405
406 // always dismiss the popup when a button gets clicked
407 vikPopupMenuHide(root);
408 });
409 }
410
411 // in case of a custom class, add it
412 if (btn.class) {
413 popupBtn.addClass(btn.class);
414 }
415
416 // wrap button within a parent for <ul> compliance
417 var popupItem = $('<li></li>').append(popupBtn);
418
419 // in case of a separator, add a specific class
420 if (btn.separator) {
421 popupItem.addClass('separator');
422 }
423
424 // wrap button within a parent and add to popup
425 popup.find('ul').append(popupItem);
426 });
427
428 // hide the popup before appending it
429 popup.hide();
430
431 // append button to body
432 $('body').append(popup);
433
434 // calculate popup position
435 vikPopupMenuCalcPosition(root, popup, event);
436
437 if (config.lockScroll) {
438 // prevent document from scrolling
439 $('body').addClass('lock-scroll');
440 }
441
442 // show popup
443 popup.show();
444
445 // look for a specific callback to be triggered on opening
446 if (config.onShow) {
447 // trigger show callback
448 config.onShow(root, popup);
449 }
450
451 // Register callback to auto dismiss the popup when clicked outside.
452 // Use mousedown event because it will be execured before any other
453 // supported trigger, so that the context menus can be shown on cascade.
454 $(document).on('mousedown.contextmenu.vikappointments', function(event) {
455 // ignore the event with this namespace because it will end up
456 // to catch also the plain mousedown event
457 if (event.namespace == 'contextmenu.vikappointments') {
458 return false;
459 }
460
461 if (!popup.is(':visible')) {
462 // dialog not visible
463 return false;
464 }
465
466 // get list of buttons
467 var links = popup.find('a');
468
469 // make sure we haven't clicked the popup or a link
470 if (!popup.is(event.target) && popup.has(event.target).length === 0
471 && !links.is(event.target) && links.has(event.target).length === 0) {
472 // auto close popup when clicked outside
473 vikPopupMenuHide(root);
474
475 event.stopPropagation();
476 event.preventDefault();
477
478 return false;
479 }
480 });
481
482 return root;
483 };
484
485 /**
486 * Hides the popup menu.
487
488 * @param object root The selector element.
489 *
490 * @return self
491 */
492 var vikPopupMenuHide = function(root) {
493 // go ahead only in case a popup is open
494 if ($('.vik-context-menu').length) {
495 // destroy any existing popup menu because we do not support
496 // more than a popup per time
497 $('.vik-context-menu').remove();
498
499 // always restore scroll functions
500 $('body').removeClass('lock-scroll');
501
502 // turn off proxy
503 $(document).off('mousedown.contextmenu.vikappointments');
504
505 // get popup configuration
506 var config = vikPopupMenuConfig(root);
507
508 // look for a specific callback to be triggered on dismiss
509 if (config.onHide) {
510 // trigger hide callback
511 config.onHide(root);
512 }
513 }
514
515 return root;
516 };
517
518 /**
519 * Destroys the popup menu.
520
521 * @param object root The selector element.
522 *
523 * @return self
524 */
525 var vikPopupMenuDestroy = function(root) {
526 // in case the popup was open, close it first
527 vikPopupMenuHide(root);
528
529 // detach keyboard listener
530 $(document).off('keydown.contextmenu.vikappointments');
531
532 // remove CSS class used to disable the selection from root element
533 $(root).removeClass('vik-context-menu-disable-selection');
534
535 // get popup configuration
536 var config = vikPopupMenuConfig(root);
537
538 // detach previous event without attaching a new one
539 vikPopupMenuTrigger(root, null, config.trigger);
540
541 // detach clickable property
542 vikPopupMenuClickable(root, null, config.clickable);
543
544 // then destroy the registered data
545 return vikPopupMenuConfig(root, null);
546 };
547
548 /**
549 * Getter and setter of the popup buttons.
550 *
551 * @param object root The selector element.
552 * @param mixed data The popup buttons to set. When omitted,
553 * the method will act as a getter.
554 *
555 * @param mixed Returns the buttons list when the data argument is
556 * missing. Otherwise itself will be returned.
557 */
558 var vikPopupMenuButtons = function(root, data) {
559 // get configuration
560 var config = vikPopupMenuConfig(root);
561
562 if (typeof data === 'undefined') {
563 // return popup buttons
564 return config.buttons;
565 }
566
567 // make sure the buttons property is an Array
568 if (!Array.isArray(data)) {
569 throw 'Invalid buttons, an Array was expected.';
570 }
571
572 // set specified buttons
573 config.buttons = data;
574
575 // iterate all buttons
576 for (var i = 0; i < config.buttons.length; i++) {
577 // create default button properties
578 config.buttons[i] = $.extend({
579 icon: null,
580 text: '',
581 action: null,
582 shortcut: null,
583 class: '',
584 disabled: false,
585 visible: true,
586 separator: false,
587 }, config.buttons[i]);
588
589 // make sure we have an array
590 if (!Array.isArray(config.buttons[i].shortcut)) {
591 // invalid shortcut
592 config.buttons[i].shortcut = null;
593 }
594 }
595
596 // register configuration
597 return vikPopupMenuConfig(root, config);
598 };
599
600 /**
601 * Calculates and sets the proper position of the popup.
602 *
603 * @param object root The selector element.
604 * @param mixed popup The popup element.
605 * @param mixed event The dispatcher DOM event.
606 *
607 * @param self
608 */
609 var vikPopupMenuCalcPosition = function(root, popup, event) {
610 // get popup configuration
611 var config = vikPopupMenuConfig(root);
612
613 // in case of "auto" placement, we need to make sure
614 // that we own an event to access the mouse coordinates
615 if (config.placement == 'auto' && !event) {
616 // no event was passed, fallback to right
617 config.placement = 'right';
618 }
619
620 // calculate root offset
621 var rootOffset = $(root).offset();
622 // calculate root size
623 var rootWidth = $(root).outerWidth();
624 var rootHeight = $(root).outerHeight();
625 // calculate popup size
626 var popupWidth = $(popup).outerWidth();
627 var popupHeight = $(popup).outerHeight();
628
629 var x, y;
630
631 // display popup above the root
632 if (config.placement == 'top') {
633 x = rootOffset.left + rootWidth / 2 - popupWidth / 2;
634 y = rootOffset.top - popupHeight - 4;
635 }
636 // display the popup below the root
637 else if (config.placement == 'bottom') {
638 x = rootOffset.left + rootWidth / 2 - popupWidth / 2;
639 y = rootOffset.top + rootHeight + 4;
640 }
641 // display the popup before the root
642 else if (config.placement == 'left') {
643 x = rootOffset.left - popupWidth - 4;
644 y = rootOffset.top + rootHeight / 2 - popupHeight / 2;
645 }
646 // display the popup after the root
647 else if (config.placement == 'right') {
648 x = rootOffset.left + rootWidth + 4;
649 y = rootOffset.top + rootHeight / 2 - popupHeight / 2;
650 }
651 // display the popup at the mouse coordinates
652 else {
653 x = event.pageX;
654 y = event.pageY;
655 }
656
657 // calculate screen size
658 var screenWidth = $(window).width();
659 var screenHeight = $(window).height();
660 // calculate window scrolls
661 var windowScrollLeft = $(window).scrollLeft();
662 var windowScrollTop = $(window).scrollTop();
663
664 // use 4 pixel as minimum value
665 x = Math.max(4, x);
666 y = Math.max(4, y);
667
668 // make sure the popup doesn't exceed the screen width
669 if (x + popupWidth + 4 > screenWidth) {
670 x = screenWidth - popupWidth - 4;
671 }
672
673 // make sure the popup doesn't exceed the screen height
674 if (y + popupHeight + 4 > screenHeight + windowScrollTop) {
675 y = screenHeight - popupHeight - 4 + windowScrollTop;
676 }
677
678 $(popup).css('top', y).css('left', x);
679
680 return root;
681 };
682
683 // register listener to auto-close the popup when clicked outside
684 $(document).on('mousedown', function() {
685 // we need to propagate the event with a proxy so that we can safely
686 // detach the registered callbacks when the popup gets closed
687 $(document).trigger('mousedown.contextmenu.vikappointments');
688 });
689
690 // register listener to dispatch the actions of the buttons via keyboard
691 $(document).on('keydown', function() {
692 // we need to propagate the event with a proxy so that we can safely
693 // detach the registered callbacks when the popup gets destroyed
694 $(document).trigger('keydown.contextmenu.vikappointments');
695 });
696
697 // register the jQuery callback
698 $.fn.vikContextMenu = function(method, data) {
699 if (!method) {
700 method = {};
701 }
702
703 // immediately exit in case of no elements found
704 if ($(this).length == 0) {
705 return this;
706 }
707
708 // initialize popup events
709 if (typeof method === 'object') {
710 return vikPopupMenuInit(this, method);
711 }
712 // check if we should dismiss the popup
713 else if (typeof method === 'string' && method.match(/^(close|dismiss|hide)$/i)) {
714 return vikPopupMenuHide(this);
715 }
716 // check if we should open the popup
717 else if (typeof method === 'string' && method.match(/^(show|open)$/i)) {
718 return vikPopupMenuShow(this);
719 }
720 // check if we destroy the popup
721 else if (typeof method === 'string' && method.match(/^(destroy)$/i)) {
722 return vikPopupMenuDestroy(this);
723 }
724 // check if we should return the popup configuration
725 else if (typeof method === 'string' && method.match(/^(config|configuration|options)$/i)) {
726 return vikPopupMenuConfig(this);
727 }
728 // check if we should handle with the popup buttons
729 else if (typeof method === 'string' && method.match(/^(buttons)$/i)) {
730 // use getter/setter according to the specified arguments
731 return vikPopupMenuButtons(this, data);
732 }
733 // fallback to configuration setting getter/setter
734 else {
735 // access configuration
736 var config = vikPopupMenuConfig(this);
737
738 // check if the second argument was passed
739 if (typeof data !== 'undefined') {
740 if (method == 'trigger') {
741 // register trigger before updating the configuration
742 data = vikPopupMenuTrigger(this, data, config.trigger);
743 } else if (method == 'clickable') {
744 // handle clickable property
745 vikPopupMenuClickable(this, data, config.clickable);
746 }
747
748 // register argument within configuration
749 config[method] = data;
750
751 // refresh configuration and return self instance
752 return vikPopupMenuConfig(this, config);
753 }
754
755 // return configuration setting
756 return config[method];
757 }
758
759 return this;
760 };
761
762 /**
763 * Checks if the KeyBoard event matches the given shortcut.
764 *
765 * @param array keys The shortcut representation.
766 *
767 * @return boolean True if matches, otherwise false.
768 */
769 KeyboardEvent.prototype.shortcut = function(keys) {
770 // get modifiers list
771 var modifiers = keys.slice(0);
772 // pop character from modifiers
773 var keyCode = modifiers.pop();
774
775 if (typeof keyCode === 'string') {
776 // get ASCII
777 keyCode = keyCode.toUpperCase().charCodeAt(0);
778 }
779
780 // make sure the modifiers are lower case
781 modifiers = modifiers.map(function(mod) {
782 return mod.toLowerCase();
783 });
784
785 var ok = false;
786
787 // validate key code
788 if (this.keyCode == keyCode) {
789 // validate modifiers
790 ok = true;
791 var lookup = ['meta', 'shift', 'alt', 'ctrl'];
792
793 for (var i = 0; i < lookup.length && ok; i++) {
794 // check if modifiers is pressed
795 var mod = this[lookup[i] + 'Key'];
796
797 if (mod) {
798 // if pressed, the shortcut must specify it
799 ok &= modifiers.indexOf(lookup[i]) !== -1;
800 } else {
801 // if not pressed, the shortcut must not include it
802 ok &= modifiers.indexOf(lookup[i]) === -1;
803 }
804 }
805 }
806
807 return ok;
808 }
809 })(jQuery);