Skip to content

Instantly share code, notes, and snippets.

@jcgregorio
Last active August 15, 2017 04:37
Show Gist options
  • Select an option

  • Save jcgregorio/482c5b6b7ee4e2c78004a5df7fe48461 to your computer and use it in GitHub Desktop.

Select an option

Save jcgregorio/482c5b6b7ee4e2c78004a5df7fe48461 to your computer and use it in GitHub Desktop.
Binds a Redux state store to and from HTML elements.
// Binder makes it easy to setup bindings between data stored in a Redux
// store and HTML elements.
class Binder {
// store - A Redux store.
// dispatch - A function called to update the state in store.
// ele - The root element where all element searches begin from, i.e.
// querySelectorAll is run on this element. Defaults to document.
constructor(store, dispatch, ele = document) {
this.to = [];
this.store = store;
this.oldState = {}; // Empty object so that filling in the default value triggers writes.
this.ele = ele;
this.dispatch = dispatch;
this.store.subscribe(this._newState.bind(this));
}
_newState() {
let delta = sr.object.getDelta(this.store.getState(), this.oldState);
this.to.forEach(item => {
let value = delta;
for (var i = 0; i < item.parsedPath.length; i++) {
value = value[item.parsedPath[i]];
if (value === undefined) {
return
}
}
item.func(value);
});
}
// add - Add a binding between some part of the state and an element.
//
// statePath - A "." separated path into the state object. Ex:
// The statePath "a.b" selects 2 in the object {a: { b: 2, c: 3}}.
// The empty statePath, "", selects any change to the state.
// query - A CSS selector that determines which element(s) are being bound.
// The selector will be run from the 'ele' passed into the
// constructor. Ex: "div > li", "length".
// to - Defines how the state maps to the element. Can be an object of the form:
//
// {
// attr: "input",
// content: false,
// };
//
// Where 'attr' is the name of the DOM attribute to set.
// If content is true then the attribute is treated as a Content attribute.
// Not specifying 'content' is the same as specifying content: false.
//
// The value of 'to' can also be a function that takes a single argument of the state
// value at the given statePath. The function will be called every time
// value at the given statePath changes. This can be used, for example, to expand
// templates based on the updated state.
//
// from - [Optional] Defines how the elements value maps to the state. Can be
// an object of the form:
//
// {
// event: "click",
// proc: e => e.target.dataset.foo,
// filter: e => e.target.tagName == "LI",
// }
//
// Where 'event' is the name of the event to register for. When the
// event is fired 'proc' will extract the value to store in the state
// at statePath. The 'filter' is optional and can be used to filter out
// unwanted events.
//
// If the 'listener' is set on the object then its value, which should
// be a function, is added as an event handler to the selected
// elements. The function should update the store's state when called.
//
add(statePath, query, to, from) {
let parsedPath = statePath.split(".");
if (statePath === "") {
parsedPath = [];
}
let pathLeadSegments = statePath.split(".");
let finalPathSegment = pathLeadSegments.pop();
let elements = [].slice.call(this.ele.querySelectorAll(query));
let toFunc = undefined;
if (typeof to === "object") {
if (to.content) {
toFunc = state => elements.forEach(ele => ele.setAttribute(to.attr, state));
} else {
toFunc = state => elements.forEach(ele => ele[to.attr] = state);
}
} else {
toFunc = to;
}
this.to.push({
func: toFunc,
parsedPath: parsedPath,
});
if (!from) {
return
}
if (from.proc) {
elements.forEach(ele => ele.addEventListener(from.event, e => {
if (from.filter && !from.filter(e)) {
return
}
let state = dup(this.store.getState());
pathLeadSegments.forEach(segment => { state = state[segment]; });
state[finalPathSegment] = from.proc(e);
this.dispatch(state);
}));
} else if (from.listener) {
elements.forEach(ele => ele.addEventListener(from.event, from.listener));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment