Skip to content

Commit ef6b259

Browse files
authored
Merge pull request #2581 from plotly/legend-events
Legend events
2 parents a22c0d5 + 05e1b6b commit ef6b259

File tree

4 files changed

+350
-64
lines changed

4 files changed

+350
-64
lines changed

src/components/legend/draw.js

+43-19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var d3 = require('d3');
1313
var Lib = require('../../lib');
1414
var Plots = require('../../plots/plots');
1515
var Registry = require('../../registry');
16+
var Events = require('../../lib/events');
1617
var dragElement = require('../dragelement');
1718
var Drawing = require('../drawing');
1819
var Color = require('../color');
@@ -347,22 +348,53 @@ module.exports = function draw(gd) {
347348
e.clientY >= bbox.top && e.clientY <= bbox.bottom);
348349
});
349350
if(clickedTrace.size() > 0) {
350-
if(numClicks === 1) {
351-
legend._clickTimeout = setTimeout(function() {
352-
handleClick(clickedTrace, gd, numClicks);
353-
}, DBLCLICKDELAY);
354-
} else if(numClicks === 2) {
355-
if(legend._clickTimeout) {
356-
clearTimeout(legend._clickTimeout);
357-
}
358-
handleClick(clickedTrace, gd, numClicks);
359-
}
351+
clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e);
360352
}
361353
}
362354
});
363355
}
364356
};
365357

358+
function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
359+
var trace = legendItem.data()[0][0].trace;
360+
361+
var evtData = {
362+
event: evt,
363+
node: legendItem.node(),
364+
curveNumber: trace.index,
365+
expandedIndex: trace._expandedIndex,
366+
data: gd.data,
367+
layout: gd.layout,
368+
frames: gd._transitionData._frames,
369+
config: gd._context,
370+
fullData: gd._fullData,
371+
fullLayout: gd._fullLayout
372+
};
373+
374+
if(trace._group) {
375+
evtData.group = trace._group;
376+
}
377+
if(trace.type === 'pie') {
378+
evtData.label = legendItem.datum()[0].label;
379+
}
380+
381+
var clickVal = Events.triggerHandler(gd, 'plotly_legendclick', evtData);
382+
if(clickVal === false) return;
383+
384+
if(numClicks === 1) {
385+
legend._clickTimeout = setTimeout(function() {
386+
handleClick(legendItem, gd, numClicks);
387+
}, DBLCLICKDELAY);
388+
}
389+
else if(numClicks === 2) {
390+
if(legend._clickTimeout) clearTimeout(legend._clickTimeout);
391+
gd._legendMouseDownTime = 0;
392+
393+
var dblClickVal = Events.triggerHandler(gd, 'plotly_legenddoubleclick', evtData);
394+
if(dblClickVal !== false) handleClick(legendItem, gd, numClicks);
395+
}
396+
}
397+
366398
function drawTexts(g, gd, maxLength) {
367399
var legendItem = g.data()[0][0];
368400
var fullLayout = gd._fullLayout;
@@ -460,15 +492,7 @@ function setupTraceToggle(g, gd) {
460492
numClicks = Math.max(numClicks - 1, 1);
461493
}
462494

463-
if(numClicks === 1) {
464-
legend._clickTimeout = setTimeout(function() { handleClick(g, gd, numClicks); }, DBLCLICKDELAY);
465-
} else if(numClicks === 2) {
466-
if(legend._clickTimeout) {
467-
clearTimeout(legend._clickTimeout);
468-
}
469-
gd._legendMouseDownTime = 0;
470-
handleClick(g, gd, numClicks);
471-
}
495+
clickOrDoubleClick(gd, legend, g, numClicks, d3.event);
472496
});
473497
}
474498

src/lib/events.js

+34-26
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var Events = {
5858
plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
5959

6060
/*
61-
* Create funtions for managing internal events. These are *only* triggered
61+
* Create functions for managing internal events. These are *only* triggered
6262
* by the mirroring of external events via the emit function.
6363
*/
6464
plotObj._internalOn = internalEv.on.bind(internalEv);
@@ -85,20 +85,17 @@ var Events = {
8585
},
8686

8787
/*
88-
* This function behaves like jQueries triggerHandler. It calls
88+
* This function behaves like jQuery's triggerHandler. It calls
8989
* all handlers for a particular event and returns the return value
9090
* of the LAST handler. This function also triggers jQuery's
9191
* triggerHandler for backwards compatibility.
92-
*
93-
* Note: triggerHandler has been recommended for deprecation in v2.0.0,
94-
* so the additional behavior of triggerHandler triggering internal events
95-
* is deliberate excluded in order to avoid reinforcing more usage.
9692
*/
9793
triggerHandler: function(plotObj, event, data) {
9894
var jQueryHandlerValue;
9995
var nodeEventHandlerValue;
96+
10097
/*
101-
* If Jquery exists run all its handlers for this event and
98+
* If jQuery exists run all its handlers for this event and
10299
* collect the return value of the LAST handler function
103100
*/
104101
if(typeof jQuery !== 'undefined') {
@@ -114,30 +111,41 @@ var Events = {
114111
var handlers = ev._events[event];
115112
if(!handlers) return jQueryHandlerValue;
116113

117-
/*
118-
* handlers can be function or an array of functions
119-
*/
120-
if(typeof handlers === 'function') handlers = [handlers];
121-
var lastHandler = handlers.pop();
122-
123-
/*
124-
* Call all the handlers except the last one.
125-
*/
126-
for(var i = 0; i < handlers.length; i++) {
127-
handlers[i](data);
114+
// making sure 'this' is the EventEmitter instance
115+
function apply(handler) {
116+
// The 'once' case, we can't just call handler() as we need
117+
// the return value here. So,
118+
// - remove handler
119+
// - call listener and grab return value!
120+
// - stash 'fired' key to not call handler twice
121+
if(handler.listener) {
122+
ev.removeListener(event, handler.listener);
123+
if(!handler.fired) {
124+
handler.fired = true;
125+
return handler.listener.apply(ev, [data]);
126+
}
127+
} else {
128+
return handler.apply(ev, [data]);
129+
}
128130
}
129131

130-
/*
131-
* Now call the final handler and collect its value
132-
*/
133-
nodeEventHandlerValue = lastHandler(data);
132+
// handlers can be function or an array of functions
133+
handlers = Array.isArray(handlers) ? handlers : [handlers];
134+
135+
var i;
136+
for(i = 0; i < handlers.length - 1; i++) {
137+
apply(handlers[i]);
138+
}
139+
// now call the final handler and collect its value
140+
nodeEventHandlerValue = apply(handlers[i]);
134141

135142
/*
136-
* Return either the jquery handler value if it exists or the
137-
* nodeEventHandler value. Jquery event value superceeds nodejs
138-
* events for backwards compatability reasons.
143+
* Return either the jQuery handler value if it exists or the
144+
* nodeEventHandler value. jQuery event value supersedes nodejs
145+
* events for backwards compatibility reasons.
139146
*/
140-
return jQueryHandlerValue !== undefined ? jQueryHandlerValue :
147+
return jQueryHandlerValue !== undefined ?
148+
jQueryHandlerValue :
141149
nodeEventHandlerValue;
142150
},
143151

test/jasmine/tests/events_test.js

+19
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,25 @@ describe('Events', function() {
220220
expect(eventBaton).toBe(3);
221221
expect(result).toBe('pong');
222222
});
223+
224+
it('works with *once* event handlers', function() {
225+
var eventBaton = 0;
226+
227+
Events.init(plotDiv);
228+
229+
plotDiv.once('ping', function() {
230+
eventBaton++;
231+
return 'pong';
232+
});
233+
234+
var result = Events.triggerHandler(plotDiv, 'ping');
235+
expect(result).toBe('pong');
236+
expect(eventBaton).toBe(1);
237+
238+
var nop = Events.triggerHandler(plotDiv, 'ping');
239+
expect(nop).toBeUndefined();
240+
expect(eventBaton).toBe(1);
241+
});
223242
});
224243

225244
describe('purge', function() {

0 commit comments

Comments
 (0)