Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout nested view model

I'm stuck with what must be a simple fix. I am using knockout.js with nested view models, and all seems fine except that my remove function is not working correctly. It appears to be binding correctly, however it is not fired when I click remove.

Why nested view models? Long story, but essentially a lot of stuff is required to be on one page!

So here is the code:

HTML

<section class="mini-form-container">
    <form data-bind="submit: repeatGuest.addDate">
        <input type="date" data-bind="value: repeatGuest.previousStay"/>
        <button type="submit" class="button-secondary ">Add date</button>
    </form>
    <div data-bind="foreach: repeatGuest.dates, visible: repeatGuest.dates().length > 0">
        <div>
            <input data-bind="value: date" disabled="disabled"  />
            <a data-bind="click: $parent.removeDate">Remove</a>
        </div>
    </div>
</section>

<section>
    <div data-bind="text: ko.toJSON($data)"></div>
</section>

Javascript

function RepeatGuest() {
    /// <summary>Child View Model</summary>
    this.dates = ko.observableArray();
    this.previousStay = ko.observable();
}

RepeatGuest.prototype.addDate = function () {
        var self = this.repeatGuest;
        if (self.previousStay()) {
            self.dates.push({
                date: self.previousStay()
            });
        }
    };

RepeatGuest.prototype.removeDate = function (date) {
    this.dates.remove(date);
}

function ViewModel() {
    var self = this;
    self.repeatGuest = new RepeatGuest();
}
ko.applyBindings(new ViewModel());

And here is my fiddle: http://jsfiddle.net/6Px4M/2/

So why isn't my remove function getting fired?

Possible side question: is nested view models the wrong path to take in knockout, there doesn't seem to much info on this?

like image 835
marvc1 Avatar asked Mar 01 '13 17:03

marvc1


2 Answers

One of the best ways to work with a nested model like this is to use the with binding. You can do:

<div data-bind="with: repeatGuest">
   ...
</div>

Now, the scope is your repeatGuest and you can bind directly against its properties.

The issue with your remove function is related to the value of this and who $parent is at that time. Functions are executed with a value of this that is equal to the current scope. When your remove function is bound, the scope is one of the objects in the date array.

The typically way to handle this is to make sure that your function is bound to always use the correct value of this. This could be done in the binding (very ugly) like:

<a data-bind="click: $parent.repeatGuest.removeDate.bind($parent.repeatGuest)">Remove</a>

A better option is to bind it in the view model, in your RepeatGuest constructor:

this.removeDate = this.removeDate.bind(this);

This allows the implementation to live on the prototype and overwrites it on each instance with a wrapper that forces the correct value of this. Alternatively, if you do not put it on the prototype, then you can use the var self = this; pattern and use self in the handler.

http://jsfiddle.net/cNdJj/

like image 76
RP Niemeyer Avatar answered Oct 02 '22 02:10

RP Niemeyer


The binding won't access the function on the wrong prototype. You're binding to the viewModel, not to the RepeatGuest object right now.

It works if you set it as a local function:

http://jsfiddle.net/6Px4M/3/

function ViewModel() {
    var self = this;
    self.repeatGuest = new RepeatGuest();
    self.removeDate = function (date) {
        self.repeatguest.dates.remove(date);
    }
}
like image 42
Ben McCormick Avatar answered Oct 02 '22 02:10

Ben McCormick