I have an array of items coming back from the service. I'm trying to define a computed observable for every Item instance, so my instinct tells me to put it on the prototype.
One case for the computed observable: the system calculates points, but the user can choose to override the calculated value. I need to keep the calculated value available in case the user removes the override. I also need to coalesce user-assigned and calculated points, and add up the totals.
I'm using mapping to do the following:
var itemsViewModel;
var items = [
{ 'PointsCalculated' : 5.1 },
{ 'PointsCalculated' : 2.37, 'PointsFromUser' : 3 }
];
var mapping = {
'Items' : {
create : function(options) {
return new Item(options.data);
}
}
};
var Item = function(data) {
var item = this;
ko.mapping.fromJS(data, mapping, item);
};
Item.prototype.Points = function () {
var item = this;
return ko.computed(function () {
// PointsFromUser may be 0, so only ignore it if the value is undefined/null
return (item.PointsFromUser != null) ? item.PointsFromUser : item.PointsCalculated;
});
};
ko.mapping.fromJS(items, mapping, itemsViewModel);
The way it works now, I have to call the anonymous function to return the computed observable. That appears to create a new instance of the computed observable for each binding, which defeats most of the point of putting it on the prototype. And it's a little annoying having to decipher how many parentheses to use each time I access an observable.
It's also somewhat fragile. If I attempt to access Points() in code, I can't do
var points = 0;
var p = item.Points;
if (p && typeof p === 'function') {
points += p();
}
because that changes to context of Points() to DOMWindow, instead of item.
If I put the computed in create() in the mapping, I could capture the context, but then there's a copy of the method on each object instance.
I've found Michael Best's Google Groups post (http://groups.google.com/group/knockoutjs/browse_thread/thread/8de9013fb7635b13). The prototype returns a new computed observable on "activate". I haven't figured out what calls "activate" (maybe Objs?), but I'm guessing it still happens once per object, and I haven't a clue what scope 'this' will get.
At this point, I believe I'm past what's available in published docs, but I'm still working up to deciphering what's going on from the source.
Master KnockoutJS : Knockout JS - JavaScript MVVMComputed Observable is a function which is dependent on one or more Observables and automatically updates whenever its underlying Observables (dependencies) change.
A pure computed observable automatically switches between two states based on whether it has change subscribers. Whenever it has no change subscribers, it is sleeping. When entering the sleeping state, it disposes all subscriptions to its dependencies.
Knockout. js defines an important role when we want to detect and respond to changes on one object, we uses the observable. An observable is useful in various scenarios where we are displaying or editing multiple values and require repeated sections of the UI to appear and disappear as items are inserted and deleted.
To create an observable, assign the ko. observable function to the variable. A default value can be specified in the constructor of the call. Knockout then converts your variable into a function and tracks when the value changes, in order to notify the UI elements associated with the variable.
You mention that you don't want to have an instance of the ko.computed
function on each instance of your javascript class, however, that won't really work with how ko's functionality has been built. When you use ko.computed
or ko.observable
they create specific memory pointers to private variables inside that you would not normally want to be shared across class instances (although in rare cases you might).
I do something like this:
var Base = function(){
var extenders = [];
this.extend = function(extender){
extenders.push(extender);
};
this.init = function(){
var self = this; // capture the class that inherits off of the 'Base' class
ko.utils.arrayForEach(extenders, function(extender){
// call each extender with the correct context to ensure all
// inheriting classes have the same functionality added by the extender
extender.call( self );
});
};
};
var MyInheritedClass = function(){
// whatever functionality you need
this.init(); // make sure this gets called
};
// add the custom base class
MyInheritedClass.prototype = new Base();
then for the computed observables (which HAVE to be instance functions on each instance of your MyInheritedClass
) I just declare them in an extender
like so:
MyInheritedClass.prototype.extend(function(){
// custom functionality that i want for each class
this.something = ko.computed(function() {
return 'test';
});
});
Given your example and the Base
class defined above, you could easily do:
var Item = function(data) {
var item = this;
ko.mapping.fromJS(data, mapping, item);
this.init(); // make sure this gets called
};
Item.prototype = new Base();
Item.prototype.extend(function () {
var self = this;
this.Points = ko.computed(function () {
// PointsFromUser may be 0, so only ignore it if the value is undefined/null
return (self.PointsFromUser != null) ?
self.PointsFromUser : self.PointsCalculated;
});
};
Then all instances of your Item
class will have a Points
property, and it will correctly handle the ko.computed
logic per instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With