UNPKG

relu-core

Version:
160 lines (142 loc) 4.43 kB
var base = require("./base"); var ActionsList = require("./ActionsList"); module.exports = function rpVariable(value) { var p = base(); p.constructor = rpVariable; var valueContainer = [value]; var actionsList = new ActionsList(); actionsList.onChange = function() { base.atAtomicEnd(applyChangesWithEvents); }; p._get = function() { p.depend(); return valueContainer[0]; }; p._getUndependend = function() { return valueContainer[0]; }; p._actionsList = function() { return actionsList; }; p._actionsTarget = function() { return this; }; p._forgetUpdate = function() { applyChanges(false); }; function applyChangesWithEvents() { applyChanges(true); } function applyChanges(events) { function Applier(path, obj, property) { this.currentPath = path; this.currentObject = obj; this.currentProperty = property; this.changed = false; } Applier.prototype.enter = function(i, apply) { var obj = this.currentObject[this.currentProperty]; if(typeof i === "number") { if(!Array.isArray(obj)) throw new Error("Cannot access item " + i + " of " + obj + " at " + this.currentPath.join(".")); } else { if(Array.isArray(obj)) { obj.forEach(function(_, idx) { this.currentPath.push(idx); var applier = new Applier(this.currentPath, obj, idx); applier.enter(i, apply); applier.done(); this.currentPath.pop(); this.changed |= applier.changed; }, this); return; } else if(typeof obj !== "object" || obj === null) { // need to create the object first var old = obj; obj = this.currentObject[this.currentProperty] = {}; if(events) fireUpdatedForPath(this.currentPath, obj, old); } } this.currentPath.push(i); var applier = new Applier(this.currentPath, obj, i); apply(applier); this.currentPath.pop(); this.changed |= applier.changed; } Applier.prototype.update = function(v) { var o = this.currentObject[this.currentProperty]; if(v === undefined) delete this.currentObject[this.currentProperty]; else this.currentObject[this.currentProperty] = v; if(isDifferent(o, v)) this.changed = true; if(events) fireUpdatedForPath(this.currentPath, v, o); }; Applier.prototype.modify = function(fns) { var o = this.currentObject[this.currentProperty]; var v = o; fns.forEach(function(fn) { v = fn(v); }); this.currentObject[this.currentProperty] = v; if(isDifferent(o, v)) this.changed = true; if(events) fireUpdatedForPath(this.currentPath, v, o); }; Applier.prototype.remove = function(i) { if(i === Infinity) i = this.currentObject[this.currentProperty].length - 1; else if(i >= this.currentObject[this.currentProperty].length) throw new Error("Cannot remove out of range value from array"); var v = this.currentObject[this.currentProperty][i]; this.currentObject[this.currentProperty].splice(i, 1); this.changed = true; if(events) fireRemovedForPath(this.currentPath, i, v); }; Applier.prototype.add = function(i, v) { if(i === Infinity) i = this.currentObject[this.currentProperty].length; this.currentObject[this.currentProperty].splice(i, 0, v); this.changed = true; if(events) fireAddedForPath(this.currentPath, i, v); }; Applier.prototype.done = function() { if(events && this.changed) { fireChangedForPath(this.currentPath); } }; actionsList.apply(new Applier([], valueContainer, 0)); } function fireChangedForPath(path) { if(path.length === 0) { p._changed(); } else { p._nested("changed", path.slice()); } } function fireUpdatedForPath(path, v, o) { if(path.length === 0) { p._updated(v, o); } else { p._nested("updated", path.slice(), v, o); } } function fireAddedForPath(path, i, v) { if(path.length === 0) { p._added(i, v); } else { p._nested("added", path.slice(), i, v); } } function fireRemovedForPath(path, i, v) { if(path.length === 0) { p._removed(i, v); } else { p._nested("removed", path.slice(), i, v); } } p._writable = true; return p; }; function isDifferent(a, b) { if(a === b) return false; if(typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b)) return false; return true; }