Skip to content

Instantly share code, notes, and snippets.

@Floofies
Last active June 11, 2019 16:06
Show Gist options
  • Save Floofies/e886496ac97cb1ab9210a364c63001c3 to your computer and use it in GitHub Desktop.
Save Floofies/e886496ac97cb1ab9210a364c63001c3 to your computer and use it in GitHub Desktop.
Tiny baby MVVM with nestable Views
// 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