Source: tools/eventEmitter.js

// inspired by tiny-events:
// https://github.com/ZauberNerd/tiny-events
// MIT License (MIT), Copyright (c) 2014 Björn Brauer

define(['mmirf/util//isArray', 'mmirf/util//toArray'], function(isArray, toArray){

/**
 * <blockquote>
 * Inspired by <a href="https://github.com/ZauberNerd/tiny-events">tiny-events</a><br>
 * MIT License (MIT), Copyright (c) 2014 Björn Brauer
 * </blockquote>
 *
 * Simple Event Emitter / Manager.
 *
 * Optionally, a list of supported events can be specified:
 * if a list of supported events is given, trying the register a listener for an
 * unsupported event will cause an error.
 *
 * @param       {any} [thisArg] OPTIONAL
 *                              this context when emitting events:
 *                              if omitted, the this context will be the EventEmitter
 *                              instance.
 *                              WARNING: if thisArg is an Array, then events argument
 *                                       must also be given, either as an Array, or
 *                                       <code>false</code> for disabling the
 *                                       supported events restriction
 *
 * @param       {Array<string>} [events] OPTIONAL
 * 															A list of supported event types/names:
 * 															if calling EventEmitter.on() with an unsupported
 * 															type, an error will be thrown.
 * 															If omitted, any event type/names is allowed.
 * @constructor
 * @memberOf mmir.tools
 */
function EventEmitter(thisArg, events) {
	if (isArray(thisArg) && typeof events === 'undefined') {
		events = thisArg;
		thisArg = void(0);
	}
	this._ctx = typeof thisArg === 'undefined'? this : thisArg;
	this._events = events? new Set(events) : null;
	this._listeners = new Map();
}

/**
 * Verifies that an event type can be used with the EventEmitter instance
 * (see <code>events</code> argument in constructor).
 *
 * @param       {string} type the event type to check
 *
 * @throws Error if the event type is not supported by the EventEmitter instance
 */
EventEmitter.prototype.verify = function _on(type) {
	if (this._events && !this._events.has(type)) {
		throw new Error('unsupported event '+type+', must be one of '+Array.from(this._events));
	}
};

/**
 * Register a listener for an event type.
 *
 * Will be ignored, if the listener already is registered for that event type.
 *
 * If a list of supported event types is specified for the EventEmitter instance,
 * and <code>type</code> is not a supported type, an error will be thrown.
 *
 * @param       {string} type the event type
 * @param       {Function} listener this listener that will be registered
 * @return      {boolean} <code>true</code> if the listener was registered
 *
 * @throws Error if the event type is not supported by the EventEmitter instance
 */
EventEmitter.prototype.on = function _on(type, listener) {
	this.verify(type);

	var l = this._listeners.get(type);
	if (!l) {
		l = new Set();
		this._listeners.set(type, l);
	}

	if(!l.has(listener)){
		l.add(listener);
		return true;
	}

	return false;
};

/**
 * Register a listener for an event type to be called one time.
 *
 * @param       {string} type the event type
 * @param       {Function} listener this listener that will be registered
 * @return      {boolean} <code>true</code> if the listener was registered
 *
 * @throws Error if the event type is not supported by the EventEmitter instance
 *
 * @see #on
 */
EventEmitter.prototype.once = function _once(type, listener) {
	var self = this;

	function __once() {
		var args = toArray(arguments);
		self.off(type, __once);
		listener.apply(self._ctx, args);
	}

	__once.listener = listener;

	return this.on(type, __once);
};

/**
 * Unregister a listener for an event.
 *
 * @param       {string} type the event type
 * @param       {Function} [listener] OPTIONAL
 * 												 The listener to be removed.
 * 												 If omitted, all listeners for the event type will be
 * 												 removed.
 * @return      {boolean} <code>true</code> if the listener was removed
 */
EventEmitter.prototype.off = function _off(type, listener) {
	var l = this._listeners.get(type);
	if (!l) {
		return false;
	}

	if (typeof listener === 'undefined') {
		this._listeners.delete(type);
		return l.size > 0;
	}

	if(l.delete(listener)) {
		//NOTE remove l, if it becomes empty!
		if(l.size === 0) {
			this._listeners.delete(type);
		}
		return true;
	}

	return false;
};

/**
 * Emit an event to registered listeners.
 *
 * @param       {string} type the event type that will be emitted
 * @param       {...any} [args] OPTIONAL arguments that will be emitted to
 *                              the listeners
 * @return      {boolean} <code>true</code> if at least one listener was notified
 */
EventEmitter.prototype.emit = function _emit(type) {
	var l = this._listeners.get(type);
	if (!l || l.size === 0) {
		return false;
	}

	var size = arguments.length;
	var args = new Array(size - 1);
	for (var i = 1; i < size; ++i) {
		args[i - 1] = arguments[i];
	}

	l.forEach(function __emit(listener) {
		listener.apply(this, args);
	}, this._ctx);

	return true;
};

/**
 * Check if the EventEmitter instance has any listener registered.
 *
 * @return      {boolean} <code>true</code> if no listener is registered
 */
EventEmitter.prototype.empty = function _empty() {
	//ASSERT this._listeners does not contain empty event type collections/Sets
	//       (that is: no empty Sets will be added & if a Set becomes empty, it will be removed)
	return this._listeners.size === 0;
};

/**
 * Get a list of registered listeners for an event type.
 *
 * @param       {string} type the event type
 * @return      {Array<Function>} a list of listeners or undefined, if there
 * 																are none for the event type
 */
EventEmitter.prototype.get = function _get(type) {
	var l = this._listeners.get(type);
	//ASSERT if l is present, then l.size > 0
	return l? toArray(l) : l;
};

/**
 * Check if listener is registered for an event.
 *
 * @param       {string} type the event type
 * @param       {Function} [listener] OPTIONAL
 * 												 The listener to be checked.
 * 												 If omitted, it will be checked, if there is any listener
 * 												 of the event type.
 * @return      {boolean} <code>true</code> if the listener (or any if listener
 * 												 was not specified) is registered for the event type
 */
EventEmitter.prototype.has = function _has(type, listener) {
	var l = this._listeners.get(type);
	//ASSERT if l is present, then l.size > 0
	return l? (listener? l.has(listener) : true) : false;
};

return EventEmitter;

});