Created
July 25, 2018 08:55
-
-
Save iainvdw/e007f858206b6d8867d57e6d19036b92 to your computer and use it in GitHub Desktop.
Transmuter.js - simple state container store, fires events when property changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
So, this has its own repository now: https://github.com/iainvdw/transmuter-store. Will develop this further in that repo!