steal('can/util', 'can/util/bind', 'can/compute/read.js','can/compute/get_value_and_bind.js','can/util/batch', function (can, bind, read, ObservedInfo) {Allows the creation of observablue values. This is a prototype based version of can.compute.
can.Computes come in different flavors:
can.Computes have public .get, .set, .on, and .off methods that call
internal methods that are configured differently depending on what flavor of
compute is being created. Those methods are:
_on(updater) - Called the first time the compute is bound. This should bind to
any source observables. When any of the source observables have changed, it should call
updater(newVal, oldVal, batchNum).
_off(updater) - Called when the compute has no more event handlers. This should unbind to any source observables.
_get - Called to get the current value of the compute._set - Called to set the value of the compute.Other internal flags and values:
value - the cached value_setUpdates - if calling _set will have updated the cached value itself so _get does not need to be called._canObserve - if this compute can be observed.hasDependencies - if this compute has source observable values.steal('can/util', 'can/util/bind', 'can/compute/read.js','can/compute/get_value_and_bind.js','can/util/batch', function (can, bind, read, ObservedInfo) { can.Compute = function(getterSetter, context, eventName, bindOnce) {
can.cid(this, 'compute');
var args = [];
for(var i = 0, arglen = arguments.length; i < arglen; i++) {
args[i] = arguments[i];
}
var contextType = typeof args[1];
if (typeof args[0] === 'function') {Getter/Setter functional computes.
new can.Compute(function(){ ... })
this._setupGetterSetterFn(args[0], args[1], args[2], args[3]);
} else if (args[1]) {
if (contextType === 'string') {Property computes.
new can.Compute(object, propertyName[, eventName])
this._setupProperty(args[0], args[1], args[2]);
} else if(contextType === 'function') {Setter computes.
new can.Compute(initialValue, function(newValue){ ... })
this._setupSetter(args[0], args[1], args[2]);
} else {
if(args[1] && args[1].fn) {Async computes.
this._setupAsyncCompute(args[0], args[1]);
} else {Settings computes.
new can.Compute(initialValue, {on, off, get, set})
this._setupSettings(args[0], args[1]);
}
}
} else {Simple value computes.
new can.Compute(initialValue)
this._setupSimpleValue(args[0]);
}
this._args = args;
this._primaryDepth = 0;
this.isComputed = true;
};
can.simpleExtend(can.Compute.prototype, {
setPrimaryDepth: function(depth) {
this._primaryDepth = depth;
}, _setupGetterSetterFn: function(getterSetter, context, eventName) {
this._set = context ? can.proxy(getterSetter, context) : getterSetter;
this._get = context ? can.proxy(getterSetter, context) : getterSetter;
this._canObserve = eventName === false ? false : true;The helper provides the on and off methods that use getValueAndBind.
var handlers = setupComputeHandlers(this, getterSetter, context || this);
can.simpleExtend(this, handlers);
}, _setupProperty: function(target, propertyName, eventName) {
var isObserve = can.isMapLike( target ),
self = this,
handler;If a can.Map, setup to read and write to that property.
if(isObserve) {We should pass the batchNum if there is one.
handler = function(ev, newVal,oldVal) {
self.updater(newVal, oldVal, ev.batchNum);
};
this.hasDependencies = true;
this._get = function() {
return target.attr(propertyName);
};
this._set = function(val) {
target.attr(propertyName, val);
};
} else {This is objects that can be bound to with can.bind.
handler = function () {
self.updater(self._get(), self.value);
};
this._get = function() {
return can.getObject(propertyName, [target]);
};
this._set = function(value) {allow setting properties n levels deep, if separated with dot syntax
var properties = propertyName.split("."),
leafPropertyName = properties.pop(),
targetProperty = can.getObject(properties.join('.'), [target]);
targetProperty[leafPropertyName] = value;
};
}
this._on = function(update) {
can.bind.call(target, eventName || propertyName, handler);Set the cached value
this.value = this._get();
};
this._off = function() {
return can.unbind.call( target, eventName || propertyName, handler);
};
}, _setupSetter: function(initialValue, setter, eventName) {
this.value = initialValue;
this._set = setter;
can.simpleExtend(this, eventName);
},Use whatever on, off, get, set the users provided
as the internal methods.
_setupSettings: function(initialValue, settings) {
this.value = initialValue;
this._set = settings.set || this._set;
this._get = settings.get || this._get;This allows updater to be called without any arguments. selfUpdater flag can be set by things that want to call updater themselves.
if(!settings.__selfUpdater) {
var self = this,
oldUpdater = this.updater;
this.updater = function() {
oldUpdater.call(self, self._get(), self.value);
};
}
this._on = settings.on ? settings.on : this._on;
this._off = settings.off ? settings.off : this._off;
},This is a special, non-documented form of a compute rhat can asynchronously update its value.
_setupAsyncCompute: function(initialValue, settings){
var self = this;
this.value = initialValue;This compute will call update with the new value itself.
this._setUpdates = true;An “async” compute has a lastSetValue that represents
the last value compute.set was called with.
The following creates lastSetValue as a can.Compute so when
lastSetValue is changed, the getter can see that change
and automatically update itself.
this.lastSetValue = new can.Compute(initialValue);Wires up setting this compute to set lastSetValue.
If the new value matches the last setValue, do nothing.
this._set = function(newVal){
if(newVal === self.lastSetValue.get()) {
return this.value;
}
return self.lastSetValue.set(newVal);
};Wire up the get to pass the lastNewValue
this._get = function() {
return getter.call(settings.context, self.lastSetValue.get() );
};This is the async getter function. Depending on how many arguments the function takes, we setup bindings differently.
var getter = settings.fn,
bindings;
if(getter.length === 0) {If it takes no arguments, it should behave just like a Getter compute.
bindings = setupComputeHandlers(this, getter, settings.context);
} else if(getter.length === 1) {If it has a single argument, pass it the last setValue.
bindings = setupComputeHandlers(this, function() {
return getter.call(settings.context, self.lastSetValue.get() );
}, settings);
} else {If the function takes 2 arguments, the second argument is a function
that should update the value of the compute (setValue). To make this we need
the “normal” updater function because we are about to overwrite it.
var oldUpdater = this.updater,
setValue = function(newVal) {
oldUpdater.call(self, newVal, self.value);
};Because setupComputeHandlers calls updater internally with its
readInfo.value as oldValue and that might not be up to date,
we overwrite updater to always use self.value.
this.updater = function(newVal) {
oldUpdater.call(self, newVal, self.value);
};
bindings = setupComputeHandlers(this, function() {Call getter, and get new value
var res = getter.call(settings.context, self.lastSetValue.get(), setValue);If undefined is returned, don’t update the value.
return res !== undefined ? res : this.value;
}, this);
}
can.simpleExtend(this, bindings);
}, _setupSimpleValue: function(initialValue) {
this.value = initialValue;
},When a compute is first bound, call the internal this._on method.
can.__notObserve makes sure if _on is listening to any observables,
they will not be observed by any outer compute.
_bindsetup: can.__notObserve(function () {
this.bound = true;
this._on(this.updater);
}), _bindteardown: function () {
this._off(this.updater);
this.bound = false;
}, bind: can.bindAndSetup,
unbind: can.unbindAndTeardown,Copies this compute, but for a different context. This is mostly used for computes on a map’s prototype.
clone: function(context) {
if(context && typeof this._args[0] === 'function') {
this._args[1] = context;
} else if(context) {
this._args[2] = context;
}
return new can.Compute(this._args[0], this._args[1], this._args[2], this._args[3]);
}, _on: can.k,
_off: can.k, get: function() {
var recordingObservation = can.__isRecordingObserves();If an external compute is tracking observables and this compute can be listened to by “function” based computes ….
if(recordingObservation && this._canObserve !== false) {… tell the tracking compute to listen to change on this computed.
can.__observe(this, 'change');… if we are not bound, we should bind so that we don’t have to re-read to get the value of this compute.
if (!this.bound) {
can.Compute.temporarilyBind(this);
}
}If computed is bound, use the cached value.
if (this.bound) {
if(recordingObservation && this.getDepth && this.getDepth() >= recordingObservation.getDepth()) {
ObservedInfo.updateUntil(this.getPrimaryDepth(), this.getDepth());
}
return this.value;
} else {
return this._get();
}
}, _get: function() {
return this.value;
},Sets the value of the compute.
Depending on the type of the compute and what _set returns, it might need to call _get after
_set to get the final value.
set: function(newVal) {
var old = this.value;Setter may return the value if setter is for a value maintained exclusively by this compute.
var setVal = this._set(newVal, old);If the setter updated this.value, just return that.
if(this._setUpdates) {
return this.value;
}If the computed function has dependencies, we should call the getter.
if (this.hasDependencies) {
return this._get();
}Setting may not fire a change event, in which case the value must be read
if (setVal === undefined) {
this.value = this._get();
} else {
this.value = setVal;
}Fire the change
updateOnChange(this, this.value, old);
return this.value;
}, _set: function(newVal) {
return this.value = newVal;
}, updater: function(newVal, oldVal, batchNum) {
this.value = newVal;
updateOnChange(this, newVal, oldVal, batchNum);
}, toFunction: function() {
return can.proxy(this._computeFn, this);
},
_computeFn: function(newVal) {
if(arguments.length) {
return this.set(newVal);
}
return this.get();
}
}); var updateOnChange = function(compute, newValue, oldValue, batchNum){
var valueChanged = newValue !== oldValue && !(newValue !== newValue && oldValue !== oldValue);Only trigger event when value has changed
if (valueChanged) {
can.batch.trigger(compute, {type: "change", batchNum: batchNum}, [
newValue,
oldValue
]);
}
};A helper that creates an _on and _off function that
will bind on source observables and update the value of the compute.
var setupComputeHandlers = function(compute, func, context) {The last observeInfo object returned by getValueAndBind.
var readInfo = new ObservedInfo(func, context, compute);
return {
readInfo: readInfo,Call onchanged when any source observables change.
_on: function() {
readInfo.getValueAndBind();
compute.value = readInfo.value;
compute.hasDependencies = !can.isEmptyObject(readInfo.newObserved);
},Unbind onchanged from all source observables.
_off: function() {
readInfo.teardown();
},
getDepth: function() {
return readInfo.getDepth();
},
getPrimaryDepth: function() {
return readInfo.getPrimaryDepth();
}
};
}; can.Compute.temporarilyBind = function (compute) {
var computeInstance = compute.computeInstance || compute;
computeInstance.bind('change', can.k);
if (!computes) {
computes = [];
setTimeout(unbindComputes, 10);
}
computes.push(computeInstance);
};A list of temporarily bound computes
var computes,Unbinds all temporarily bound computes.
unbindComputes = function () {
for (var i = 0, len = computes.length; i < len; i++) {
computes[i].unbind('change', can.k);
}
computes = null;
}; can.Compute.async = function(initialValue, asyncComputer, context){
return new can.Compute(initialValue, {
fn: asyncComputer,
context: context
});
};Wraps a compute with another compute that only changes when
the wrapped compute’s truthiness changes.
can.Compute.truthy = function(compute) {
return new can.Compute(function() {
var res = compute.get();
if(typeof res === 'function') {
res = res.get();
}
return !!res;
});
}; can.Compute.read = read;
can.Compute.set = read.write;
return can.Compute;
});