UNPKG

awv3

Version:
144 lines (131 loc) 5.36 kB
import v4 from 'uuid-v4'; import omit from 'lodash/omit'; import cloneDeep from 'lodash/cloneDeep'; import { halt } from '../core/error'; import { createObserver } from './helpers'; // Store mixin which creates four actions: register(id, props), unregister(id), update(id, props) & patch(props) export const mixin = (name, subReducer) => { const references = {}; const types = { register: `${name}/REGISTER`, registerQueue: `${name}/REGISTERQUEUE`, unregister: `${name}/UNREGISTER`, update: `${name}/UPDATE`, merge: `${name}/MERGE` }; return { references, types, actions: { register: (reference, props) => dispatch => { // Store references to the actual lifecycle class making the register call references[reference.id] = reference; dispatch({ type: types.register, id: reference.id, props }); }, unregister: ids => dispatch => { let objects = Array.isArray(ids) ? ids : [ids]; dispatch({ type: types.unregister, ids }); objects.forEach(id => { // Destroy & remove reference if (references[id]) { references[id].destroy(false); delete references[id]; } }); }, update: (id, props) => ({ type: types.update, id, props }), merge: props => ({ type: types.merge, props }) }, reducer(state = {}, { type, id, ...payload }) { switch (type) { case types.register: return { ...state, [id]: { id: id, ...payload.props } }; case types.unregister: return omit(state, payload.ids); case types.update: return { ...state, [id]: { ...state[id], ...payload.props } }; case types.merge: return Object.keys(payload.props) .reduce( (prev, key) => ({ ...prev, [key]: { id: key, ...prev[key], ...payload.props[key] } }), state ); default: if (payload && id && state[id]) { return { ...state, [id]: subReducer(state[id], { type, ...payload }) }; } } return state; } }; }; // Base for all objects tied to the store. Lifecycle takes care of the initial // register, watches for removal, manages child-relations and observers // Lifecycle eligiable objects must have store actions that expose: // 1. register(id, props) method // 2. unregister(id) method // 3. update(id, props) method export default (Base = class {}, arg) => class Lifecycle extends Base { constructor( session = halt('Lifecycle object is missing a session'), actions = halt('Lifecycle object is missing link to actions'), selector = halt('Lifecycle object is missing a selector'), props ) { super(arg); this.id = v4(); this.session = session; this.store = session.store; this.actions = actions; this.getState = () => selector(this.store.getState()); this.subscriptions = new Set(); this.dependencies = []; // Register object and store original properties this.store.dispatch(actions.register(this, props)); this.props = cloneDeep(props); // Create managed observer let observer = createObserver(this.store, this.getState); this.observe = (selector, callback, options) => observer(selector, callback, { manager: this.managed && this.addSubscription.bind(this), ...options }); // Make props available on the object with automatic setters/getters for (let prop in props) { !this[prop] && Object.defineProperty(this, prop, { configurable: true, get: () => { let state = this.getState(); return state && state[prop]; }, set: value => this.store.dispatch(actions.update(this.id, { [prop]: value })) }); } } reset() { this.store.dispatch(this.actions.update(this.id, this.props)); } destroy(dispatchUnregister = true) { if (dispatchUnregister) { this.store.dispatch(this.actions.unregister(this.id)); } else { this.removeSubscriptions(); this.__onDestroyed && this.__onDestroyed(); this.onDestroyed(); this.dependencies.slice().forEach(child => child.destroy()); this.dependencies = []; } } addSubscription(unsubscribe) { this.subscriptions.add(unsubscribe); return unsubscribe; } removeSubscription(unsubscribe) { unsubscribe(); this.subscriptions.delete(unsubscribe); } removeSubscriptions() { this.subscriptions.forEach(unsubscribe => unsubscribe()); this.subscriptions.clear(); this.dependencies.forEach(child => child.removeSubscriptions()); } onDestroyed() {} };