src/Store.js
let generatedId = 1;
function generateId() {
return generatedId++;
}
/**
* A simple Store which receive all state changes with an event (string).
*/
export class Store {
/**
* The constructor.
* @param {object} initialeState the initial state.
*/
constructor(initialeState = {}) {
/**
* The state of store.
* @type {object}
*/
this.state = initialeState;
/**
* Don't touch :)
* @type {Object}
*/
this.subscribers = {};
/**
* Don't touch :)
* @type {Array}
*/
this.componentsSubscribes = [];
/**
* Don't touch :)
* @type {Array}
*/
this.componentsToUnmount = [];
}
/**
* Method to call to change the state.
* @param {object} newState the new state (you can put only attributes changed).
* @param {...string} events events to the origin of the state change.
*/
updateState(newState, ...events) {
const oldState = this.state;
this.state = {...this.state, ...newState};
const sourceEvent = events.length === 1 ? events[0] : events;
const eventsToPropagate = events.concat('*');
let index = eventsToPropagate.length;
while (index--) {
const event = eventsToPropagate[index];
if (this.subscribers[event]) {
for (const id in this.subscribers[event]) {
if (this.subscribers[event].hasOwnProperty(id)) {
this.subscribers[event][id](event === '*' ? sourceEvent : event, this.state, oldState);
}
}
}
}
}
/**
* Method to call to subscribe to an event.
*
* There is a special event that catches all events : '*'.
*
* @param {string} event event to subscribe.
* @param {function(event: string, state: object, oldState:object)} callback the callback called when receive the event.
* @param {Component} component use internally to unsubcribe component when node disappear
* @return {number} the id to put in param of {@link unsubscribe} to unsubscribe.
*/
subscribe(event, callback = state => {}, component) {
if (!this.subscribers[event]) {
this.subscribers[event] = {};
}
const id = generateId();
this.subscribers[event][id] = callback;
if (component) {
let componentWrapper = undefined;
for (const componentAlreadySubcribes of this.componentsSubscribes) {
if (componentAlreadySubcribes.component === component) {
componentWrapper = componentAlreadySubcribes;
break;
}
}
if (!componentWrapper) {
componentWrapper = {
component,
subscribes: []
};
this.componentsSubscribes.push(componentWrapper);
}
componentWrapper.subscribes.push({
event,
id
});
}
return id;
}
/**
* Unsubscribe.
* @param {number} id id received at {@link subscribe}
*/
unsubsribe(id) {
for (let event of Object.keys(this.subscribers)) {
if (this.subscribers[event][id]) {
delete this.subscribers[event][id];
}
}
}
/**
* Unsubcribe all subcribers.
*/
unsubscribeAll() {
this.subscribers = {};
this.componentsSubscribes = [];
this.componentsToUnmount.forEach(component => component.componentDidUnmount());
this.componentsToUnmount = [];
}
/**
* Unsubscribe.
* @param {string} event event sent to {@link subscribe}
* @param {number} id id received at {@link subscribe}
*/
unsubscribeByEventAndId(event, id) {
if (this.subscribers[event][id]) {
delete this.subscribers[event][id];
}
}
}