Search code examples
knockout.jsprototypejsobservable

"variable is not a function" error while reading knockout variable


I am using prototypejs and knockout in my web project.

First of all let me explain the overall structure.

I have a base class named as userViewModel and an observable variable named accNumber is declared in this class definition.

 userViewModel = Class.create(baseViewModel , {
        accNumber: ko.observable("")
});

There is another class which is derived from my base class and a computed variable named accNumberComputed is declared in this derived class.

 femaleUserViewModel  = Class.create(userViewModel , {
        accNumberComputed : ko.pureComputed({
                read: function () {
                   return this.accNumber();     
                },
                write: function (value) {
                    this.accNumber(value);
                },
                owner: this
        })
});

I want to update accNumberComputed variable depends on the accNumber observable variable. So that any modification on the accNumber variable will be tracked on the accNumberComputed variable.

But any this.accNumber() statement usage returns a "TypeError: this.accNumber is not a function" error message. As far as i know an observable variable must be read by using a function call operator.

Could you please enlighten me about this problem.


Solution

  • If you dig a little deeper, you'll find out that in your example this.accNumber is not only not a function, it's actually undefined.

    The this in your computed's read method refers to window.

    I'm not familiar with the prototypejs library, but there's probably a way to get it to bind to the instance correctly.

    I had a quick glance at the documentation, and did see a way that resembles how it'd work in a "plain javascript approach". It works like this:

    1. Don't add the observable and computed to the prototype, but create them in the constructor. (initialize in prototypejs)

    This:

     var userViewModel = Class.create(baseViewModel , {
       accNumber: ko.observable("")
     });
    

    becomes:

    var userViewModel = Class.create(baseViewModel, {
      initialize: function() {
        this.accNumber = ko.observable("");
      }
    });
    
    1. Explicitly call the base class' constructor in the extended class' constructor.

    Not sure if it's the best way of doing it, but can be done like:

    var Extended = Class.create(Base, {
      initialize: function() {
        Extended.superclass.prototype.initialize.call(this);
    

    In your example, this is what you'll get:

    var baseViewModel = Class.create({
      initialize: function() {
        this.base = "base";
      }
    });
    
    var userViewModel = Class.create(baseViewModel, {
      initialize: function() {
        this.accNumber = ko.observable("1");
      }
    });
    
    var femaleUserViewModel = Class.create(userViewModel, {
      initialize: function() {
        femaleUserViewModel.superclass.prototype.initialize.call(this);
        
        this.accNumberComputed = ko.pureComputed({
          read: function() {
            return this.accNumber();
          },
          write: function(value) {
            this.accNumber(value);
          },
          owner: this
        })
      }
    });
    
    var janeDoe = new femaleUserViewModel();
    console.log(janeDoe.accNumberComputed());
    janeDoe.accNumberComputed(2);
    console.log(janeDoe.accNumberComputed());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.3/prototype.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>