/**
* (event)Emitter renamed to avoid confusion with prvy's events
*/
var _ = require('underscore');
var SignalEmitter = module.exports = function (messagesMap) {
SignalEmitter.extend(this, messagesMap);
};
SignalEmitter.extend = function (object, messagesMap, name) {
if (! name) {
throw new Error('"name" parameter must be set');
}
object._signalEmitterEvents = {};
_.each(_.values(messagesMap), function (value) {
object._signalEmitterEvents[value] = [];
});
_.extend(object, SignalEmitter.prototype);
object._signalEmitterName = name;
};
SignalEmitter.Messages = {
/** called when a batch of changes is expected, content: <batchId> unique**/
BATCH_BEGIN : 'beginBatch',
/** called when a batch of changes is done, content: <batchId> unique**/
BATCH_DONE : 'doneBatch',
/** if an eventListener return this string, it will be removed automatically **/
UNREGISTER_LISTENER : 'unregisterMePlease'
};
/**
* Add an event listener
* @param signal one of Messages.SIGNAL.*.*
* @param callback function(content) .. content vary on each signal.
* If the callback returns SignalEmitter.Messages.UNREGISTER_LISTENER it will be removed
* @return the callback function for further reference
*/
SignalEmitter.prototype.addEventListener = function (signal, callback) {
this._signalEmitterEvents[signal].push(callback);
return callback;
};
/**
* remove the callback matching this signal
*/
SignalEmitter.prototype.removeEventListener = function (signal, callback) {
for (var i = 0; i < this._signalEmitterEvents[signal].length; i++) {
if (this._signalEmitterEvents[signal][i] === callback) {
this._signalEmitterEvents[signal][i] = null;
}
}
};
/**
* A changes occurred on the filter
* @param signal
* @param content
* @param batch
* @private
*/
SignalEmitter.prototype._fireEvent = function (signal, content, batch) {
var batchId = batch ? batch.id : null;
if (! signal) { throw new Error(); }
var batchStr = batchId ? ' batch: ' + batchId + ', ' + batch.batchName : '';
console.log('FireEvent-' + this._signalEmitterName + ' : ' + signal + batchStr);
_.each(this._signalEmitterEvents[signal], function (callback) {
if (callback !== null &&
SignalEmitter.Messages.UNREGISTER_LISTENER === callback(content, batch)) {
this.removeEventListener(signal, callback);
}
}, this);
};
SignalEmitter.batchSerial = 0;
/**
* Start a batch process
*
* @param batchName Name of the new batch
* @param orHookOnBatch Existing batch to hook on ("superbatch")
* @return A batch object (call `done()` when done)
* @private
*/
SignalEmitter.prototype.startBatch = function (name, orHookOnBatch) {
if (! orHookOnBatch) {
return new Batch(name, this);
}
name = orHookOnBatch.name + '/' + name;
var batch = new Batch(name, this);
orHookOnBatch.waitForMeToFinish(name + ':hook');
batch.addOnDoneListener(name, function () {
orHookOnBatch.done(name + ':hook');
});
return batch;
};
var Batch = function (name, owner) {
this.owner = owner;
this.name = name || 'x';
this.id = owner._signalEmitterName + SignalEmitter.batchSerial++;
this.waitFor = 0;
this.history = [];
this.doneCallbacks = {};
this.waitForMeToFinish(this.name);
this.owner._fireEvent(SignalEmitter.Messages.BATCH_BEGIN, this.id, this);
};
/**
* listener are stored in key/map fashion, so addOnDoneListener('bob',..)
* may be called several time, callback 'bob', will be done just once
* @param key a unique key per callback
* @param callback
*/
Batch.prototype.addOnDoneListener = function (key, callback) {
this.checkAlreadyDone('addOnDoneListener(' + key + ')');
this.doneCallbacks[key] = callback;
};
Batch.prototype.waitForMeToFinish = function (key) {
this.checkAlreadyDone('waitForMeToFinish(' + key + ')');
this.waitFor++;
this.history.push({wait: key, waitFor: this.waitFor});
return this;
};
Batch.prototype.done = function (key) {
this.checkAlreadyDone('done(' + key + ')');
key = key || '--';
this.waitFor--;
this.history.push({done: key, waitFor: this.waitFor});
if (this.waitFor === 0) {
this.doneTriggered = true;
_.each(this.doneCallbacks, function (callback) {
callback();
});
delete this.doneCallbacks; // prevents memory leaks
this.owner._fireEvent(SignalEmitter.Messages.BATCH_DONE, this.id, this);
}
};
Batch.prototype.checkAlreadyDone = function (addon) {
if (this.doneTriggered) {
var msg = 'Batch ' + this.name + ', ' + this.id + ' called ' + addon + ' when already done';
throw new Error(msg + ' ' + JSON.stringify(this.history));
}
};