mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[vislib/Dispatch] move away from d3.dispatch
This commit is contained in:
parent
c41c401c02
commit
baab9cad8d
6 changed files with 279 additions and 25 deletions
|
@ -3,6 +3,7 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var Tooltip = Private(require('components/vislib/components/tooltip/tooltip'));
|
||||
var SimpleEmitter = require('utils/SimpleEmitter');
|
||||
|
||||
/**
|
||||
* Handles event responses
|
||||
|
@ -12,18 +13,15 @@ define(function (require) {
|
|||
* @param handler {Object} Reference to Handler Class Object
|
||||
*/
|
||||
|
||||
_(Dispatch).inherits(SimpleEmitter);
|
||||
function Dispatch(handler) {
|
||||
|
||||
var stockEvents = ['brush', 'click', 'hover', 'mouseup', 'mousedown', 'mouseover', 'mouseout'];
|
||||
var customEvents = _.deepGet(handler, 'vis.eventTypes.enabled');
|
||||
var eventTypes = customEvents ? stockEvents.concat(customEvents) : stockEvents;
|
||||
|
||||
if (!(this instanceof Dispatch)) {
|
||||
return new Dispatch(handler);
|
||||
}
|
||||
|
||||
Dispatch.Super.call(this);
|
||||
this.handler = handler;
|
||||
this.dispatch = d3.dispatch.apply(this, eventTypes);
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,7 +94,6 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @method addHoverEvent
|
||||
|
@ -104,7 +101,7 @@ define(function (require) {
|
|||
*/
|
||||
Dispatch.prototype.addHoverEvent = function () {
|
||||
var self = this;
|
||||
var isClickable = (this.dispatch.on('click'));
|
||||
var isClickable = this.listenerCount('click') > 0;
|
||||
var addEvent = this.addEvent;
|
||||
var $el = this.handler.el;
|
||||
|
||||
|
@ -117,7 +114,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
self.highlightLegend.call(this, $el);
|
||||
self.dispatch.hover.call(this, self.eventResponse(d, i));
|
||||
self.emit('hover', self.eventResponse(d, i));
|
||||
}
|
||||
|
||||
return addEvent('mouseover', hover);
|
||||
|
@ -153,7 +150,7 @@ define(function (require) {
|
|||
|
||||
function click(d, i) {
|
||||
d3.event.stopPropagation();
|
||||
self.dispatch.click.call(this, self.eventResponse(d, i));
|
||||
self.emit('click', self.eventResponse(d, i));
|
||||
}
|
||||
|
||||
return addEvent('click', click);
|
||||
|
@ -177,7 +174,7 @@ define(function (require) {
|
|||
* @returns {Boolean}
|
||||
*/
|
||||
Dispatch.prototype.isBrushable = function () {
|
||||
return this.allowBrushing() && (typeof this.dispatch.on('brush') === 'function');
|
||||
return this.allowBrushing() && this.listenerCount('brush') > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -265,8 +262,8 @@ define(function (require) {
|
|||
* @returns {*} Returns a D3 brush function and a SVG with a brush group attached
|
||||
*/
|
||||
Dispatch.prototype.createBrush = function (xScale, svg) {
|
||||
var dispatch = this.dispatch;
|
||||
var attr = this.handler._attr;
|
||||
var self = this;
|
||||
var attr = self.handler._attr;
|
||||
var height = attr.height;
|
||||
var margin = attr.margin;
|
||||
|
||||
|
@ -286,7 +283,7 @@ define(function (require) {
|
|||
});
|
||||
var range = isTimeSeries ? brush.extent() : selected;
|
||||
|
||||
return dispatch.brush({
|
||||
return self.emit('brush', {
|
||||
range: range,
|
||||
config: attr,
|
||||
e: d3.event,
|
||||
|
@ -295,7 +292,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
// if `addBrushing` is true, add brush canvas
|
||||
if (dispatch.on('brush')) {
|
||||
if (self.listenerCount('brush')) {
|
||||
svg.insert('g', 'g')
|
||||
.attr('class', 'brush')
|
||||
.call(brush)
|
||||
|
|
|
@ -100,11 +100,11 @@ define(function (require) {
|
|||
* clean up event handlers every time it destroys the chart
|
||||
* rebind them every time it creates the charts
|
||||
*/
|
||||
if (chart.events.dispatch) {
|
||||
if (chart.events) {
|
||||
enabledEvents = self.vis.eventTypes.enabled;
|
||||
|
||||
// Copy dispatch.on methods to chart object
|
||||
d3.rebind(chart, chart.events.dispatch, 'on');
|
||||
d3.rebind(chart, chart.events, 'on');
|
||||
|
||||
// Bind events to chart(s)
|
||||
if (enabledEvents.length) {
|
||||
|
|
|
@ -84,7 +84,7 @@ define(function (require) {
|
|||
|
||||
var drawOptions = {draw: {}};
|
||||
_.each(['polyline', 'polygon', 'circle', 'marker', 'rectangle'], function (drawShape) {
|
||||
if (!self.events.dispatch[drawShape]) {
|
||||
if (!self.events.listenerCount(drawShape)) {
|
||||
drawOptions.draw[drawShape] = false;
|
||||
} else {
|
||||
drawOptions.draw[drawShape] = {
|
||||
|
@ -133,12 +133,12 @@ define(function (require) {
|
|||
|
||||
map.on('draw:created', function (e) {
|
||||
var drawType = e.layerType;
|
||||
if (!self.events.dispatch[drawType]) return;
|
||||
if (!self.events.listenerCount(drawType)) return;
|
||||
|
||||
// TODO: Different drawTypes need differ info. Need a switch on the object creation
|
||||
var bounds = e.layer.getBounds();
|
||||
|
||||
self.events.dispatch[drawType]({
|
||||
self.events.emit(drawType, {
|
||||
e: e,
|
||||
data: self.chartData,
|
||||
bounds: {
|
||||
|
|
101
src/kibana/utils/SimpleEmitter.js
Normal file
101
src/kibana/utils/SimpleEmitter.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Simple event emitter class used in the vislib. Calls
|
||||
* handlers synchronously and implements a chainable api
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function SimpleEmitter() {
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event handler
|
||||
*
|
||||
* @param {string} event
|
||||
* @param {function} handler
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.on = function (event, handler) {
|
||||
var handlers = this._listeners[event];
|
||||
if (!handlers) handlers = this._listeners[event] = [];
|
||||
|
||||
if (!_.contains(handlers, handler)) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an event handler
|
||||
*
|
||||
* @param {string} event
|
||||
* @param {function} [handler] - optional handler to remove, if no handler is
|
||||
* passed then all are removed
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.off = function (event, handler) {
|
||||
if (!this._listeners[event]) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// remove a specific handler
|
||||
if (handler) _.pull(this._listeners[event], handler);
|
||||
// or remove all listeners
|
||||
else this._listeners[event] = null;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event and all arguments to all listeners for an event name
|
||||
*
|
||||
* @param {string} event
|
||||
* @param {*} [arg...] - any number of arguments that will be applied to each handler
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.emit = function (event, arg) {
|
||||
if (!this._listeners[event]) return this;
|
||||
|
||||
var args = _.rest(arguments);
|
||||
var handlers = this._listeners[event].slice(0);
|
||||
var i = -1;
|
||||
|
||||
while (++i < handlers.length) {
|
||||
handlers[i].apply(this, args);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the count of handlers for a specific event
|
||||
*
|
||||
* @param {string} [event] - optional event name to filter by
|
||||
* @return {number}
|
||||
*/
|
||||
SimpleEmitter.prototype.listenerCount = function (event) {
|
||||
if (event) {
|
||||
return _.size(this._listeners[event]);
|
||||
}
|
||||
|
||||
return _.reduce(this._listeners, function (count, handlers) {
|
||||
return count + _.size(handlers);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all event listeners bound to this emitter.
|
||||
*
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.removeAllListeners = function () {
|
||||
this._listeners = {};
|
||||
return this;
|
||||
};
|
||||
|
||||
return SimpleEmitter;
|
||||
});
|
156
test/unit/specs/utils/SimpleEmitter.js
Normal file
156
test/unit/specs/utils/SimpleEmitter.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
define(function (require) {
|
||||
describe('SimpleEmitter class', function () {
|
||||
var SimpleEmitter = require('utils/SimpleEmitter');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var emitter;
|
||||
|
||||
beforeEach(function () {
|
||||
emitter = new SimpleEmitter();
|
||||
});
|
||||
|
||||
it('constructs an event emitter', function () {
|
||||
expect(emitter).to.have.property('on');
|
||||
expect(emitter).to.have.property('off');
|
||||
expect(emitter).to.have.property('emit');
|
||||
expect(emitter).to.have.property('listenerCount');
|
||||
expect(emitter).to.have.property('removeAllListeners');
|
||||
});
|
||||
|
||||
describe('#listenerCount', function () {
|
||||
it('counts all event listeners without any arg', function () {
|
||||
expect(emitter.listenerCount()).to.be(0);
|
||||
emitter.on('a', function () {});
|
||||
expect(emitter.listenerCount()).to.be(1);
|
||||
emitter.on('b', function () {});
|
||||
expect(emitter.listenerCount()).to.be(2);
|
||||
});
|
||||
|
||||
it('limits to the event that is passed in', function () {
|
||||
expect(emitter.listenerCount()).to.be(0);
|
||||
emitter.on('a', function () {});
|
||||
expect(emitter.listenerCount('a')).to.be(1);
|
||||
emitter.on('a', function () {});
|
||||
expect(emitter.listenerCount('a')).to.be(2);
|
||||
emitter.on('b', function () {});
|
||||
expect(emitter.listenerCount('a')).to.be(2);
|
||||
expect(emitter.listenerCount('b')).to.be(1);
|
||||
expect(emitter.listenerCount()).to.be(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#on', function () {
|
||||
it('registers a handler', function () {
|
||||
var handler = sinon.stub();
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount('a')).to.be(1);
|
||||
|
||||
expect(handler.callCount).to.be(0);
|
||||
emitter.emit('a');
|
||||
expect(handler.callCount).to.be(1);
|
||||
});
|
||||
|
||||
it('allows multiple event handlers for the same event', function () {
|
||||
emitter.on('a', function () {});
|
||||
emitter.on('a', function () {});
|
||||
expect(emitter.listenerCount('a')).to.be(2);
|
||||
});
|
||||
|
||||
it('only registers a specific listener once', function () {
|
||||
var handler = function () {};
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount()).to.be(1);
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount()).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#off', function () {
|
||||
it('removes a listener if it was registered', function () {
|
||||
var handler = sinon.stub();
|
||||
expect(emitter.listenerCount()).to.be(0);
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount('a')).to.be(1);
|
||||
emitter.off('a', handler);
|
||||
expect(emitter.listenerCount('a')).to.be(0);
|
||||
});
|
||||
|
||||
it('clears all listeners if no handler is passed', function () {
|
||||
emitter.on('a', function () {});
|
||||
emitter.on('a', function () {});
|
||||
expect(emitter.listenerCount()).to.be(2);
|
||||
emitter.off('a');
|
||||
expect(emitter.listenerCount()).to.be(0);
|
||||
});
|
||||
|
||||
it('does not mind if the listener is not registered', function () {
|
||||
emitter.off('a', function () {});
|
||||
});
|
||||
|
||||
it('does not mind if the event has no listeners', function () {
|
||||
emitter.off('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#emit', function () {
|
||||
it('calls the handlers in the order they were defined', function () {
|
||||
var i = 0;
|
||||
var incr = function () { return ++i; };
|
||||
var one = sinon.spy(incr);
|
||||
var two = sinon.spy(incr);
|
||||
var three = sinon.spy(incr);
|
||||
var four = sinon.spy(incr);
|
||||
|
||||
emitter
|
||||
.on('a', one)
|
||||
.on('a', two)
|
||||
.on('a', three)
|
||||
.on('a', four)
|
||||
.emit('a');
|
||||
|
||||
expect(one).to.have.property('callCount', 1);
|
||||
expect(one.returned(1)).to.be.ok();
|
||||
|
||||
expect(two).to.have.property('callCount', 1);
|
||||
expect(two.returned(2)).to.be.ok();
|
||||
|
||||
expect(three).to.have.property('callCount', 1);
|
||||
expect(three.returned(3)).to.be.ok();
|
||||
|
||||
expect(four).to.have.property('callCount', 1);
|
||||
expect(four.returned(4)).to.be.ok();
|
||||
});
|
||||
|
||||
it('always emits the handlers that were initially registered', function () {
|
||||
|
||||
var destructive = sinon.spy(function () {
|
||||
emitter.removeAllListeners();
|
||||
expect(emitter.listenerCount()).to.be(0);
|
||||
});
|
||||
var stub = sinon.stub();
|
||||
|
||||
emitter.on('run', destructive).on('run', stub).emit('run');
|
||||
|
||||
expect(destructive).to.have.property('callCount', 1);
|
||||
expect(stub).to.have.property('callCount', 1);
|
||||
});
|
||||
|
||||
it('applies all arguments except the first', function () {
|
||||
emitter
|
||||
.on('a', function (a, b, c) {
|
||||
expect(a).to.be('foo');
|
||||
expect(b).to.be('bar');
|
||||
expect(c).to.be('baz');
|
||||
})
|
||||
.emit('a', 'foo', 'bar', 'baz');
|
||||
});
|
||||
|
||||
it('uses the SimpleEmitter as the this context', function () {
|
||||
emitter
|
||||
.on('a', function () {
|
||||
expect(this).to.be(emitter);
|
||||
})
|
||||
.emit('a');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -57,7 +57,7 @@ define(function (require) {
|
|||
|
||||
it('should attach a hover event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.hover)).to.be(true);
|
||||
expect(chart.events.listenerCount('hover')).to.be.above(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ define(function (require) {
|
|||
|
||||
it('should attach a click event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.click)).to.be(true);
|
||||
expect(chart.events.listenerCount('click')).to.be.above(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ define(function (require) {
|
|||
|
||||
it('should attach a brush event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.brush)).to.be(true);
|
||||
expect(chart.events.listenerCount('brush')).to.be.above(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
describe('Custom event handlers', function () {
|
||||
it('should attach whatever gets passed on vis.on() to dispatch', function (done) {
|
||||
it('should attach whatever gets passed on vis.on() to chart.events', function (done) {
|
||||
var vis;
|
||||
var chart;
|
||||
module('AreaChartFactory');
|
||||
|
@ -116,7 +116,7 @@ define(function (require) {
|
|||
vis.render(data);
|
||||
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.events.dispatch.someEvent).to.be.a(Function);
|
||||
expect(chart.events.listenerCount('someEvent')).to.be.above(0);
|
||||
});
|
||||
|
||||
destroyVis(vis);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue