19

I have been using Knockout.js for a lot of projects lately, and I am writing a lot of repetitive code. I would like to be able to define a BaseViewModel class and have my page-specific ViewModels inherit from it. I am a bit confused about how to do this is Javascript. Here is my basic BaseViewModel:

(function (ko, undefined) {
    ko.BaseViewModel = function () {
        var self = this;
        self.items = ko.observable([]);
        self.newItem = {};
        self.dirtyItems = ko.computed(function () {
            return self.items().filter(function (item) {
                return item.dirtyFlag.isDirty();
            });
        });
        self.isDirty = ko.computed(function () {
            return self.dirtyItems().length > 0;
        });
        self.load = function () { }
    };
}(ko));

I would like to be able to list signatures for methods like load in the BaseViewModel and then give them definitions in the inheriting ViewModel. Is any of this possible? I have found a few solutions online but they all rely on defining functions/classes to make the inheritance work.

wholol
  • 918
  • 2
  • 9
  • 22
  • why are you passing `undefined` into the function? – Kyeotic May 15 '13 at 16:03
  • http://programmers.stackexchange.com/a/158376/90240 – wholol May 15 '13 at 16:07
  • Huh, never seen that before. I've also never run into the case where not doing that causes a bug, and he doesn't explain what that case is. – Kyeotic May 15 '13 at 16:11
  • 4
    It is a defensive technique if you are writing code to be consumed by others. It is possible to re-define the `undefined` symbol. Declaring a function parameter `undefined` and then not passing any value for that argument is a technique to capture the actual `undefined` value even if someone has redefined the symbol. As it happens, it also lets your JS minimizer minimize your usages of `undefined` since it is now just a local variable. – Brandon May 15 '13 at 16:16
  • 1
    http://stackoverflow.com/questions/8783510/javascript-how-dangerous-is-it-really-to-assume-undefined-is-not-overwritten – Brandon May 15 '13 at 16:16
  • I didn't look at the other stackoverflow posts but the case of `undefined` not being `undefined` is an ES3 phenomenon that is not an issue anymore...but a lot of the libraries out there had to worry about it when they started, so they pass it in, and it became sort of a tradition, I suppose. – sunny-mittal Oct 09 '15 at 04:16

2 Answers2

18

Since your BaseViewModel is just adding all of the properties/methods to this (and not using prototype) then it is pretty easy:

In your new view models, just call BaseViewModel:

var MyVM = function () {
    var self = this;
    ko.BaseViewModel.call(self);

    self.somethingElse = ko.observable();
    self.itemCount = ko.computed(function() { return self.items().length; });
    self.items([1, 2, 3]); 
};


// ...
var vm = new MyVM();
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • Well that is way easier than I was expecting. Now I just need to figure out how that works! – wholol May 15 '13 at 17:12
  • The `ko.BaseViewModel.call(self)` line is the same as doing `:base()` in c#, right? – wholol May 15 '13 at 17:16
  • 1
    @theyshookhands Yes, it is the same as a `:base()` call. The `ViewModel.prototype = new BaseViewModel()` is the same as `Class : SubClass` in c#. – Kyeotic May 15 '13 at 17:56
  • 1
    @theyshookhands - read about [Function.call](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call). It allows you to call any function and supply the `this` value to use. In this example, `BaseViewModel` is just being used as a helper function to initialize your viewmodel. We are not *really* doing inheritance. Things gets slightly more interesting when you start using prototype, which is JavaScript's mechanism for doing inheritance. – Brandon May 15 '13 at 20:38
  • What is the difference in doing `ko.BaseViewModel.call(self)` vs `ko.utils.extend(self, new BaseViewModel(item))`, as seen here http://stackoverflow.com/a/14968058/251614 – Homer Jan 30 '14 at 22:03
  • 2
    @Homer The `extend` technique allocates a new object (BaseViewModel), then copies the properties over to your subclass object and throws away the `BaseViewModel` object. So a wasted allocation. But more importantly, if `BaseViewModel` defines some functions or computeds that reference `self`, they will be referring to the thrown away object instead of your subclass instance, so the `extend` technique can actually lead to some subtle and hard to identify bugs. – Brandon Jan 31 '14 at 14:31
10

Javascript inheritance is done in two pieces. The first is in the constructor, and the second is on the prototype (which you aren't using, so you could skip).

var ViewModel = function(data) {
    BaseViewModel.call(this);
};
//you only need to do this if you are adding prototype properties
ViewModel.prototype = new BaseViewModel();

To your last point, about overriding load, its no different that putting a load function on your viewmodel normally. Javascript allows you to override any objects properties with anything, there are no special steps here.

Here is a fiddle demonstrating the inheritance.

Kyeotic
  • 19,697
  • 10
  • 71
  • 128
  • If you only need to worry about supporting modern browsers, you can replace that last line with `Viewmodel.prototype = Object.create(BaseViewModel.prototype);` and avoid some thorny issues with any undesired effects of calling `BaseViewModel` in this way. – Brandon May 15 '13 at 20:41
  • And it's easy to shim `Object.create` in older browsers: `var objCreate = Object.create || function(o) { function F(){}; F.prototype = o; return new F(); };` – Michael Best Aug 08 '13 at 02:15