relu-core
Version:
160 lines (142 loc) • 4.43 kB
JavaScript
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;
}