Jedediah
Jedediah

Reputation: 1944

Why is the context of this different in these two functions

I have the following code:

function ViewModel() {
    var self = this;

    self.newCategory = {
        name: ko.observable(''),
        canSubmit: function() {
            return this.name() !== '';
        },
        submit: function() {
            var data = 'name=' + this.name();
            $.post('/api/createcategory', data, function(res) {
                //...
            });
        }
    };
}

And the HTML

<button type="button" data-bind="enable: newCategory.canSubmit(), click: newCategory.submit">Create category</button>

In the canSubmit() function, this refers to self.newCategory as I'm expecting. However, for some reason in the submit() function, this refers to ViewModel() Why is this?

Upvotes: 0

Views: 49

Answers (1)

Tomalak
Tomalak

Reputation: 338228

Knockout calls functions in the context of the viewmodel that they are bound to, in your case the main viewmodel. Hint: The viewmodel for submit is not the object that you assigned to newCategory.

In your binding you do two different things:

  • enable: newCategory.canSubmit() calls the function right-away, on newCateory. So that's what this is going to be inside the function.
  • click: newCategory.submit mentions the function, i.e. knockout creates a click handler that will run in the context of the current viewmodel.

Note that canSubmit actually ought to be observable.

When you change the binding context, for example using the with binding, the behavior is like you would expect it.

<!-- ko with: newCategory -->
<button data-bind="enable: canSubmit, click: submit">Create category</button>
<!-- /ko -->

For sub viewmodels I like to create actual stand-alone constructors:

function Category() {
    var self = this;

    self.busy = ko.observable(false);
    self.name = ko.observable('');
    self.canSubmit = ko.computed(function() {
        return !self.busy() && self.name() > '';
    });
}
Category.prototype.submit = function() {
    var self = this;

    if ( !self.canSubmit() ) return;

    self.busy(true);
    $.post('/api/createcategory', {
        name: this.name()
    }).done(function(res) {
        //...
    }).always(function() {
        self.busy(false);
    });
};

function ViewModel() {
    var self = this;

    self.newCategory = new Category();
}

and

<!-- ko with: newCategory -->
<input type="text" data-bind="name">
<button data-bind="enable: canSubmit, click: submit">Create category</button>
<img src="spinner.gif" data-bind="visible: busy">
<!-- /ko -->

Upvotes: 2

Related Questions