Last active
June 11, 2019 16:06
-
-
Save Floofies/e886496ac97cb1ab9210a364c63001c3 to your computer and use it in GitHub Desktop.
Tiny baby MVVM with nestable Views
This file contains hidden or 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
// Ultra basic DataStore with publisher/subscriber model | |
function DataStore(state = null) { | |
this.state = {}; | |
if (state !== null) this.state = state; | |
this.subscribers = []; | |
} | |
DataStore.prototype.stateChanged = function () { | |
for (const callback of this.subscribers) callback(() => this.getState()); | |
}; | |
DataStore.prototype.setState = function (newState) { | |
this.state = newState; | |
this.stateChanged(); | |
}; | |
DataStore.prototype.getState = function () { | |
return this.state; | |
}; | |
DataStore.prototype.subscribe = function (callback) { | |
this.subscribers.push(callback); | |
}; | |
DataStore.prototype.addAction = function (name, callback) { | |
this[name] = (...args) => { | |
const newState = callback(this.state, ...args); | |
if ((typeof newState) === "undefined") return; | |
this.setState(newState); | |
}; | |
this[name].bind(this); | |
}; | |
function View(renderer = null) { | |
this.renderer = renderer; | |
this.state = {}; | |
this.oldState = {}; | |
this.children = {}; | |
this.childCount = 0; | |
} | |
View.prototype.shouldRender = function () { | |
return true; | |
}; | |
View.prototype.render = function (state = null) { | |
if (state === null) state = this.state; | |
if (!this.shouldRender(state)) return; | |
if (this.renderer !== null) this.renderer(state, this.oldState); | |
if (this.childCount === 0) return; | |
for (const child of Object.values(this.children)) child.setState(state); | |
}; | |
View.prototype.setState = function (state, render = true) { | |
this.oldState = this.state; | |
this.state = state; | |
if (render) this.render(); | |
}; | |
View.prototype.watch = function (dataStore) { | |
dataStore.subscribe(getState => this.setState(getState())); | |
}; | |
View.prototype.addChild = function (label, renderer = null) { | |
this.childCount++; | |
if ((typeof label) !== "string") label = this.childCount.toString(); | |
const child = renderer instanceof View ? renderer : new View(renderer); | |
this.children[label] = child; | |
return child; | |
}; | |
View.prototype.removeChild = function (child) { | |
if ((typeof child) !== "object") { | |
if (child in this.children) { | |
delete this.children[child]; | |
return; | |
} | |
} | |
if (!(child instanceof View)) return; | |
for (const label of this.children) { | |
if (this.children[label] === child) { | |
delete this.children[label]; | |
} | |
} | |
}; | |
/* EXAMPLE - Logs "bar" to the console: | |
// Create a DataStore with some initial state: | |
const datastore = new DataStore({foo: null}); | |
// Create a new top-level View (AKA "Glue View") without a renderer: | |
const topView = new View(); | |
// Create a new View with a renderer, and add it as a child of the top-level View: | |
const childView = topView.addChild("Child View", state => console.log(state.foo)); | |
// Subscribe the top-level View to the DataStore, so that it renders when the DataStore state is updated: | |
topView.watch(datastore); | |
// Mount the top-level View (Children are mounted too) so that it renders when updated: | |
topView.mount(); | |
// Set the state of the DataStore, causing the subscribed Views to render: | |
datastore.setState({foo: "bar"}); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment