From 90d8c85ec3273a2a428a6f41d2928e96fb801363 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Wed, 21 Mar 2018 15:04:55 +0800 Subject: [PATCH] MDL-61138 javascript: stop duplicate custom events firing --- .../build/custom_interaction_events.min.js | 2 +- lib/amd/src/custom_interaction_events.js | 66 ++++++++++++++++--- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/amd/build/custom_interaction_events.min.js b/lib/amd/build/custom_interaction_events.min.js index dcf7563577b..8c7b7b15c95 100644 --- a/lib/amd/build/custom_interaction_events.min.js +++ b/lib/amd/build/custom_interaction_events.min.js @@ -1 +1 @@ -define(["jquery","core/key_codes"],function(a,b){var c={activate:"cie:activate",keyboardActivate:"cie:keyboardactivate",escape:"cie:escape",down:"cie:down",up:"cie:up",home:"cie:home",end:"cie:end",next:"cie:next",previous:"cie:previous",asterix:"cie:asterix",scrollLock:"cie:scrollLock",scrollTop:"cie:scrollTop",scrollBottom:"cie:scrollBottom",ctrlPageUp:"cie:ctrlPageUp",ctrlPageDown:"cie:ctrlPageDown",enter:"cie:enter"},d=function(a,b){return b=b||[],!(!b.length||b.indexOf(a)===-1)},e=function(a){return a.shiftKey||a.metaKey||a.altKey||a.ctrlKey},f=function(b,c,d){b.off("keydown."+c).on("keydown."+c,function(b){e(b)||b.keyCode==d&&a(b.target).trigger(c,[{originalEvent:b}])})},g=function(d){d.off("click.cie.activate").on("click.cie.activate",function(b){a(b.target).trigger(c.activate,[{originalEvent:b}])}),d.off("keydown.cie.activate").on("keydown.cie.activate",function(d){e(d)||d.keyCode!=b.enter&&d.keyCode!=b.space||a(d.target).trigger(c.activate,[{originalEvent:d}])})},h=function(d){d.off("keydown.cie.keyboardactivate").on("keydown.cie.keyboardactivate",function(d){e(d)||d.keyCode!=b.enter&&d.keyCode!=b.space||a(d.target).trigger(c.keyboardActivate,[{originalEvent:d}])})},i=function(a){f(a,c.escape,b.escape)},j=function(a){f(a,c.down,b.arrowDown)},k=function(a){f(a,c.up,b.arrowUp)},l=function(a){f(a,c.home,b.home)},m=function(a){f(a,c.end,b.end)},n=function(d){var e="rtl"==a("html").attr("dir")?b.arrowLeft:b.arrowRight;f(d,c.next,e)},o=function(d){var e="rtl"==a("html").attr("dir")?b.arrowRight:b.arrowLeft;f(d,c.previous,e)},p=function(a){f(a,c.asterix,b.asterix)},q=function(a){a.off("scroll.cie.scrollTop").on("scroll.cie.scrollTop",function(b){var d=a.scrollTop();0===d&&a.trigger(c.scrollTop,[{originalEvent:b}])})},r=function(a){a.off("scroll.cie.scrollBottom").on("scroll.cie.scrollBottom",function(b){var d=a.scrollTop(),e=a.innerHeight(),f=a[0].scrollHeight;d+e>=f&&a.trigger(c.scrollBottom,[{originalEvent:b}])})},s=function(a){a.off("DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock").on("DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock",function(b){var d=a.scrollTop(),e=a[0].scrollHeight,f=a.height(),g="DOMMouseScroll"==b.type?b.originalEvent.detail*-40:b.originalEvent.wheelDelta,h=g>0;return!h&&-g>e-f-d?(a.scrollTop(e),b.stopPropagation(),b.preventDefault(),b.returnValue=!1,a.trigger(c.scrollLock,[{originalEvent:b}]),!1):!(h&&g>d)||(a.scrollTop(0),b.stopPropagation(),b.preventDefault(),b.returnValue=!1,a.trigger(c.scrollLock,[{originalEvent:b}]),!1)})},t=function(d){d.off("keydown.cie.ctrlpageup").on("keydown.cie.ctrlpageup",function(d){d.ctrlKey&&d.keyCode==b.pageUp&&a(d.target).trigger(c.ctrlPageUp,[{originalEvent:d}])})},u=function(d){d.off("keydown.cie.ctrlpagedown").on("keydown.cie.ctrlpagedown",function(d){d.ctrlKey&&d.keyCode==b.pageDown&&a(d.target).trigger(c.ctrlPageDown,[{originalEvent:d}])})},v=function(a){f(a,c.enter,b.enter)},w=function(){var a={};return a[c.activate]=g,a[c.keyboardActivate]=h,a[c.escape]=i,a[c.down]=j,a[c.up]=k,a[c.home]=l,a[c.end]=m,a[c.next]=n,a[c.previous]=o,a[c.asterix]=p,a[c.scrollLock]=s,a[c.scrollTop]=q,a[c.scrollBottom]=r,a[c.ctrlPageUp]=t,a[c.ctrlPageDown]=u,a[c.enter]=v,a},x=function(b,c){b=a(b),c=c||[],b.length&&c.length&&a.each(w(),function(a,e){d(a,c)&&e(b)})};return{define:x,events:c}}); \ No newline at end of file +define(["jquery","core/key_codes"],function(a,b){var c={activate:"cie:activate",keyboardActivate:"cie:keyboardactivate",escape:"cie:escape",down:"cie:down",up:"cie:up",home:"cie:home",end:"cie:end",next:"cie:next",previous:"cie:previous",asterix:"cie:asterix",scrollLock:"cie:scrollLock",scrollTop:"cie:scrollTop",scrollBottom:"cie:scrollBottom",ctrlPageUp:"cie:ctrlPageUp",ctrlPageDown:"cie:ctrlPageDown",enter:"cie:enter"},d={},e=function(a,b){return b=b||[],!(!b.length||b.indexOf(a)===-1)},f=function(a){return a.shiftKey||a.metaKey||a.altKey||a.ctrlKey},g=function(b,c){var e="";return c.hasOwnProperty("originalEvent")?(e="triggeredCustom_"+b,c.originalEvent.hasOwnProperty(e)?void 0:(c.originalEvent[e]=!0,void a(c.target).trigger(b,[{originalEvent:c}]))):(e=""+b+c.type+c.timeStamp,void(d.hasOwnProperty(e)||(d[e]=!0,a(c.target).trigger(b,[{originalEvent:c}]))))},h=function(a,b,c){a.off("keydown."+b).on("keydown."+b,function(a){f(a)||a.keyCode==c&&g(b,a)})},i=function(a){a.off("click.cie.activate").on("click.cie.activate",function(a){g(c.activate,a)}),a.off("keydown.cie.activate").on("keydown.cie.activate",function(a){f(a)||a.keyCode!=b.enter&&a.keyCode!=b.space||g(c.activate,a)})},j=function(a){a.off("keydown.cie.keyboardactivate").on("keydown.cie.keyboardactivate",function(a){f(a)||a.keyCode!=b.enter&&a.keyCode!=b.space||g(c.keyboardActivate,a)})},k=function(a){h(a,c.escape,b.escape)},l=function(a){h(a,c.down,b.arrowDown)},m=function(a){h(a,c.up,b.arrowUp)},n=function(a){h(a,c.home,b.home)},o=function(a){h(a,c.end,b.end)},p=function(d){var e="rtl"==a("html").attr("dir")?b.arrowLeft:b.arrowRight;h(d,c.next,e)},q=function(d){var e="rtl"==a("html").attr("dir")?b.arrowRight:b.arrowLeft;h(d,c.previous,e)},r=function(a){h(a,c.asterix,b.asterix)},s=function(a){a.off("scroll.cie.scrollTop").on("scroll.cie.scrollTop",function(b){var d=a.scrollTop();0===d&&g(c.scrollTop,b)})},t=function(a){a.off("scroll.cie.scrollBottom").on("scroll.cie.scrollBottom",function(b){var d=a.scrollTop(),e=a.innerHeight(),f=a[0].scrollHeight;d+e>=f&&g(c.scrollBottom,b)})},u=function(a){a.off("DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock").on("DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock",function(b){var d=a.scrollTop(),e=a[0].scrollHeight,f=a.height(),h="DOMMouseScroll"==b.type?b.originalEvent.detail*-40:b.originalEvent.wheelDelta,i=h>0;return!i&&-h>e-f-d?(a.scrollTop(e),b.stopPropagation(),b.preventDefault(),b.returnValue=!1,g(c.scrollLock,b),!1):!(i&&h>d)||(a.scrollTop(0),b.stopPropagation(),b.preventDefault(),b.returnValue=!1,g(c.scrollLock,b),!1)})},v=function(a){a.off("keydown.cie.ctrlpageup").on("keydown.cie.ctrlpageup",function(a){a.ctrlKey&&a.keyCode==b.pageUp&&g(c.ctrlPageUp,a)})},w=function(a){a.off("keydown.cie.ctrlpagedown").on("keydown.cie.ctrlpagedown",function(a){a.ctrlKey&&a.keyCode==b.pageDown&&g(c.ctrlPageDown,a)})},x=function(a){h(a,c.enter,b.enter)},y=function(){var a={};return a[c.activate]=i,a[c.keyboardActivate]=j,a[c.escape]=k,a[c.down]=l,a[c.up]=m,a[c.home]=n,a[c.end]=o,a[c.next]=p,a[c.previous]=q,a[c.asterix]=r,a[c.scrollLock]=u,a[c.scrollTop]=s,a[c.scrollBottom]=t,a[c.ctrlPageUp]=v,a[c.ctrlPageDown]=w,a[c.enter]=x,a},z=function(b,c){b=a(b),c=c||[],b.length&&c.length&&a.each(y(),function(a,d){e(a,c)&&d(b)})};return{define:z,events:c}}); \ No newline at end of file diff --git a/lib/amd/src/custom_interaction_events.js b/lib/amd/src/custom_interaction_events.js index d0fbb6cc0c0..98725b8efcb 100644 --- a/lib/amd/src/custom_interaction_events.js +++ b/lib/amd/src/custom_interaction_events.js @@ -44,6 +44,10 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { ctrlPageDown: 'cie:ctrlPageDown', enter: 'cie:enter', }; + // Static cache of jQuery events that have been handled. This should + // only be populated by JavaScript generated events (which will keep it + // fairly small). + var triggeredEvents = {}; /** * Check if the caller has asked for the given event type to be @@ -77,6 +81,48 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey); }; + /** + * Trigger the custom event for the given jQuery event. + * + * This function will only fire the custom event if one hasn't already been + * fired for the jQuery event. + * + * This is to prevent multiple custom event handlers triggering multiple + * custom events for a single jQuery event as it bubbles up the stack. + * + * @param {string} eventName The name of the custom event + * @param {event} e The jQuery event + * @return {void} + */ + var triggerEvent = function(eventName, e) { + var eventTypeKey = ""; + + if (!e.hasOwnProperty('originalEvent')) { + // This is a jQuery event generated from JavaScript not a browser event so + // we need to build the cache key for the event. + eventTypeKey = "" + eventName + e.type + e.timeStamp; + + if (!triggeredEvents.hasOwnProperty(eventTypeKey)) { + // If we haven't seen this jQuery event before then fire a custom + // event for it and remember the event for later. + triggeredEvents[eventTypeKey] = true; + $(e.target).trigger(eventName, [{originalEvent: e}]); + } + return; + } + + eventTypeKey = "triggeredCustom_" + eventName; + if (!e.originalEvent.hasOwnProperty(eventTypeKey)) { + // If this is a jQuery event generated by the browser then set a + // property on the original event to track that we've seen it before. + // The property is set on the original event because it's the only part + // of the jQuery event that is maintained through multiple event handlers. + e.originalEvent[eventTypeKey] = true; + $(e.target).trigger(eventName, [{originalEvent: e}]); + return; + } + }; + /** * Register a keyboard event that ignores modifier keys. * @@ -90,7 +136,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { element.off('keydown.' + event).on('keydown.' + event, function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCode) { - $(e.target).trigger(event, [{originalEvent: e}]); + triggerEvent(event, e); } } }); @@ -106,12 +152,12 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { */ var addActivateListener = function(element) { element.off('click.cie.activate').on('click.cie.activate', function(e) { - $(e.target).trigger(events.activate, [{originalEvent: e}]); + triggerEvent(events.activate, e); }); element.off('keydown.cie.activate').on('keydown.cie.activate', function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { - $(e.target).trigger(events.activate, [{originalEvent: e}]); + triggerEvent(events.activate, e); } } }); @@ -129,7 +175,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { - $(e.target).trigger(events.keyboardActivate, [{originalEvent: e}]); + triggerEvent(events.keyboardActivate, e); } } }); @@ -250,7 +296,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function(e) { var scrollTop = element.scrollTop(); if (scrollTop === 0) { - element.trigger(events.scrollTop, [{originalEvent: e}]); + triggerEvent(events.scrollTop, e); } }); }; @@ -270,7 +316,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { var scrollHeight = element[0].scrollHeight; if (scrollTop + innerHeight >= scrollHeight) { - element.trigger(events.scrollBottom, [{originalEvent: e}]); + triggerEvent(events.scrollBottom, e); } }); }; @@ -302,7 +348,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { e.preventDefault(); e.returnValue = false; // Fire the scroll lock event. - element.trigger(events.scrollLock, [{originalEvent: e}]); + triggerEvent(events.scrollLock, e); return false; } else if (up && delta > scrollTop) { @@ -312,7 +358,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { e.preventDefault(); e.returnValue = false; // Fire the scroll lock event. - element.trigger(events.scrollLock, [{originalEvent: e}]); + triggerEvent(events.scrollLock, e); return false; } @@ -333,7 +379,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) { if (e.ctrlKey) { if (e.keyCode == keyCodes.pageUp) { - $(e.target).trigger(events.ctrlPageUp, [{originalEvent: e}]); + triggerEvent(events.ctrlPageUp, e); } } }); @@ -351,7 +397,7 @@ define(['jquery', 'core/key_codes'], function($, keyCodes) { element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) { if (e.ctrlKey) { if (e.keyCode == keyCodes.pageDown) { - $(e.target).trigger(events.ctrlPageDown, [{originalEvent: e}]); + triggerEvent(events.ctrlPageDown, e); } } });