Skip to content

Instantly share code, notes, and snippets.

@iainvdw
Created July 25, 2018 08:55
Show Gist options
  • Save iainvdw/e007f858206b6d8867d57e6d19036b92 to your computer and use it in GitHub Desktop.
Save iainvdw/e007f858206b6d8867d57e6d19036b92 to your computer and use it in GitHub Desktop.
Transmuter.js - simple state container store, fires events when property changes
import { Transmuter, listen } from './transmuter.js';
// Set up intial state
const initialState = {
name: 'lala',
isIt: true,
count: 123,
};
// Setup store
const storeName = 'shed';
const store = new Transmuter(storeName, initialState);
// Logger factory
const logger = name => (prop, value, oldValue) => {
document.body.insertAdjacentHTML(
'afterbegin',
`<p><strong>Logging ${name}</strong>
<br>
Prop changed: ${prop}. Value: ${value}. Old value: ${oldValue}</p>`,
);
};
// Listen to single prop
const listener1 = listen(store, 'name', logger('single prop'));
// Listen to multiple props
const listener2 = listen(store, ['name', 'count', 'isIt'], logger('multiple props'));
// Listen to props that don't exist yet
const listener3 = listen(store, 'notYet', logger('non-existing prop'));
// Add props to a listener
listener1.addProp('count');
// Remove prop from a listener
listener2.removeProp('isIt');
// Modify store state
store.state.name = 'testing!';
store.state.count = 456;
store.state.notYet = 'yes!'; // Set non-existent property
// Stop listener
listener1.stop();
// Modify store state again
store.state.name = 'testing again!';
store.state.notYet = 'uhuh!';
// Start listener again
listener1.start();
// More modifications!
store.state.name = 'and again!';
/**
* Store container object
*
* @author Iain van der Wiel
* @param {String} name Name of store
* @param {Object} target Target object containing the state to watch
* @param {Element} context Element or object to dispatch events on
* @returns {Transmuter} Store object containing name, context and state Proxy
*/
class Transmuter {
constructor(name, target, context = window) {
this.name = name;
this.context = context;
this.state = new Proxy(target, {
set: (store, prop, value) => {
// Return early if value hasn't changed
if (store[prop] === value) {
return true;
}
// Setup custom event
const event = new CustomEvent(`${name}:${prop}`, {
detail: {
prop,
value,
oldValue: store[prop],
},
});
// Set value like normal
const success = Reflect.set(store, prop, value);
if (success) {
// Dispatch name:prop event on context
this.context.dispatchEvent(event);
}
// Assignment of property in store succeeded, required per strict mode
return success;
},
});
}
}
/**
* Store listener
* @param {Transmuter} store Store object to listen to
* @param {String|Array} props Prop(s) of the store to watch
* @param {Function} handler Handler function that's called when a property changes
*/
const listen = (store, props, handler) => {
// Concat props in new array. This allows strings and arrays of props to listen to.
let propsToListen = [].concat(props);
// Listener callback that destructures values from event.detail
const listener = ({ detail: { prop, value, oldValue } }) => handler(prop, value, oldValue);
// Listen to single prop
const listenToProp = (prop) => {
store.context.addEventListener(`${store.name}:${prop}`, listener);
};
// Stop listening to single prop
const stopListeningToProp = prop => store.context.removeEventListener(`${store.name}:${prop}`, listener);
// Apply listener to watched props
const start = () => {
propsToListen.forEach(listenToProp);
};
// Remove listener to watched props
const stop = () => {
propsToListen.forEach(stopListeningToProp);
};
// Add prop to listen to
const addProp = (propToAdd) => {
listenToProp(propToAdd);
propsToListen.push(propToAdd);
};
// Remove listened prop
const removeProp = (propToRemove) => {
stopListeningToProp(propToRemove);
propsToListen = propsToListen.filter(prop => prop !== propToRemove);
};
// Kickstart the listening
start();
// Return listener API
return {
start,
stop,
addProp,
removeProp,
};
};
export { Transmuter, listen };
@iainvdw
Copy link
Author

iainvdw commented Jul 25, 2018

So, this has its own repository now: https://github.com/iainvdw/transmuter-store. Will develop this further in that repo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment