0
votes

After reading this article: A fresh look at javascript mixins I wanted to move some common knockout computed functions to a mixin and mix that into each viewmodel that needs the functionality.

First the mixin function

var asActor = function() {
    this.nrOfEmployees = ko.computed(function () {
        if (this.employees())
            return this.employees().length;
        return 0;
    }, this).extend({ throttle: 20 });
    this.nrOfAddresses = ko.computed(function () {
        if (this.addresses())
            return this.addresses().length;
        return 0;
    }, this).extend({ throttle: 20 })
};

Then the call to mix the mixin into the target objects prototype

asActor.call(SenderActor.prototype);
asActor.call(ReceiverActor.prototype);

My problem seems to be that this (or self which is set to this inside the viewmodel) points to the windows object and not the viewmodel. Any ideas? Javascript mixins is new to me so I'm probably missing something obvious here.

3

3 Answers

2
votes

asActor.call(SenderActor.prototype); does the following -

  1. it calls asActor and passes SenderActor.protototype object as this
  2. this.nrOfEmployees = ko.computed(...) creates a ko.computed inside SenderActor.protototype
  3. it evaluates the computed in same context this still points to SenderActor.protototype

Depending on whether you do or do not have employees in your SenderActor.protototype or it's chain of prototypes this code will or will not produce errors.

Now I am assuming this behaviour is not what you were aming at.

Try running asActor.call(this); inside SenderActor constructor.

1
votes

When you create new object by a function, say new asActor(), there will be a default value to return for you - the magic "this". You must add a return statement when you just run a function.

I've tested your code, it has no problems but there no return value from running asActor.

var asActor = function() {
  this.nrOfEmployees = ko.computed(function () {
      if (this.employees())
        return this.employees().length;
    return 0;
  }, this).extend({ throttle: 20 });
  this.nrOfAddresses = ko.computed(function () {
    if (this.addresses())
        return this.addresses().length;
    return 0;
  }, this).extend({ throttle: 20 })

  return this;
};
1
votes

As Olga said, creating a computed on the prototype itself was not really my intention. The solution I chose was to use a Base class:

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);
        });
    };
};

In the generation code I added this.Init() call in all viewmodel constructors.

Finally I called the .extend function on each viewmodel to have these functions mixed in:

SenderActor.prototype.extend(asActor);
ReceiverActor.prototype.extend(asActor);