Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are computed observables on prototypes feasible in KnockoutJS?

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.

like image 207
Chris Hance Avatar asked Apr 24 '12 14:04

Chris Hance


People also ask

Which function used to perform computation in knockout JS?

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.

What is pure computed in knockout JS?

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.

What is the use of observable in knockout JS?

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.

How do you assign a value to observable Knockout?

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.


1 Answers

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.

like image 133
ericb Avatar answered Oct 20 '22 14:10

ericb