/*
* Copyright 2020 Martin F. Schlegel Jr. | MIT AND BSD-3-Clause
*/
// Virtual Interfaces
/**
*
* @interface SimpleEventIntf
* @classdesc
*
* Simplified definition of a DOM `Event` for the purposes of {@link SimpleEventDispatcher} that only contains the `type` attribute.
* Although it is unlikely standard DOM events will be used with {@link SimpleEventDispatcher}, they do, nonetheless, implement this interface.
*
*
* <div class="see-also-section">
*
* <div class="see-also">
* <span class="label">See Also:</span>
* <a href="https://dom.spec.whatwg.org/#interface-event">https://dom.spec.whatwg.org/#interface-event</a>
* </div>
*
* </div>
*/
/**
* The type of this event.
*
* @member {string} SimpleEventIntf#type
*/
/**
*
* @interface SimpleEventListener
* @classdesc
*
* Definition of an event listener for the purposes of {@link SimpleEventDispatcher}. Effectively identical to the standard DOM
* `EventListener`.
*
*
* <div class="see-also-section">
*
* <div class="see-also">
* <span class="label">See Also:</span>
* <a href="https://dom.spec.whatwg.org/#callbackdef-eventlistener">https://dom.spec.whatwg.org/#callbackdef-eventlistener</a>
* </div>
*
* </div>
*
*/
/**
* Callback function to handle events for which this listener is registered.
*
* @function SimpleEventListener#handleEvent
* @param {SimpleEvent} event
*/
// Constructor
/**
* Default constructor.
*
* @constructor
* @classdesc
*
* Simple implementation of an event dispatcher that supports the registration of multiple listeners for various events. Although
* this type can be used on its own, it is often more convenient to extend it.
*
* This type is designed to have (loose) consistency with the DOM `EventTarget`, however events are simply dispatched to listeners in the
* order they are registered. I.e. there is no support for bubbling, cancelling, etc.
*
*
* <div class="see-also-section">
*
* <div class="see-also">
* <span class="label">See Also:</span>
* <a href="https://dom.spec.whatwg.org/#interface-eventtarget">https://dom.spec.whatwg.org/#interface-eventtarget</a>
* </div>
*
* </div>
*/
function SimpleEventDispatcher() {
'use strict';
this.listeners = {};
}
// Static methods
/**
* Utility function to validate an event type string, and convert it to a consistent case.
*
* @private
* @param {string} type An event type string.
* @returns The given type converted to a consistent case.
* @throws {ReferenceError} If type is not defined or is a zero-length string.
* @throws {TypeError} If type is not a string.
*/
SimpleEventDispatcher.processType = function (type) {
'use strict';
if (!type) {
throw new ReferenceError('Event type must be defined (and have a length greater than 0).');
}
if (typeof type !== 'string' && !(type instanceof String)) {
throw new TypeError('Event type must be a string.');
}
return type.toLowerCase();
};
// Instance methods
/**
* Adds the given `listener` for the given event `type`, provided it is not already registered for that `type`.
*
* @param {string} type Event type for which the given `listener` is to be registered.
* @param {(SimpleEventListener|function)} listener Listener to register.
* @param {boolean} [useCapture=false]
* Optional parameter added for consistency with the standard DOM `EventTarget.addEventListener` definition. If not {@link Nothing}, will print a warning on the console.
* @throws {ReferenceError} If `type` is not defined or is a zero-length string; if `listener` is not defined.
* @throws {TypeError} If type is not a `string`; if `listener` does not implement {@link SimpleEventListener} or is not a function.
*/
SimpleEventDispatcher.prototype.addEventListener = function (type, listener, useCapture) {
'use strict';
var listeners;
if (useCapture && console && console.warn) {
console.warn('Capture/bubble phase not supported by this event listener; events are simply dispatched to listeners in the order they are registered.');
}
type = SimpleEventDispatcher.processType(type);
if (!listener) {
throw new ReferenceError('Listener must be defined.');
}
if (typeof listener.handleEvent !== 'function' && typeof listener !== 'function') {
throw new TypeError('Listener must either define a handleEvent function or be a function itself.');
}
listeners = this.listeners[type];
if (!listeners) {
listeners = this.listeners[type] = [];
}
if (listeners.indexOf(listener) === -1) {
listeners.push(listener);
}
};
/**
* Removes the given `listener` for the given event `type`, provided it is currently registered for that `type`.
*
* @param {string} type Event type for which the given `listener` is to be removed.
* @param {(SimpleEventListener|function)} listener Listener to be removed.
* @param {boolean} [useCapture=false]
* Optional parameter added for consistency with the standard DOM `EventTarget.removeEventListener` definition. If not {@link Nothing}, will print a warning on the console.
* @throws {ReferenceError} If `type` is not defined or is a zero-length string.
* @throws {TypeError} If `type` is not a string.
*/
SimpleEventDispatcher.prototype.removeEventListener = function (type, listener, useCapture) {
'use strict';
var listeners, targetIndex;gh
if (useCapture && console && console.warn) {
console.warn('Capture/bubble phase not supported by this event listener; events are simply dispatched to listeners in the order they are registered.');
}
type = SimpleEventDispatcher.processType(type);
listeners = this.listeners[type];
if (!listeners) {
return;
}
targetIndex = listeners.indexOf(listener);
if (targetIndex === -1) {
return;
}
listeners.splice(targetIndex, 1);
};
/**
* Dispatches the given `event` to registered listeners. Listeners are called in the order they are added.
*
* @param {SimpleEventIntf} event Event to dispatch.
* @throws {ReferenceError} If `event` is not defined.
* @throws {TypeError} If `event` does not implement {@link SimpleEventIntf}.
*/
SimpleEventDispatcher.prototype.dispatchEvent = function (event) {
'use strict';
var listeners, listener, i;
if (!event) {
throw new ReferenceError('Event must be defined.');
}
if (typeof event.type !== 'string' && !(event.type instanceof String)) {
throw new TypeError('Event must, at minimum, define a type property.');
}
listeners = this.listeners[event.type.toLowerCase()];
if (!listeners) {
return;
}
for (i = 0; i < listeners.length; ++i) {
listener = listeners[i];
if (typeof listener.handleEvent === 'function') {
listener.handleEvent(event);
} else {
listener(event);
}
}
};
// Nested types
/**
*
* @constructor
* @implements SimpleEventIntf
* @param {string} type Type of this event. No case conversion is performed in this constructor; `type` is used as-given.
* @param {object} target Target of this event.
* @classdesc
*
* Simplistic, yet effective implementation of {@link SimpleEventIntf} that also includes the standard DOM
* `target` and `currentTarget` properties. Both properties are set to the given `target`.
*
*
* <div class="see-also-section">
*
* <div class="see-also">
* <span class="label">See Also:</span>
* <a href="https://dom.spec.whatwg.org/#dom-event-target">https://dom.spec.whatwg.org/#dom-event-target</a>
* </div>
*
* <div class="see-also">
* <span class="label">See Also:</span>
* <a href="https://dom.spec.whatwg.org/#dom-event-currenttarget">https://dom.spec.whatwg.org/#dom-event-currenttarget</a>
* </div>
*
* </div>
*
*/
SimpleEventDispatcher.SimpleEvent = function (type, target) {
'use strict';
this.type = type;
/**
* Target of this event.
*/
this.target = target;
/**
* Added for consistency with DOM event definition; same as {@link SimpleEventDispatcher.SimpleEvent#target}.
*/
this.currentTarget = target;
};