can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
284 lines (283 loc) • 10.8 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/proto_compute*/
define([
'can/util/library',
'can/util/bind',
'can/read',
'can/get_value_and_bind',
'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') {
this._setupGetterSetterFn(args[0], args[1], args[2], args[3]);
} else if (args[1]) {
if (contextType === 'string') {
this._setupProperty(args[0], args[1], args[2]);
} else if (contextType === 'function') {
this._setupSetter(args[0], args[1], args[2]);
} else {
if (args[1] && args[1].fn) {
this._setupAsyncCompute(args[0], args[1]);
} else {
this._setupSettings(args[0], args[1]);
}
}
} else {
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;
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 (isObserve) {
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 {
handler = function () {
self.updater(self._get(), self.value);
};
this._get = function () {
return can.getObject(propertyName, [target]);
};
this._set = function (value) {
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);
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);
},
_setupSettings: function (initialValue, settings) {
this.value = initialValue;
this._set = settings.set || this._set;
this._get = settings.get || this._get;
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;
},
_setupAsyncCompute: function (initialValue, settings) {
var self = this;
this.value = initialValue;
this._setUpdates = true;
this.lastSetValue = new can.Compute(initialValue);
this._set = function (newVal) {
if (newVal === self.lastSetValue.get()) {
return this.value;
}
return self.lastSetValue.set(newVal);
};
this._get = function () {
return getter.call(settings.context, self.lastSetValue.get());
};
var getter = settings.fn, bindings;
if (getter.length === 0) {
bindings = setupComputeHandlers(this, getter, settings.context);
} else if (getter.length === 1) {
bindings = setupComputeHandlers(this, function () {
return getter.call(settings.context, self.lastSetValue.get());
}, settings);
} else {
var oldUpdater = this.updater, setValue = function (newVal) {
oldUpdater.call(self, newVal, self.value);
};
this.updater = function (newVal) {
oldUpdater.call(self, newVal, self.value);
};
bindings = setupComputeHandlers(this, function () {
var res = getter.call(settings.context, self.lastSetValue.get(), setValue);
return res !== undefined ? res : this.value;
}, this);
}
can.simpleExtend(this, bindings);
},
_setupSimpleValue: function (initialValue) {
this.value = initialValue;
},
_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,
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 (recordingObservation && this._canObserve !== false) {
can.__observe(this, 'change');
if (!this.bound) {
can.Compute.temporarilyBind(this);
}
}
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;
},
set: function (newVal) {
var old = this.value;
var setVal = this._set(newVal, old);
if (this._setUpdates) {
return this.value;
}
if (this.hasDependencies) {
return this._get();
}
if (setVal === undefined) {
this.value = this._get();
} else {
this.value = setVal;
}
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);
if (valueChanged) {
can.batch.trigger(compute, {
type: 'change',
batchNum: batchNum
}, [
newValue,
oldValue
]);
}
};
var setupComputeHandlers = function (compute, func, context) {
var readInfo = new ObservedInfo(func, context, compute);
return {
readInfo: readInfo,
_on: function () {
readInfo.getValueAndBind();
compute.value = readInfo.value;
compute.hasDependencies = !can.isEmptyObject(readInfo.newObserved);
},
_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);
};
var 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
});
};
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;
});