Jason Maggard
Jason Maggard

Reputation: 1672

knockout - reusing the same view model

Let's take an example of a shopping cart...

Each time a user selects an item we move it to a line of our cart.

/* would normally autoincrement */ 
var num = '1'; 
document.getElementById('cart').innerHTML = oldHTML + 
"<div id='line" + num + "'>
<input type='text' data-bind='value: quantity'>
<input type='text' data-bind='value: price'>
<input type='text' data-bind='value: extendedPrice'>
</div>";`

var lineVM () {
    var self = this;
    self.quantity = ko.observable();
    self.price = ko.observable();
    self.extendedPrice = ko.computed(function () {return self.price() * self.quantity();});
}

ko.applyBindings(new lineVM(), document.getElementById('line' + num));

As lines get added, we have line1, line2, etc...

From the docs:

Optionally, you can pass a second parameter to define which part of the document you want to search for data-bind attributes. For example, ko.applyBindings(myViewModel, document.getElementById('someElementId')). This restricts the activation to the element with ID someElementId and its descendants, which is useful if you want to have multiple view models and associate each with a different region of the page.

Testing shows that there is no separation... If I reuse the view model, then adding another line "blanks" the one before it. However, I need the same view model for an indeterminate number of lines. I don't want to create 100 view models for every potential line, and I don't want my cart to break for the customer that is going to have 101 items.

So, what if I have a single view model and want to associate it with multiple regions on the page? The docs don't really say. Am I doing it the wrong way, am I trying to do something Knockout doesn't allow for?

Thank you very much for your help.

Upvotes: 1

Views: 961

Answers (1)

David Sherret
David Sherret

Reputation: 106630

I think you should rethink your design as a whole. Why not do something like this?

var viewModel = function () {
    var self = this;
    self.items = ko.observableArray();
    self.addItem = function(num, quantity, price) {
        var item = {
            num: num,
            quantity: ko.observable(quantity),
            price: ko.observable(price)
        };
        item.extendedPrice = ko.computed(function() {
            return item.price() * item.quantity();
        }, self);
        self.items.push(item);
    };
};

ko.applyBindings(new viewModel());

Html:

<div data-bind="foreach: items">
    <div data-bind="attr: { id: 'line' + num }">
        <input type="text" data-bind="value: quantity" />
        <input type="text" data-bind="value: price" />
        <input type="text" data-bind="value: extendedPrice" readonly="readonly" />
    </div>
</div>

The principle is to add items to the observableArray to change the view. You really shouldn't be dynamically creating view models.

With this code, you have to have some way of calling self.addItem and that depends on the rest of how you designed your code.

Edit: by the way, the line var lineVM () { in your code should be var lineVM = function () {

Also, sorry if someone of this code doesn't work, it's untested, but should help you get the idea.

Upvotes: 2

Related Questions