Skip to content

Commit

Permalink
Make the code more DRY and more flexible using EventEmitter2.
Browse files Browse the repository at this point in the history
Add also a way to output a log of the `.trigger` actions

The code that was doing event management was copy and pasted in 3 places.
Plus it has a mechanism that prevent the unprocessed listeners to execute if a listener return false. This mechanisms is error prone since it depends on the order in which the event listeners (.on) are registered. It seemed that the mechanism was not used beside optimization.
  • Loading branch information
ChrisCinelli committed Jan 25, 2016
1 parent 4317fc0 commit 37457e2
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 78 deletions.
83 changes: 5 additions & 78 deletions lib/CoreBot.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ var simple_storage = require(__dirname + '/storage/simple_storage.js');
var ConsoleLogger = require(__dirname + '/console_logger.js');
var LogLevels = ConsoleLogger.LogLevels;

var EE = require(__dirname + '/events.js');

function Botkit(configuration) {
var botkit = {
events: {}, // this will hold event handlers
config: {}, // this will hold the configuration
tasks: [],
taskCount: 0,
Expand All @@ -33,8 +34,6 @@ function Botkit(configuration) {
this.sent = [];
this.transcript = [];

this.events = {};

this.vars = {};

this.topics = {};
Expand Down Expand Up @@ -140,30 +139,7 @@ function Botkit(configuration) {
};


this.on = function(event, cb) {
botkit.debug('Setting up a handler for', event);
var events = event.split(/\,/g);
for (var e in events) {
if (!this.events[events[e]]) {
this.events[events[e]] = [];
}
this.events[events[e]].push(cb);
}
return this;
};

this.trigger = function(event, data) {
if (this.events[event]) {
for (var e = 0; e < this.events[event].length; e++) {
var res = this.events[event][e].apply(this, data);
if (res === false) {
return;
}
}
} else {
botkit.debug('No handler for ', event);
}
};
EE.extendObject(this, EE.create({newListener : true}));

// proceed to the next message after waiting for an answer
this.next = function() {
Expand Down Expand Up @@ -415,7 +391,6 @@ function Botkit(configuration) {
this.botkit = botkit;
this.bot = bot;

this.events = {};
this.source_message = message;
this.status = 'active';
this.startTime = new Date();
Expand Down Expand Up @@ -462,31 +437,7 @@ function Botkit(configuration) {

};

this.on = function(event, cb) {
botkit.debug('Setting up a handler for', event);
var events = event.split(/\,/g);
for (var e in events) {
if (!this.events[events[e]]) {
this.events[events[e]] = [];
}
this.events[events[e]].push(cb);
}
return this;
};

this.trigger = function(event, data) {
if (this.events[event]) {
for (var e = 0; e < this.events[event].length; e++) {
var res = this.events[event][e].apply(this, data);
if (res === false) {
return;
}
}
} else {
botkit.debug('No handler for ', event);
}
};

EE.extendObject(this, EE.create({newListener : true}));

this.getResponsesByUser = function() {

Expand Down Expand Up @@ -638,31 +589,7 @@ function Botkit(configuration) {
return this;
};

botkit.on = function(event, cb) {
botkit.debug('Setting up a handler for', event);
var events = (typeof(event) == 'string') ? event.split(/\,/g) : event;

for (var e in events) {
if (!this.events[events[e]]) {
this.events[events[e]] = [];
}
this.events[events[e]].push(cb);
}
return this;
};

botkit.trigger = function(event, data) {
if (this.events[event]) {
for (var e = 0; e < this.events[event].length; e++) {
var res = this.events[event][e].apply(this, data);
if (res === false) {
return;
}
}
} else {
botkit.debug('No handler for ', event);
}
};
EE.extendObject(botkit, EE.create({newListener : true}));

botkit.startConversation = function(bot, message, cb) {
botkit.startTask(bot, message, function(task, convo) {
Expand Down
129 changes: 129 additions & 0 deletions lib/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Implement a pub sub sing the great EventEmitter2.
* The basic interface is:
* trigger(event, [param1, param2, ...])
* on(events, function(param1, param2, ...){ ... })
* It also log events with stack trace, when the DEBUG options are active
**/


var EventEmitter2 = require('eventemitter2').EventEmitter2;
var debug = require('debug');


/**
* Execute each of the listeners that may be listening for the specified event
* name in order with the list of arguments.
* @param {string} event - the event name
* @param {*[]} [data] - an array of data being passed.
**/

EventEmitter2.prototype.trigger = function(event, data) {
var args = [event];
if (data) for(var i in data){
args.push(data[i]);
}
EventEmitter2.prototype.emit.apply(this, args);

// for chaining
return this;
}

var oldOn = EventEmitter2.prototype.on;

/**
* Callback of the on function.
* The event name is avaiable in this.event
*
* @callback eventCallback
* @param {...*} [opt] - List of arguments passed in the trigger function
*/

/**
* Adds a listener to the end of the listeners array for the specified event.
* Support:
* - simple string as one event
* - a comma separated string to define multiple events
* - an array of strings to define multiple events
*
* @param {string|string[]} event - the event's name or events' names
* @param {eventCallback} eventFunction - fuction associated to the event(s)
**/

EventEmitter2.prototype.on = function (event){
var events = (typeof(event) == 'string') ? event.split(/\,/g) : event;

//For 1 or not an array
if (!(events.length >= 2)){
oldOn.apply(this, arguments);
// for chaining
return this;
}

// For 2+ events
var l = arguments.length;
var args = new Array(l);
for (var i = 1; i < l; i++) args[i] = arguments[i];

for (var i in events) {
args[0] = events[i];
oldOn.apply(this, args);
}

// for chaining
return this;
}

/**
* Create a EventEmitter2
* Attach also a default tracer when an event is triggered.
*
* @param {Object} opt - Options for eventEmitter2 constructor (See https://github.com/asyncly/EventEmitter2#differences-non-breaking-compatible-with-existing-eventemitter)
**/

EventEmitter2.create = function(opt){
var ret = new EventEmitter2(opt);

var trace = false;
if (debug.enabled("botkit:events:stackTrace:trigger")){
trace = true;
}

ret.onAny(function(event){
if (trace) {
// NOTE: it is NOT an error! Just an effective way to get a trace.
var here = new Error("The event Trace");
debug("botkit:events:trigger:" + this.event)(arguments, here.stack);
} else {
debug("botkit:events:trigger:" + this.event)(arguments);
}
});

return ret;
}

/**
* Utility to add alias `functions` of a object ee in a object o
* and create an alias of ee as o[member]
*
* Ex : EventEmitter2.extendObject(this, EventEmitter2.create());
*
* @param {Object} o - the object that receives the alias function
* @param {Object} ee - the EventEmitter2 object to be associated with o
* @param {string} [member] - the alias of the EventEmitter2 in o (default _EE)
* @param {string[]} [functions] - the names of the function to be aliased
**/

EventEmitter2.extendObject = function(o, ee, member, functions){
if(!member) member = '_EE';
if(!functions) functions = ['on', 'off', 'trigger', 'many', 'once'];
o[member] = ee;
for (var i in functions){
var f = functions[i];
// create o.on = o._EE.on ... etc
o[f] = o[member][f].bind(o[member]);
}
return o;
}

module.exports = EventEmitter2;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"main": "lib/Botkit.js",
"dependencies": {
"body-parser": "^1.14.2",
"debug": "2.2.0",
"eventemitter2": "0.4.14",
"express": "^4.13.3",
"jfs": "^0.2.6",
"mustache": "^2.2.1",
Expand Down
63 changes: 63 additions & 0 deletions tests/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
var should = require('should');
var EE = require('../lib/events.js');

describe('Test events', function() {
it('should create an EventEmitter2', function(done) {
var ev = EE.create();
should(ev).be.an.Object(ev);
done();
});

it('should trigger an event', function(done) {
var ev = EE.create();
ev.on('event1', function(param) {
done();
});
ev.trigger('event1');
});

it('should trigger two differnt events defined as a comma separated string', function(done) {
var ev = EE.create();
var eventsLeft = 2;
ev.on('event2,event3', function(param) {
eventsLeft--;
should(param).be.equalOneOf([11, 12]);
if (eventsLeft == 0) done();
});
ev.trigger('event2', [11]);
ev.trigger('event3', [12]);
});

it('should trigger two differnt events defined as an array', function(done) {
var ev = EE.create();
var eventsLeft = 2;
ev.on(['event4', 'event5'], function(param) {
eventsLeft--;
should(param).be.equalOneOf([21, 22]);
if (eventsLeft == 0) done();
});
ev.trigger('event5', [22]);
ev.trigger('event4', [21]);
});

it('should extend an object', function(done) {
var MyObject = function() {
EE.extendObject(this, EE.create());
};

var myObject = new MyObject();

should(myObject).have.ownProperty('_EE');

var eventsLeft = 2;
myObject.on(['event6', 'event7'], function(param) {
eventsLeft--;
should(param).be.equalOneOf([25, 26]);
if (eventsLeft == 0) done();
});
// Trigger on the main object
myObject.trigger('event6', [25]);
// Trigger on the EventEmitter member
myObject._EE.trigger('event7', [26]);
});
});

0 comments on commit 37457e2

Please sign in to comment.