Are computed observables on prototypes feasible in KnockoutJS?
knockout-mapping-plugin, knockout.js
Solution
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.
Problem
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.