relu-core
Version:
545 lines (526 loc) • 14.6 kB
JavaScript
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;
};