diff --git a/src/lib/events.js b/src/lib/events.js index 8b8d63d5614..2d7cbd65908 100644 --- a/src/lib/events.js +++ b/src/lib/events.js @@ -24,6 +24,7 @@ var Events = { if(plotObj._ev instanceof EventEmitter) return plotObj; var ev = new EventEmitter(); + var internalEv = new EventEmitter(); /* * Assign to plot._ev while we still live in a land @@ -32,6 +33,16 @@ var Events = { */ plotObj._ev = ev; + /* + * Create a second event handler that will manage events *internally*. + * This allows parts of plotly to respond to thing like relayout without + * having to use the user-facing event handler. They cannot peacefully + * coexist on the same handler because a user invoking + * plotObj.removeAllListeners() would detach internal events, breaking + * plotly. + */ + plotObj._internalEv = internalEv; + /* * Assign bound methods from the ev to the plot object. These methods * will reference the 'this' of plot._ev even though they are methods @@ -46,6 +57,15 @@ var Events = { plotObj.removeListener = ev.removeListener.bind(ev); plotObj.removeAllListeners = ev.removeAllListeners.bind(ev); + /* + * Create funtions for managing internal events. These are *only* triggered + * by the mirroring of external events via the emit function. + */ + plotObj._internalOn = internalEv.on.bind(internalEv); + plotObj._internalOnce = internalEv.once.bind(internalEv); + plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv); + plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv); + /* * We must wrap emit to continue to support JQuery events. The idea * is to check to see if the user is using JQuery events, if they are @@ -58,6 +78,7 @@ var Events = { } ev.emit(event, data); + internalEv.emit(event, data); }; return plotObj; @@ -68,6 +89,10 @@ var Events = { * all handlers for a particular event and returns the return value * of the LAST handler. This function also triggers jQuery's * triggerHandler for backwards compatibility. + * + * Note: triggerHandler has been recommended for deprecation in v2.0.0, + * so the additional behavior of triggerHandler triggering internal events + * is deliberate excluded in order to avoid reinforcing more usage. */ triggerHandler: function(plotObj, event, data) { var jQueryHandlerValue; @@ -124,6 +149,13 @@ var Events = { delete plotObj.removeAllListeners; delete plotObj.emit; + delete plotObj._ev; + delete plotObj._internalEv; + delete plotObj._internalOn; + delete plotObj._internalOnce; + delete plotObj._removeInternalListener; + delete plotObj._removeAllInternalListeners; + return plotObj; } diff --git a/test/jasmine/tests/events_test.js b/test/jasmine/tests/events_test.js index ecf3c0ba4fe..d4cc5bf423a 100644 --- a/test/jasmine/tests/events_test.js +++ b/test/jasmine/tests/events_test.js @@ -70,6 +70,19 @@ describe('Events', function() { $(plotDiv).trigger('ping', 'pong'); }); }); + + it('mirrors events on an internal handler', function(done) { + Events.init(plotDiv); + + plotDiv._internalOn('ping', function(data) { + expect(data).toBe('pong'); + done(); + }); + + setTimeout(function() { + plotDiv.emit('ping', 'pong'); + }); + }); }); describe('triggerHandler', function() { @@ -100,6 +113,34 @@ describe('Events', function() { expect(result).toBe('pong'); }); + it('does *not* mirror triggerHandler events on the internal handler', function() { + var eventBaton = 0; + var internalEventBaton = 0; + + Events.init(plotDiv); + + plotDiv.on('ping', function() { + eventBaton++; + return 'ping'; + }); + + plotDiv._internalOn('ping', function() { + internalEventBaton++; + return 'foo'; + }); + + plotDiv.on('ping', function() { + eventBaton++; + return 'pong'; + }); + + var result = Events.triggerHandler(plotDiv, 'ping'); + + expect(eventBaton).toBe(2); + expect(internalEventBaton).toBe(0); + expect(result).toBe('pong'); + }); + it('triggers jQuery handlers when no matching node events bound', function() { var eventBaton = 0; diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index 4f9284b3753..dad7cf6e8e0 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -301,9 +301,10 @@ describe('Test Plots', function() { it('should unset everything in the gd except _context', function() { var expectedKeys = [ - '_ev', 'on', 'once', 'removeListener', 'removeAllListeners', - 'emit', '_context', '_replotPending', '_mouseDownTime', - '_hmpixcount', '_hmlumcount' + '_ev', '_internalEv', 'on', 'once', 'removeListener', 'removeAllListeners', + '_internalOn', '_internalOnce', '_removeInternalListener', + '_removeAllInternalListeners', 'emit', '_context', '_replotPending', + '_mouseDownTime', '_hmpixcount', '_hmlumcount' ]; Plots.purge(gd);