Skip to content

Instantly share code, notes, and snippets.

@hejrobin
Last active January 27, 2017 14:43
Show Gist options
  • Save hejrobin/37c6290a1360bb3c83a0537b844eb61f to your computer and use it in GitHub Desktop.
Save hejrobin/37c6290a1360bb3c83a0537b844eb61f to your computer and use it in GitHub Desktop.
Common ES7 modules I use for a simple Flux architecture.
/**
* @const int EVENT_MIN_LISTENERS
*/
const EVENT_MIN_LISTENERS = 1;
/**
* @const int EVENT_MAX_LISTENERS
*/
const EVENT_MAX_LISTENERS = 10;
/**
* @const Symbol EVENT_STORE
*/
const EVENT_STORE = Symbol('EVENT_STORE');
/**
* @private validateArguments
*
* Validates eventType and eventListener arguments.
*
* @param string eventType
* @param callable eventListener
*
* @throws TypeError
*
* @return void
*/
const validateArguments = (eventType, eventListener) => {
if (typeof eventType !== 'string') {
throw new TypeError();
}
if (typeof eventListener !== 'function') {
throw new TypeError();
}
}
/**
* @class EventEmitter
*
* Event emitter class implementation in ES6.
*/
export default class EventEmitter {
/**
* @var int maxListenerCount
*/
maxListenerCount = EVENT_MAX_LISTENERS;
/**
* Constructor
*
* Creates a new instance of EventEmitter.
*
* @return void
*/
constructor() {
this[EVENT_STORE] = {};
}
/**
* @getter eventStore
*
* Returns current event store.
*
* @return object
*/
get eventStore() {
return this[EVENT_STORE];
}
/**
* setMaxListenerCount
*
* Sets new max listener count per event type.
*
* @param int newMaxListenerCount
*
* @return void
*/
setMaxListenerCount(newMaxListenerCount) {
if (isNaN(newMaxListenerCount) === true) return;
let checkFloat = parseFloat(newMaxListenerCount);
if (((checkFloat | 0) === checkFloat) && newMaxListenerCount >= EVENT_MIN_LISTENERS) {
this.maxListenerCount = newMaxListenerCount;
}
}
/**
* getMaxListenerCount
*
* Returns current max listener count.
*
* @return int
*/
getMaxListenerCount() {
return this.maxListenerCount;
}
/**
* @getter eventTypes
*
* Returns all registered event types to current instance.
*
* @return array
*/
get eventTypes() {
return Object.keys(this.eventStore);
}
/**
* getListeners
*
* Returns all registered listeners to event type.
*
* @param string eventType
*
* @return array
*/
getListeners(eventType) {
if (this.eventTypes.indexOf(eventType) >= 0) {
return Object.values(this.eventStore[eventType]);
}
return [];
}
/**
* getListenerPosition
*
* Returns index of event listener in event store.
*
* @param string eventType
* @param callabke eventListener
*
* @return int
*/
getListenerPosition(eventType, eventListener) {
validateArguments(...arguments);
return this.getListeners(eventType).indexOf(eventListener);
}
/**
* hasListener
*
* Validates whether or not event type has a specific listener registered.
*
* @param string eventType
* @param callable eventListener
*
* @return bool
*/
hasListener(eventType, eventListener) {
validateArguments(...arguments);
if (this.eventTypes.indexOf(eventType) >= 0) {
return (this.getListenerPosition(eventType, eventListener)>= 0);
}
return false;
}
/**
* addListener
*
* Attempts to add an event listener to event type.
*
* @param string eventType
* @param callabke eventListener
*
* @return self
*/
addListener(eventType, eventListener) {
validateArguments(...arguments);
if (this.hasListener(eventType, eventListener) === true) {
return this;
}
if (this.getListeners(eventType).length >= this.maxListenerCount) {
throw new RangeError(`Possible memory leak, max listener count exceeded.`);
}
if (this.eventStore.hasOwnProperty(eventType) === false) {
this.eventStore[eventType] = [];
}
this.eventStore[eventType].push(eventListener);
return this;
}
// @alias this.on > this.addListener
on = ::this.addListener
/**
* addOnceListener
*
* Creates a self removing event listener and registerers it.
*
* @param string eventType
* @param callable eventListener
*
* @return this
*/
addOnceListener(eventType, eventListener) {
const selfRemovingEventListener = () => {
this.removeListener(eventType, selfRemovingEventListener);
eventListener.apply(this, arguments);
}
return this.addListener(eventType, selfRemovingEventListener);
}
// @alias this.once > this.addOnceListener
once = ::this.addOnceListener
/**
* removeListener
*
* Attempts to remove event listener.
*
* @param string eventType
* @param callable eventListener
*
* @return this
*/
removeListener(eventType, eventListener) {
validateArguments(...arguments);
if (this.hasListener(eventType, eventListener) === true) {
let listenerPosition = this.getListenerPosition(eventType, eventListener);
this.eventStore[eventType].splice(listenerPosition, 1);
}
return this;
}
// @alias this.off > this.removeListener
off = ::this.removeListener
/**
* removeListeners
*
* Removes all specified event listeners.
*
* @param string eventType
* @param callabke eventListeners, ...
*
* @return self
*/
removeListeners(eventType, ...eventListeners) {
eventListeners.forEach((eventListener) => {
this.removeListener(eventType, eventListener);
});
return this;
}
/**
* removeAllListeners
*
* Removes all listeners to registered event type.
*
* @param string eventType
*
* @return self
*/
removeAllListeners(eventType) {
if (typeof eventType !== 'string') {
throw new TypeError();
}
if (this.eventStore.hasOwnProperty(eventType) === true) {
this.eventStore[eventType] = [];
}
return this;
}
emit(eventType, ...listenerArguments) {
if (this.eventStore.hasOwnProperty(eventType) === true) {
this.eventStore[eventType].forEach(eventListener => {
eventListener.apply(null, listenerArguments)
});
}
}
}
// Required packages
import EventEmitter from './EventEmitter.es7';
/**
* @const Symbol STORE_REGISTRY
*/
const STORE_REGISTRY = Symbol('STORE_REGISTRY');
/**
* @class Store
*
* Primitive key-value store class, extends EventEmitter.
*
* @emits change
*/
export default class Store extends EventEmitter {
constructor() {
super();
this[STORE_REGISTRY] = {};
}
/**
* @getter storage
*
* Returns current store.
*
* @return object
*/
get storage() {
return this[STORE_REGISTRY];
}
/**
* @getter entries
*
* Returns entries of store.
*
* @return array
*/
get entries() {
return Object.entries(this.store);
}
/**
* @getter keys
*
* Returns keys of store.
*
* @return array
*/
get keys() {
return Object.keys(this.store);
}
/**
* @getter values
*
* Returns values of store.
*
* @return array
*/
get values() {
return Object.values(this.store);
}
/**
* has
*
* Validates whether or not store key data exists.
*
* @param string key
*
* @return bool
*/
has(key) {
return this.storage.hasOwnProperty(key) === true;
}
/**
* get
*
* Attempts to return storage value, returns undefined if not set.
*
* @param string key
*
* @return mixed
*/
get(key) {
if (this.has(key) === true) {
return this.storage[key];
}
return undefined;
}
/**
* set
*
* Sets new storage value to key, emits 'change' event if value changes.
*
* @param string key
* @param mixed data
*
* @return void
*/
set(key, data) {
let previousData = this.get(key);
if (data !== previousData) {
this.storage[key] = data;
if (previousData !== undefined) {
this.emit('change', previousData, data);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment