UNPKG

relu-core

Version:
545 lines (526 loc) 14.6 kB
var base = require("./base"); var slice = Array.prototype.slice; var rpConst = require("./const"); var rpVariable = require("./variable"); var rpArrayOperation = require("./arrayOperation"); var rpDelegated = require("./delegated"); var rpProperty = require("./property"); var rpElement = require("./element"); var rpHandled = require("./handled"); var rpComputed = require("./computed"); var rpDoubleComputed = require("./doubleComputed"); var rpFilter = require("./filter"); var rpRestricted = require("./restricted"); var rpOrdered = require("./ordered"); var isRp = exports.isRp = base.isRp; exports.const = rpConst; exports.variable = rpVariable; exports.computed = rpComputed; exports.arrayOperation = rpArrayOperation; exports.delegated = function(x) { if(isRp(x)) return rpDelegated(x); else if(typeof x === "function") return rpDelegated(rpComputed(x)); else throw new Error("rp.delegated expect a RP or function as argument"); }; exports.capture = base.capture; exports.refAll = base.refAll; exports.unrefAll = base.unrefAll; exports.atomic = base.atomic; exports.afterAtomic = function(fn) { base.afterAtomic(fn); }; exports.atAtomicEnd = function(fn) { base.atAtomicEnd(fn); }; exports.globalScope = base.globalScope; exports.scope = base.scope; exports.undependend = base.undependend; exports.ever = function(fn) { return rpComputed(fn).scope(); }; var checkNotCapturing = base.checkNotCapturing; function toRp(x) { if(isRp(x)) return x; return rpConst(x); } base.prototype._element = function(name) { return rpElement(this, name); }; base.prototype._property = function(name) { return rpProperty(this, name); }; base.prototype._makeAttr = function(name, initial) { return rpVariable(initial); }; base.prototype.getHandled = function(changedHander, addedHandler, removedHandler) { if(this.isConst()) return this._getUndependend(); var p = rpHandled(this, changedHander, addedHandler, removedHandler); return p.scope()(); }; base.prototype.writable = function() { if(isRp(this._writable)) return this._writable.temporary(); return rpConst(!!this._writable); }; base.prototype.isWritable = function() { return this.writable()(); }; // infer other properties base.prototype.computed = function(fn, fnBack) { if(!fnBack) { var me = this; return rpComputed(function() { // propagate undefined source if(me.index === -1) return undefined; return fn(me()); }); } else { return rpDoubleComputed(this, fn, fnBack); } }; base.prototype.delegated = function(fn) { if(!fn) return rpDelegated(this); var me = this; return rpDelegated(rpComputed(function() { // propagate undefined source if(me.index === -1) return undefined; return fn(me()); })); }; base.prototype.decide = function(ifTrue, ifFalse) { var me = this; return rpComputed(function() { return me() ? ifTrue : ifFalse; }); }; base.prototype.inversed = function() { var me = this; return rpComputed(function() { return !me(); }); }; base.prototype.asBool = function() { var me = this; return rpComputed(function() { return !!me(); }); }; base.prototype.asNumber = function() { var me = this; return rpComputed(function() { return +me(); }); }; base.prototype.asFloatString = function() { return rpDoubleComputed(this, function(me) { return me + ""; }, function(me) { return parseFloat(me); }); }; base.prototype.asIntString = function() { return rpDoubleComputed(this, function(me) { return me + ""; }, function(me) { return parseInt(me, 10); }); }; base.prototype.asString = function() { var me = this; return rpComputed(function() { return me() + ""; }); }; base.prototype.rounded = function() { var me = this; return rpComputed(function() { return Math.round(me()); }); }; base.prototype.floored = function() { var me = this; return rpComputed(function() { return Math.floor(me()); }); }; base.prototype.ceiled = function() { var me = this; return rpComputed(function() { return Math.ceil(me()); }); }; base.prototype.plus = function(x) { var me = this; x = toRp(x); return rpComputed(function() { return me() + x(); }); }; base.prototype.minus = function(x) { var me = this; x = toRp(x); return rpComputed(function() { return me() - x(); }); }; base.prototype.dividedBy = function(x) { var me = this; x = toRp(x); return rpComputed(function() { return me() / x(); }); }; base.prototype.multipliedBy = function(x) { var me = this; x = toRp(x); return rpComputed(function() { return me() * x(); }); }; base.prototype.equals = function(x) { var me = this; x = toRp(x); return rpComputed(function() { return me() === x(); }); }; base.prototype.comparedTo = function(x, comparator) { var me = this; x = toRp(x); comparator = toRp(comparator || function(a, b) { if(a < b) return -1; if(a > b) return 1; return 0; }); return rpComputed(function() { return comparator()(me(), x()); }); }; base.prototype.split = function(splitter) { splitter = toRp(splitter); var me = this; return rpComputed(function() { return me().split(splitter()); }); }; base.prototype.stringified = function(a, b) { a = toRp(a); b = toRp(b); var me = this; return rpComputed(function() { return JSON.stringify(me(), a(), b()); }); }; base.prototype.parsed = function() { var me = this; return rpComputed(function() { return JSON.parse(me()); }); }; // infer array properties base.prototype.map = function(fn) { var me = this; return rpDelegated(rpArrayOperation(me, function(array) { this.set(array.map(function(item, idx) { return me(idx).computed(fn); })); }, function(idx, item) { this.splice(idx, 0, me(idx).computed(fn)); }, function(idx) { this.splice(idx, 1); })); }; base.prototype.mapRp = function(fn) { var me = this; return rpArrayOperation(me, function(array) { this.set(array.map(function(item, idx) { return fn(me(idx)); })); }, function(idx, item) { this.splice(idx, 0, fn(me(idx))); }, function(idx) { this.splice(idx, 1); }); }; base.prototype.filter = function(fn) { return rpFilter(this, fn); }; base.prototype.ordered = function(fn) { return rpOrdered(this, fn); }; base.prototype.reduce = function(fn, initialValue) { fn = toRp(fn); if(arguments.length === 1) { return this.computed(function(array) { if(!Array.isArray(array) || array.length === 0) return undefined; return array.reduce(fn()); }); } else { initialValue = toRp(initialValue); return this.computed(function(array) { if(!Array.isArray(array)) return undefined; return array.reduce(fn(), initialValue()); }); } }; base.prototype.slice = function(start, count) { start = toRp(start); count = toRp(count); // TODO forward array events // TODO convert start, count changes to array events return this.computed(function(array) { return array.slice(start(), count()); }); }; base.prototype.indexOf = function(item) { item = toRp(item); var me = this; return rpDelegated(rpComputed(function() { var i = item(); return rpArrayOperation(me, function(array) { this.set(array.indexOf(i)); }, function(idx, item) { if(this._getUndependend() >= idx) { if(i === item) { this.set(idx); } else { this.increment(); } } else if(this._getUndependend() === -1 && i === item) { this.set(idx); } }, function(idx) { var x = this._getUndependend(); if(x === idx) { this.set(me._getUndependend().indexOf(i)); } else if(idx < x) { this.decrement(); } }); })); }; base.prototype.size = function() { return rpArrayOperation(this, function(array) { this.set(array.length); }, function() { this.increment(); }, function() { this.decrement(); }); }; base.prototype.keys = function() { var initialValue = this._getUndependend(); var x = rpVariable(initialValue && typeof initialValue === "object" ? Object.keys(initialValue) : []); initialValue = undefined; this.onUpdated(onUpdated); this.onNested(onNested); this.ref(x); x.onceDisposed(function() { this.removeUpdatedListener(onUpdated); this.removeNestedListener(onNested); this.unref(x); }.bind(this)); return x; function onUpdated(newValue, oldValue) { x.set(newValue && typeof newValue === "object" ? Object.keys(newValue) : []); } function onNested(event, path, newValue, oldValue) { if(event !== "updated" || path.length !== 1) return; var property = path[0]; var removed = newValue === undefined; var added = oldValue === undefined; if(added && !removed) { x.push(property); } else if(removed && !added) { x.spliceBefore(x._getUndependend().indexOf(property), 1) } } }; base.prototype.flatten = function() { var lengths; var listeners; var arrays; var me = this; var arrayOp; var result = rpDelegated(arrayOp = rpArrayOperation(this, function(array) { if(arrays) { listeners.forEach(function(l, idx) { arrays[idx].removeAddedListener(l.added); arrays[idx].removeRemovedListener(l.removed); arrays[idx].removeUpdatedListener(l.updated); }); arrays.forEach(function(p) { p.unref(); }); } arrays = array.map(function(_, idx) { return me(idx).ref(); }); lengths = array.map(function(_) { return Array.isArray(_) ? _.length : 0; }); listeners = array.map(function(_, idx) { return bind(me(idx)); }, this); this.set(array.reduce(function(a, b, idx) { return a.concat(Array.isArray(b) ? b.map(function(_, idx2) { return me(idx, idx2); }) : []); }, [])); }, function(idx, newItem) { var pos = 0; for(var i = 0; i < idx; i++) pos += lengths[i]; lengths.splice(idx, 0, Array.isArray(newItem) ? newItem.length : 0); listeners.splice(idx, 0, bind(me(idx))); arrays.splice(idx, 0, me(idx).ref()); if(Array.isArray(newItem)) this.splice.apply(this, [pos, 0].concat(newItem.map(function(_, idx2) { return me(idx, idx2); }))); }, function(idx) { var pos = 0; for(var i = 0; i < idx; i++) pos += lengths[i]; this.splice(pos, lengths[idx]); lengths.splice(idx, 1); var l = listeners.splice(idx, 1); var p = arrays.splice(idx, 1)[0]; p.removeAddedListener(l.added); p.removeRemovedListener(l.removed); p.removeUpdatedListener(l.updated); p.unref(); })); result.onceDisposed(function() { listeners.forEach(function(l, idx) { arrays[idx].removeAddedListener(l.added); arrays[idx].removeRemovedListener(l.removed); arrays[idx].removeUpdatedListener(l.updated); }); arrays.forEach(function(p) { p.unref(); }); }); return result; function bind(p) { var l = {}; p.onAdded(l.added = function(idx2, newItem) { var idx = arrays.indexOf(p); var pos = 0; for(var i = 0; i < idx; i++) pos += lengths[i]; arrayOp.internal.splice(pos + idx2, 0, me(idx, idx2)); lengths[idx]++; }); p.onRemoved(l.removed = function(idx2) { var idx = arrays.indexOf(p); var pos = 0; for(var i = 0; i < idx; i++) pos += lengths[i]; arrayOp.internal.splice(pos + idx2, 1); lengths[idx]--; }); p.onUpdated(l.updated = function(newArray) { var idx = arrays.indexOf(p); var pos = 0; for(var i = 0; i < idx; i++) pos += lengths[i]; arrayOp.internal.splice.apply(arrayOp.internal, [pos, lengths[idx]].concat(Array.isArray(newArray) ? newArray.map(function(_, idx2) { return me(idx, idx2); }) : [])); lengths[idx] = Array.isArray(newArray) ? newArray.length : 0; }); return l; } }; // updating modifications base.prototype.increment = function(x) { checkNotCapturing(); if(x === undefined) x = 1; this.modify(function(a) { return a + x; }); }; base.prototype.decrement = function(x) { checkNotCapturing(); if(x === undefined) x = 1; this.modify(function(a) { return a - x; }); }; base.prototype.add = function(x) { checkNotCapturing(); this.modify(function(a) { return a + x; }); }; base.prototype.subtract = function(x) { checkNotCapturing(); this.modify(function(a) { return a - x; }); }; base.prototype.multiply = function(x) { checkNotCapturing(); this.modify(function(a) { return a * x; }); }; base.prototype.divide = function(x) { checkNotCapturing(); this.modify(function(a) { return a / x; }); }; base.prototype.invert = function() { checkNotCapturing(); this.modify(function(a) { return !a; }); }; // array modifications base.prototype.push = function(x) { this.splice(Infinity, 0, x); }; base.prototype.unshift = function(x) { this.splice(0, 0, x); }; base.prototype.shift = function() { this.splice(0, 1); }; base.prototype.pop = function() { this.splice(Infinity, 1); }; base.prototype.splice = function() { checkNotCapturing(); var args = slice.call(arguments); var p = this; base.atomic(function() { p._splice.apply(p, args); }); }; base.prototype.spliceBefore = function() { checkNotCapturing(); var args = slice.call(arguments); var p = this; base.atomic(function() { p._spliceBefore.apply(p, args); }); }; // helpers base.prototype.forEach = function(fn) { var array = this(); if(!Array.isArray(array)) throw new Error("Called .forEach on non-array"); array.map(function(item, idx) { return [this(idx), idx]; }, this).forEach(function(p) { fn(p[0], p[1]); }); }; base.prototype.readonly = function() { return rpRestricted(this); }; base.prototype.fixed = function() { return rpRestricted(this, false, true, true); }; base.prototype.fixedArray = function() { return rpRestricted(this, false, false, true); }; base.prototype.log = function(name) { console.log("logging " + name + " with " + (this.isConst() ? "const ": "") + "value ", this._getUndependend()); this.onChanged(function() { var v = this._getUndependend(); console.log(name + " was changed: ", isRp(v) ? "RP[" + v._getUndependend() + "]" : v); }.bind(this)); this.onUpdated(function(v) { console.log(name + " was updated to ", isRp(v) ? "RP[" + v._getUndependend() + "]" : v); }); this.onAdded(function(i, v) { console.log(name + " was added an item ", isRp(v) ? "RP[" + v._getUndependend() + "]" : v, " at ", i); }); this.onRemoved(function(i, v) { console.log(name + " was removed an item at ", i, " (" + this._getUndependend().length + " items remaining)"); }); return this; };