mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge pull request #3788 from spalger/fix/3768
[vislib/Dispatch] switch to a more standard event emitter api
This commit is contained in:
commit
23c58ac52a
15 changed files with 504 additions and 257 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)
|
||||
|
|
|
@ -50,6 +50,15 @@ define(function (require) {
|
|||
this.xAxis,
|
||||
this.yAxis
|
||||
], Boolean);
|
||||
|
||||
// memoize so that the same function is returned every time,
|
||||
// allowing us to remove/re-add the same function
|
||||
this.getProxyHandler = _.memoize(function (event) {
|
||||
var self = this;
|
||||
return function (e) {
|
||||
self.vis.emit(event, e);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,27 +101,10 @@ define(function (require) {
|
|||
selection.selectAll('.chart')
|
||||
.each(function (chartData) {
|
||||
var chart = new self.ChartClass(self, this, chartData);
|
||||
var enabledEvents;
|
||||
|
||||
/*
|
||||
* inside handler: if there are charts, bind events to charts
|
||||
* functionality: track in array that event is enabled
|
||||
* clean up event handlers every time it destroys the chart
|
||||
* rebind them every time it creates the charts
|
||||
*/
|
||||
if (chart.events.dispatch) {
|
||||
enabledEvents = self.vis.eventTypes.enabled;
|
||||
|
||||
// Copy dispatch.on methods to chart object
|
||||
d3.rebind(chart, chart.events.dispatch, 'on');
|
||||
|
||||
// Bind events to chart(s)
|
||||
if (enabledEvents.length) {
|
||||
enabledEvents.forEach(function (event) {
|
||||
self.enable(event, chart);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.vis.activeEvents().forEach(function (event) {
|
||||
self.enable(event, chart);
|
||||
});
|
||||
|
||||
charts.push(chart);
|
||||
chart.render();
|
||||
|
@ -123,33 +115,34 @@ define(function (require) {
|
|||
/**
|
||||
* Enables events, i.e. binds specific events to the chart
|
||||
* object(s) `on` method. For example, `click` or `mousedown` events.
|
||||
* Emits the event to the Events class.
|
||||
*
|
||||
* @method enable
|
||||
* @param event {String} Event type
|
||||
* @param chart {Object} Chart
|
||||
* @returns {*}
|
||||
*/
|
||||
Handler.prototype.enable = function (event, chart) {
|
||||
return chart.on(event, function (e) {
|
||||
this.vis.emit(event, e);
|
||||
}.bind(this));
|
||||
};
|
||||
Handler.prototype.enable = chartEventProxyToggle('on');
|
||||
|
||||
/**
|
||||
* Disables events by passing null to the event listener.
|
||||
* According to the D3 documentation for event handling:
|
||||
* https://github.com/mbostock/d3/wiki/Selections#on, to remove all
|
||||
* listeners for a particular event type, pass null as the listener.
|
||||
* Disables events for all charts
|
||||
*
|
||||
* @method disable
|
||||
* @param event {String} Event type
|
||||
* @param chart {Object} Chart
|
||||
* @returns {*}
|
||||
*/
|
||||
Handler.prototype.disable = function (event, chart) {
|
||||
return chart.on(event, null);
|
||||
};
|
||||
Handler.prototype.disable = chartEventProxyToggle('off');
|
||||
|
||||
|
||||
function chartEventProxyToggle(method) {
|
||||
return function (event, chart) {
|
||||
var proxyHandler = this.getProxyHandler(event);
|
||||
|
||||
_.each(chart ? [chart] : this.charts, function (chart) {
|
||||
chart.events[method](event, proxyHandler);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all DOM elements from the HTML element provided
|
||||
|
|
|
@ -26,9 +26,6 @@ define(function (require) {
|
|||
this.el = $el.get ? $el.get(0) : $el;
|
||||
this.ChartClass = chartTypes[config.type];
|
||||
this._attr = _.defaults({}, config || {}, {});
|
||||
this.eventTypes = {
|
||||
enabled: []
|
||||
};
|
||||
|
||||
// bind the resize function so it can be used as an event handler
|
||||
this.resize = _.bind(this.resize, this);
|
||||
|
@ -146,26 +143,13 @@ define(function (require) {
|
|||
* @returns {*}
|
||||
*/
|
||||
Vis.prototype.on = function (event, listener) {
|
||||
var ret = Events.prototype.on.call(this, event, listener); // Adds event to _listeners array
|
||||
var listeners = this._listeners[event].length;
|
||||
var charts = (this.handler && this.handler.charts);
|
||||
var chartCount = charts ? charts.length : 0;
|
||||
var enabledEvents = this.eventTypes.enabled;
|
||||
var eventAbsent = (enabledEvents.indexOf(event) === -1);
|
||||
var first = this.listenerCount(event) === 0;
|
||||
var ret = Events.prototype.on.call(this, event, listener);
|
||||
var added = this.listenerCount(event) > 0;
|
||||
|
||||
// if this is the first listener added for the event
|
||||
// and charts are available, bind the event to the chart(s)
|
||||
// `on` method
|
||||
if (listeners === 1 && chartCount > 0) {
|
||||
charts.forEach(function (chart) {
|
||||
this.handler.enable(event, chart);
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Keep track of enabled events
|
||||
if (eventAbsent) {
|
||||
enabledEvents.push(event);
|
||||
}
|
||||
// enable the event in the handler
|
||||
if (first && added && this.handler) this.handler.enable(event);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
@ -178,25 +162,12 @@ define(function (require) {
|
|||
* @returns {*}
|
||||
*/
|
||||
Vis.prototype.off = function (event, listener) {
|
||||
var ret = Events.prototype.off.call(this, event, listener); // Removes event from _listeners array
|
||||
var listeners = (!!this._listeners[event] && this._listeners[event].length !== 0);
|
||||
var charts = (this.handler && this.handler.charts);
|
||||
var chartCount = charts ? charts.length : 0;
|
||||
var eventIndex = this.eventTypes.enabled.indexOf(event);
|
||||
var eventPresent = (eventIndex !== -1);
|
||||
|
||||
// Once the listener array reaches zero, turn off event
|
||||
if (!listeners && eventPresent) {
|
||||
if (chartCount > 0) {
|
||||
charts.forEach(function (chart) {
|
||||
this.handler.disable(event, chart);
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Remove event from enabled array
|
||||
this.eventTypes.enabled.splice(eventIndex, 1);
|
||||
}
|
||||
var last = this.listenerCount(event) === 1;
|
||||
var ret = Events.prototype.off.call(this, event, listener);
|
||||
var removed = this.listenerCount(event) === 0;
|
||||
|
||||
// Once all listeners are removed, disable the events in the handler
|
||||
if (last && removed && this.handler) this.handler.disable(event);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ define(function (require) {
|
|||
*/
|
||||
Chart.prototype.destroy = function () {
|
||||
var selection = d3.select(this.chartEl);
|
||||
|
||||
this.events.removeAllListeners();
|
||||
selection.remove();
|
||||
selection = null;
|
||||
};
|
||||
|
|
|
@ -85,7 +85,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] = {
|
||||
|
@ -134,12 +134,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: {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
define(function (require) {
|
||||
return function BaseObjectProvider() {
|
||||
var _ = require('lodash');
|
||||
var rison = require('utils/rison');
|
||||
var angular = require('angular');
|
||||
|
||||
function BaseObject(attributes) {
|
||||
// Set the attributes or default to an empty object
|
||||
_.assign(this, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attirbutes for the objct
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toObject = function () {
|
||||
// return just the data.
|
||||
return _.omit(this, function (value, key) {
|
||||
return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to RISON
|
||||
* @returns {string}
|
||||
*/
|
||||
BaseObject.prototype.toRISON = function () {
|
||||
var obj = this.toObject();
|
||||
// Use Angular to remove the private vars
|
||||
return rison.encode(JSON.parse(angular.toJson(obj)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to JSON
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toJSON = function () {
|
||||
return this.toObject();
|
||||
};
|
||||
|
||||
return BaseObject;
|
||||
};
|
||||
});
|
|
@ -2,10 +2,10 @@ define(function (require) {
|
|||
var _ = require('lodash');
|
||||
|
||||
return function EventsProvider(Private, Promise, Notifier) {
|
||||
var BaseObject = Private(require('factories/base_object'));
|
||||
var SimpleEmitter = require('utils/SimpleEmitter');
|
||||
var notify = new Notifier({ location: 'EventEmitter' });
|
||||
|
||||
_(Events).inherits(BaseObject);
|
||||
_(Events).inherits(SimpleEmitter);
|
||||
function Events() {
|
||||
Events.Super.call(this);
|
||||
this._listeners = {};
|
||||
|
@ -89,6 +89,16 @@ define(function (require) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of the handler functions for a specific event
|
||||
*
|
||||
* @param {string} name
|
||||
* @return {array[function]}
|
||||
*/
|
||||
Events.prototype.listeners = function (name) {
|
||||
return _.pluck(this._listeners[name], 'handler');
|
||||
};
|
||||
|
||||
return Events;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -17,11 +17,12 @@ define(function (require) {
|
|||
|
||||
self.vislibParams = self._getVislibParams();
|
||||
self.vislibVis = new vislib.Vis(self.$el[0], self.vislibParams);
|
||||
if (this.chartData) self.vislibVis.render(this.chartData);
|
||||
|
||||
_.each(self.vis.listeners, function (listener, event) {
|
||||
self.vislibVis.on(event, listener);
|
||||
});
|
||||
|
||||
if (this.chartData) self.vislibVis.render(this.chartData);
|
||||
};
|
||||
|
||||
VislibRenderbot.prototype._getVislibParams = function () {
|
||||
|
|
40
src/kibana/utils/BaseObject.js
Normal file
40
src/kibana/utils/BaseObject.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var rison = require('utils/rison');
|
||||
var angular = require('angular');
|
||||
|
||||
function BaseObject(attributes) {
|
||||
// Set the attributes or default to an empty object
|
||||
_.assign(this, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attirbutes for the objct
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toObject = function () {
|
||||
// return just the data.
|
||||
return _.omit(this, function (value, key) {
|
||||
return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to RISON
|
||||
* @returns {string}
|
||||
*/
|
||||
BaseObject.prototype.toRISON = function () {
|
||||
// Use Angular to remove the private vars, and JSON.stringify to serialize
|
||||
return rison.encode(JSON.parse(angular.toJson(this)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the model to JSON
|
||||
* @returns {object}
|
||||
*/
|
||||
BaseObject.prototype.toJSON = function () {
|
||||
return this.toObject();
|
||||
};
|
||||
|
||||
return BaseObject;
|
||||
});
|
123
src/kibana/utils/SimpleEmitter.js
Normal file
123
src/kibana/utils/SimpleEmitter.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var BaseObject = require('utils/BaseObject');
|
||||
|
||||
/**
|
||||
* Simple event emitter class used in the vislib. Calls
|
||||
* handlers synchronously and implements a chainable api
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
_(SimpleEmitter).inherits(BaseObject);
|
||||
function SimpleEmitter() {
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event handler
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {function} handler
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.on = function (name, handler) {
|
||||
var handlers = this._listeners[name];
|
||||
if (!handlers) handlers = this._listeners[name] = [];
|
||||
|
||||
handlers.push(handler);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an event handler
|
||||
*
|
||||
* @param {string} name
|
||||
* @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 (name, handler) {
|
||||
if (!this._listeners[name]) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// remove a specific handler
|
||||
if (handler) _.pull(this._listeners[name], handler);
|
||||
// or remove all listeners
|
||||
else this._listeners[name] = null;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all event listeners bound to this emitter.
|
||||
*
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.removeAllListeners = function () {
|
||||
this._listeners = {};
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event and all arguments to all listeners for an event name
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {*} [arg...] - any number of arguments that will be applied to each handler
|
||||
* @return {SimpleEmitter} - this, for chaining
|
||||
*/
|
||||
SimpleEmitter.prototype.emit = function (name, arg) {
|
||||
if (!this._listeners[name]) return this;
|
||||
|
||||
var args = _.rest(arguments);
|
||||
var listeners = this.listeners(name);
|
||||
var i = -1;
|
||||
|
||||
while (++i < listeners.length) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of the event names that currently have listeners
|
||||
*
|
||||
* @return {array[string]}
|
||||
*/
|
||||
SimpleEmitter.prototype.activeEvents = function () {
|
||||
return _.reduce(this._listeners, function (active, listeners, name) {
|
||||
return active.concat(_.size(listeners) ? name : []);
|
||||
}, []);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of the handler functions for a specific event
|
||||
*
|
||||
* @param {string} name
|
||||
* @return {array[function]}
|
||||
*/
|
||||
SimpleEmitter.prototype.listeners = function (name) {
|
||||
return this._listeners[name] ? this._listeners[name].slice(0) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the count of handlers for a specific event
|
||||
*
|
||||
* @param {string} [name] - optional event name to filter by
|
||||
* @return {number}
|
||||
*/
|
||||
SimpleEmitter.prototype.listenerCount = function (name) {
|
||||
if (name) {
|
||||
return _.size(this._listeners[name]);
|
||||
}
|
||||
|
||||
return _.reduce(this._listeners, function (count, handlers) {
|
||||
return count + _.size(handlers);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
||||
return SimpleEmitter;
|
||||
});
|
|
@ -13,7 +13,7 @@ define(function (require) {
|
|||
|
||||
inject(function (_$rootScope_, Private) {
|
||||
$rootScope = _$rootScope_;
|
||||
BaseObject = Private(require('factories/base_object'));
|
||||
BaseObject = require('utils/BaseObject');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
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('allows the same function to be registered multiple times', function () {
|
||||
var handler = function () {};
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount()).to.be(1);
|
||||
emitter.on('a', handler);
|
||||
expect(emitter.listenerCount()).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ define(function (require) {
|
|||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var d3 = require('d3');
|
||||
|
||||
// Data
|
||||
var data = require('vislib_fixtures/mock_data/date_histogram/_series');
|
||||
|
@ -15,87 +16,96 @@ define(function (require) {
|
|||
vis = null;
|
||||
}
|
||||
|
||||
function getEls(n, type) {
|
||||
return d3.select().data(new Array(n)).enter().append(type);
|
||||
}
|
||||
|
||||
describe('', function () {
|
||||
var vis;
|
||||
var SimpleEmitter;
|
||||
|
||||
beforeEach(module('AreaChartFactory'));
|
||||
beforeEach(inject(function (Private) {
|
||||
vis = Private(require('vislib_fixtures/_vis_fixture'))();
|
||||
vis.render(data);
|
||||
SimpleEmitter = require('utils/SimpleEmitter');
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
destroyVis(vis);
|
||||
});
|
||||
|
||||
it('extends the SimpleEmitter class', function () {
|
||||
var events = _.pluck(vis.handler.charts, 'events');
|
||||
expect(events.length).to.be.above(0);
|
||||
events.forEach(function (dispatch) {
|
||||
expect(dispatch).to.be.a(SimpleEmitter);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stock event handlers', function () {
|
||||
var vis;
|
||||
|
||||
beforeEach(function () {
|
||||
module('AreaChartFactory');
|
||||
});
|
||||
beforeEach(module('AreaChartFactory'));
|
||||
beforeEach(inject(function (Private) {
|
||||
vis = Private(require('vislib_fixtures/_vis_fixture'))();
|
||||
require('css!components/vislib/styles/main');
|
||||
|
||||
beforeEach(function () {
|
||||
inject(function (Private) {
|
||||
vis = Private(require('vislib_fixtures/_vis_fixture'))();
|
||||
require('css!components/vislib/styles/main');
|
||||
vis.on('brush', _.noop);
|
||||
|
||||
vis.on('brush', _.noop);
|
||||
|
||||
vis.render(data);
|
||||
});
|
||||
});
|
||||
vis.render(data);
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
destroyVis(vis);
|
||||
});
|
||||
|
||||
describe('addEvent method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var addEvent = chart.events.addEvent;
|
||||
expect(_.isFunction(addEvent('click', _.noop))).to.be(true);
|
||||
it('returns a function that binds the passed event to a selection', function () {
|
||||
var chart = _.first(vis.handler.charts);
|
||||
var apply = chart.events.addEvent('event', _.noop);
|
||||
expect(apply).to.be.a('function');
|
||||
|
||||
var els = getEls(3, 'div');
|
||||
apply(els);
|
||||
els.each(function () {
|
||||
expect(d3.select(this).on('event')).to.be(_.noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addHoverEvent method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var hover = chart.events.addHoverEvent;
|
||||
// test the addHoverEvent, addClickEvent, addBrushEvent methods by
|
||||
// checking that they return function which bind the events expected
|
||||
function checkBoundAddMethod(name, event) {
|
||||
describe(name + ' method', function () {
|
||||
it('should be a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.events[name]).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
expect(_.isFunction(hover)).to.be(true);
|
||||
it('returns a function that binds ' + event + ' events to a selection', function () {
|
||||
var chart = _.first(vis.handler.charts);
|
||||
var apply = chart.events[name](d3.select(document.createElement('svg')));
|
||||
expect(apply).to.be.a('function');
|
||||
|
||||
var els = getEls(3, 'div');
|
||||
apply(els);
|
||||
els.each(function () {
|
||||
expect(d3.select(this).on(event)).to.be.a('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('should attach a hover event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.hover)).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addClickEvent method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var click = chart.events.addClickEvent;
|
||||
|
||||
expect(_.isFunction(click)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should attach a click event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.click)).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addBrushEvent method', function () {
|
||||
it('should return a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var brush = chart.events.addBrushEvent;
|
||||
|
||||
expect(_.isFunction(brush)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should attach a brush event', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(_.isFunction(chart.events.dispatch.brush)).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
checkBoundAddMethod('addHoverEvent', 'mouseover');
|
||||
checkBoundAddMethod('addMouseoutEvent', 'mouseout');
|
||||
checkBoundAddMethod('addClickEvent', 'click');
|
||||
checkBoundAddMethod('addBrushEvent', 'mousedown');
|
||||
|
||||
describe('addMousePointer method', function () {
|
||||
it('should return a function', function () {
|
||||
it('should be a function', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var pointer = chart.events.addMousePointer;
|
||||
|
||||
|
@ -106,7 +116,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,13 +126,30 @@ 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(1);
|
||||
});
|
||||
|
||||
destroyVis(vis);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be added after rendering', function () {
|
||||
var vis;
|
||||
var chart;
|
||||
module('AreaChartFactory');
|
||||
inject(function (Private) {
|
||||
vis = Private(require('vislib_fixtures/_vis_fixture'))();
|
||||
vis.render(data);
|
||||
vis.on('someEvent', _.noop);
|
||||
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
expect(chart.events.listenerCount('someEvent')).to.be(1);
|
||||
});
|
||||
|
||||
destroyVis(vis);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,7 +77,7 @@ define(function (require) {
|
|||
it('should add events to chart and emit to the Events class', function () {
|
||||
charts.forEach(function (chart) {
|
||||
events.forEach(function (event) {
|
||||
expect(typeof chart.on(event)).to.be('function');
|
||||
expect(chart.events.listenerCount(event)).to.be.above(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ define(function (require) {
|
|||
it('should remove events from the chart', function () {
|
||||
charts.forEach(function (chart) {
|
||||
events.forEach(function (event) {
|
||||
expect(typeof chart.on(event)).to.be('undefined');
|
||||
expect(chart.events.listenerCount(event)).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -138,11 +138,10 @@ define(function (require) {
|
|||
var listener2;
|
||||
|
||||
beforeEach(function () {
|
||||
listeners = [];
|
||||
listener1 = function () {};
|
||||
listener2 = function () {};
|
||||
listeners.push(listener1);
|
||||
listeners.push(listener2);
|
||||
listeners = [
|
||||
listener1 = function () {},
|
||||
listener2 = function () {}
|
||||
];
|
||||
|
||||
// Add event and listeners to chart
|
||||
listeners.forEach(function (listener) {
|
||||
|
@ -163,35 +162,22 @@ define(function (require) {
|
|||
vis.off(afterEvent);
|
||||
});
|
||||
|
||||
it('should add an event and its listeners to the _listeners object', function () {
|
||||
// Test for presence of beforeEvent in _listener object
|
||||
expect(vis._listeners[beforeEvent] instanceof Array).to.be(true);
|
||||
|
||||
vis._listeners[beforeEvent].forEach(function (listener, i) {
|
||||
expect(typeof listener.handler).to.be('function');
|
||||
expect(listener.handler).to.be(listeners[i]);
|
||||
it('should add an event and its listeners', function () {
|
||||
listeners.forEach(function (listener) {
|
||||
expect(vis.listeners(beforeEvent)).to.contain(listener);
|
||||
});
|
||||
|
||||
vis._listeners[afterEvent].forEach(function (listener, i) {
|
||||
expect(typeof listener.handler).to.be('function');
|
||||
expect(listener.handler).to.be(listeners[i]);
|
||||
listeners.forEach(function (listener) {
|
||||
expect(vis.listeners(afterEvent)).to.contain(listener);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add an event to the eventTypes.enabled array', function () {
|
||||
vis.eventTypes.enabled.forEach(function (eventType, i) {
|
||||
expect(eventType).to.be(events[i]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should attach an event and its listeners to the chart', function () {
|
||||
it('should cause a listener for each event to be attached to each chart', function () {
|
||||
var charts = vis.handler.charts;
|
||||
|
||||
charts.forEach(function (chart, i) {
|
||||
expect(typeof chart.on(beforeEvent) === 'function');
|
||||
expect(typeof chart.on(afterEvent) === 'function');
|
||||
expect(chart.on(beforeEvent) === listeners[i]);
|
||||
expect(chart.on(afterEvent) === listeners[i]);
|
||||
expect(chart.events.listenerCount(beforeEvent)).to.be.above(0);
|
||||
expect(chart.events.listenerCount(afterEvent)).to.be.above(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -233,30 +219,19 @@ define(function (require) {
|
|||
vis.off(afterEvent);
|
||||
});
|
||||
|
||||
it('should remove a listener from the _listeners[event] array', function () {
|
||||
it('should remove a listener', function () {
|
||||
var charts = vis.handler.charts;
|
||||
|
||||
expect(vis._listeners[beforeEvent].length).to.be(1);
|
||||
expect(vis._listeners[afterEvent].length).to.be(1);
|
||||
expect(vis.listeners(beforeEvent)).to.not.contain(listener1);
|
||||
expect(vis.listeners(beforeEvent)).to.contain(listener2);
|
||||
|
||||
// should still have the 2 events in the eventTypes enabled array
|
||||
expect(vis.eventTypes.enabled.length).to.be(2);
|
||||
|
||||
// Test that listener that was not removed is still present
|
||||
vis._listeners[beforeEvent].forEach(function (listener) {
|
||||
expect(typeof listener.handler).to.be('function');
|
||||
expect(listener.handler).to.be(listener2);
|
||||
});
|
||||
|
||||
vis._listeners[afterEvent].forEach(function (listener) {
|
||||
expect(typeof listener.handler).to.be('function');
|
||||
expect(listener.handler).to.be(listener2);
|
||||
});
|
||||
expect(vis.listeners(afterEvent)).to.not.contain(listener1);
|
||||
expect(vis.listeners(afterEvent)).to.contain(listener2);
|
||||
|
||||
// Events should still be attached to charts
|
||||
charts.forEach(function (chart) {
|
||||
expect(typeof chart.on(beforeEvent)).to.be('function');
|
||||
expect(typeof chart.on(afterEvent)).to.be('function');
|
||||
expect(chart.events.listenerCount(beforeEvent)).to.be.above(0);
|
||||
expect(chart.events.listenerCount(afterEvent)).to.be.above(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -264,28 +239,25 @@ define(function (require) {
|
|||
var charts = vis.handler.charts;
|
||||
vis.off(afterEvent);
|
||||
|
||||
// should remove 'brush' from _listeners object
|
||||
expect(vis._listeners[afterEvent]).to.be(undefined);
|
||||
|
||||
// should remove 'brush' from eventTypes.enabled array
|
||||
expect(vis.eventTypes.enabled.length).to.be(1);
|
||||
// should remove 'brush' event
|
||||
expect(vis.listeners(beforeEvent)).to.contain(listener2);
|
||||
expect(vis.listeners(afterEvent)).to.not.contain(listener2);
|
||||
|
||||
// should remove the event from the charts
|
||||
charts.forEach(function (chart) {
|
||||
expect(typeof chart.on(afterEvent)).to.be('undefined');
|
||||
expect(chart.events.listenerCount(beforeEvent)).to.be.above(0);
|
||||
expect(chart.events.listenerCount(afterEvent)).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the event from the eventTypes.enabled array as well as ' +
|
||||
'from the chart when the _listeners array has a length of 0', function () {
|
||||
it('should remove the event from the chart when the last listener is removed', function () {
|
||||
var charts = vis.handler.charts;
|
||||
vis.off(afterEvent, listener2);
|
||||
|
||||
expect(vis._listeners[afterEvent].length).to.be(0);
|
||||
expect(vis.eventTypes.enabled.length).to.be(1);
|
||||
expect(vis.listenerCount(afterEvent)).to.be(0);
|
||||
|
||||
charts.forEach(function (chart) {
|
||||
expect(typeof chart.on(afterEvent)).to.be('undefined');
|
||||
expect(chart.events.listenerCount(afterEvent)).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue