can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
250 lines (249 loc) • 8.85 kB
JavaScript
/*!
* CanJS - 2.3.34
* http://canjs.com/
* Copyright (c) 2018 Bitovi
* Mon, 30 Apr 2018 20:56:51 GMT
* Licensed MIT
*/
/*can@2.3.34#compute/get_value_and_bind*/
define(['can/util/library'], function (can) {
function ObservedInfo(func, context, compute) {
this.newObserved = {};
this.oldObserved = null;
this.func = func;
this.context = context;
this.compute = compute;
this.onDependencyChange = can.proxy(this.onDependencyChange, this);
this.depth = null;
this.childDepths = {};
this.ignore = 0;
this.inBatch = false;
this.ready = false;
compute.observedInfo = this;
this.setReady = can.proxy(this._setReady, this);
}
can.simpleExtend(ObservedInfo.prototype, {
getPrimaryDepth: function () {
return this.compute._primaryDepth;
},
_setReady: function () {
this.ready = true;
},
getDepth: function () {
if (this.depth !== null) {
return this.depth;
} else {
return this.depth = this._getDepth();
}
},
_getDepth: function () {
var max = 0, childDepths = this.childDepths;
for (var cid in childDepths) {
if (childDepths[cid] > max) {
max = childDepths[cid];
}
}
return max + 1;
},
addEdge: function (objEv) {
objEv.obj.bind(objEv.event, this.onDependencyChange);
if (objEv.obj.observedInfo) {
this.childDepths[objEv.obj._cid] = objEv.obj.observedInfo.getDepth();
this.depth = null;
}
},
removeEdge: function (objEv) {
objEv.obj.unbind(objEv.event, this.onDependencyChange);
if (objEv.obj.observedInfo) {
delete this.childDepths[objEv.obj._cid];
this.depth = null;
}
},
dependencyChange: function (ev) {
if (this.bound && this.ready) {
if (ev.batchNum !== undefined) {
if (ev.batchNum !== this.batchNum) {
ObservedInfo.registerUpdate(this);
this.batchNum = ev.batchNum;
}
} else {
this.updateCompute(ev.batchNum);
}
}
},
onDependencyChange: function (ev, newVal, oldVal) {
this.dependencyChange(ev, newVal, oldVal);
},
updateCompute: function (batchNum) {
if (this.bound) {
var oldValue = this.value;
this.getValueAndBind();
this.compute.updater(this.value, oldValue, batchNum);
}
},
getValueAndBind: function () {
this.bound = true;
this.oldObserved = this.newObserved || {};
this.ignore = 0;
this.newObserved = {};
this.ready = false;
observedInfoStack.push(this);
this.value = this.func.call(this.context);
observedInfoStack.pop();
this.updateBindings();
can.batch.afterPreviousEvents(this.setReady);
},
updateBindings: function () {
var newObserved = this.newObserved, oldObserved = this.oldObserved, name, obEv;
for (name in newObserved) {
obEv = newObserved[name];
if (!oldObserved[name]) {
this.addEdge(obEv);
} else {
oldObserved[name] = null;
}
}
for (name in oldObserved) {
obEv = oldObserved[name];
if (obEv) {
this.removeEdge(obEv);
}
}
},
teardown: function () {
this.bound = false;
for (var name in this.newObserved) {
var ob = this.newObserved[name];
this.removeEdge(ob);
}
this.newObserved = {};
}
});
var updateOrder = [], curPrimaryDepth = Infinity, maxPrimaryDepth = 0, currentBatchNum;
ObservedInfo.registerUpdate = function (observeInfo, batchNum) {
var depth = observeInfo.getDepth() - 1;
var primaryDepth = observeInfo.getPrimaryDepth();
curPrimaryDepth = Math.min(primaryDepth, curPrimaryDepth);
maxPrimaryDepth = Math.max(primaryDepth, maxPrimaryDepth);
var primary = updateOrder[primaryDepth] || (updateOrder[primaryDepth] = {
observeInfos: [],
current: Infinity,
max: 0
});
var objs = primary.observeInfos[depth] || (primary.observeInfos[depth] = []);
objs.push(observeInfo);
primary.current = Math.min(depth, primary.current);
primary.max = Math.max(depth, primary.max);
};
ObservedInfo.updateUntil = function (primaryDepth, depth) {
var cur;
while (true) {
if (curPrimaryDepth <= maxPrimaryDepth && curPrimaryDepth <= primaryDepth) {
var primary = updateOrder[curPrimaryDepth];
if (primary && primary.current <= primary.max) {
if (primary.current > depth) {
return;
}
var last = primary.observeInfos[primary.current];
if (last && (cur = last.pop())) {
cur.updateCompute(currentBatchNum);
} else {
primary.current++;
}
} else {
curPrimaryDepth++;
}
} else {
return;
}
}
};
ObservedInfo.batchEnd = function (batchNum) {
var cur;
currentBatchNum = batchNum;
while (true) {
if (curPrimaryDepth <= maxPrimaryDepth) {
var primary = updateOrder[curPrimaryDepth];
if (primary && primary.current <= primary.max) {
var last = primary.observeInfos[primary.current];
if (last && (cur = last.pop())) {
cur.updateCompute(batchNum);
} else {
primary.current++;
}
} else {
curPrimaryDepth++;
}
} else {
updateOrder = [];
curPrimaryDepth = Infinity;
maxPrimaryDepth = 0;
return;
}
}
};
var observedInfoStack = [];
can.__observe = function (obj, event) {
var top = observedInfoStack[observedInfoStack.length - 1];
if (top && !top.ignore) {
var evStr = event + '', name = obj._cid + '|' + evStr;
if (top.traps) {
top.traps.push({
obj: obj,
event: evStr,
name: name
});
} else if (!top.newObserved[name]) {
top.newObserved[name] = {
obj: obj,
event: evStr
};
}
}
};
can.__reading = can.__observe;
can.__trapObserves = function () {
if (observedInfoStack.length) {
var top = observedInfoStack[observedInfoStack.length - 1];
var traps = top.traps = [];
return function () {
top.traps = null;
return traps;
};
} else {
return function () {
return [];
};
}
};
can.__observes = function (observes) {
var top = observedInfoStack[observedInfoStack.length - 1];
if (top) {
for (var i = 0, len = observes.length; i < len; i++) {
var trap = observes[i], name = trap.name;
if (!top.newObserved[name]) {
top.newObserved[name] = trap;
}
}
}
};
can.__isRecordingObserves = function () {
var len = observedInfoStack.length, last = observedInfoStack[len - 1];
return len && last.ignore === 0 && last;
};
can.__notObserve = function (fn) {
return function () {
if (observedInfoStack.length) {
var top = observedInfoStack[observedInfoStack.length - 1];
top.ignore++;
var res = fn.apply(this, arguments);
top.ignore--;
return res;
} else {
return fn.apply(this, arguments);
}
};
};
can.batch._onDispatchedEvents = ObservedInfo.batchEnd;
return ObservedInfo;
});