shenku
shenku

Reputation: 12420

Unable to get correct knockout binding context

I have the following javascript that does my knockout binding.

var context = this;

var viewModel = {
    lineitems: [{
        quantity: ko.observable(1),
        title: 'bar',
        description: 'foo',
        price: ko.observable(10),
        total: ko.observable(10),
        formattedTotal: ko.computed({
        read: function () { 
            return '$' + this.price().toFixed(2);
        },
        write: function (value) { 
            value = parseFloat(value.replace(/[^\.\d]/g, ""));
            this.price(isNaN(value) ? 0 : value);
        } 
       })
    }]
};

ko.applyBindings(viewModel);

Which binds as expected, however when I apply the formattedTotal, I get the following javascript error.

Uncaught TypeError: Object [object global] has no method 'price'

I've tried a few changes to the syntax, but I can't seem to get it right, where am I going wrong?

Upvotes: 1

Views: 289

Answers (2)

janfoeh
janfoeh

Reputation: 10328

The problem is inside your formattedTotal method: the scope - this - is not your viewModel. Try this:

var viewModel = {
    lineitems: [{
        quantity: ko.observable(1),
        title: 'bar',
        description: 'foo',
        price: ko.observable(10),
        total: ko.observable(10),
        formattedTotal: ko.computed({
        read: function () { 
            return '$' + viewModel.lineitems.price().toFixed(2);
        },
        write: function (value) { 
            value = parseFloat(value.replace(/[^\.\d]/g, ""));
            viewModel.lineitems.price(isNaN(value) ? 0 : value);
        } 
       })
    }]
};

Consider using a constructor function for your viewmodels instead of an object literal; this makes dealing with scope issues easier and cleaner. See this answer for an example.

Upvotes: 1

Grzegorz W
Grzegorz W

Reputation: 3517

Usually it is not a best idea to use this in JavaScript. Especially with Knockout. You never know what this will be during execution.

So I recommend writing it this way:

function LineItem(_quantity, _title, _description, _price) {
    var self = this;

    self.quantity = ko.observable(_quantity);
    self.title = ko.observable(_title);
    self.description = ko.observable(_description);
    self.price = ko.observable(_price);

    self.total = ko.computed(function () {
        return self.price() * self.quantity();
    }, self);

    self.formattedTotal = ko.computed(function () {
        return '$' + self.total().toFixed(2);
    }, self);
};

var viewModel = {
    lineItems: [
    new LineItem(10, 'Some Item', 'Some description', 200),
    new LineItem(5, 'Something else', 'Some other desc', 100)
]
};

ko.applyBindings(viewModel);

You can read some discussion about self=this pattern here.

Upvotes: 1

Related Questions