Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for modifying knockout observable on parent from child view model

I have a parent-child view model object structure set up and need to update an observable on the parent from the child. I've basically come up with two patterns for doing so:

1] Pass a reference of the parent property to the child and update the property from within the child:

var ParentViewModel = function(){
    var self = this;
    this.selectedItem = ko.observable();
    this.child = ko.observable(new ChildViewModel(self.selectedItem));
}

var ChildViewModel = function(parentSelectedItem){
    var self = this;
    this.id = ko.observable();
    this.parentSelectedItem = parentSelectedItem;
    this.select = function(){
        self.parentSelectedItem(self);
    }
}

2] Create the child's select method on the parent and reference the parent observable locally:

var ParentViewModel = function(){
    var self = this;
    this.selectedItem = ko.observable();

    var child = new ChildViewModel();
    child.select = function(){
        self.selectedItem(child);
    }
    this.child = ko.observable(child);
}

var ChildViewModel = function(){
    this.id = ko.observable();
}

Neither of these patterns send me head over heels. The first one pushes the entire property reference into children view models, the second defines a child's function outside of the scope of the child.

Does anyone have any other pattern suggestions as to how this operation could be achieved in javascript in a clean and testable manner? Or am I more or less stuck with just these two options?

like image 639
KodeKreachor Avatar asked Apr 04 '12 04:04

KodeKreachor


1 Answers

The most common pattern to do this in Knockout is to put a "selectChild" method on your parent that takes in a child. In most cases, the actual child does not need to know that it is being selected.

Then in your binding, you can bind to $root.selectChild or $parent.selectChild. The first argument passed to a handler bound to the click/event binding is the actual data (in KO 2.0), so your method can live on the parent and receive the child as the first arg.

var Item = function(id, name) {
    this.id = id;
    this.name = ko.observable(name);    
};

var ViewModel = function() {
    var self = this;
    this.items = ko.observableArray([
        new Item(1, "one"),
        new Item(2, "two"),
        new Item(3, "three")
    ]);  

    this.selectedItem = ko.observable();

    this.selectItem = function(item) {
        self.selectedItem(item);
    };     
};

In this case, your binding would look like:

<ul data-bind="foreach: items">
    <li>
        <a href="#" data-bind="text: name, click: $root.selectItem"></a>
    </li>
</ul>

Here it is in jsFiddle: http://jsfiddle.net/rniemeyer/anRsA/

You can even simplify it further. Observables are functions and the first argument that you pass to them is used to set their value, so you can even choose to not include the selectItem method and simply bind against $root.selectedItem directly (would look like: http://jsfiddle.net/rniemeyer/anRsA/1/). I usually use a separate method to be explicit, to give it a proper name (action), and in case there is extra processing that needs to be done before or after setting the item.

Prior to KO 2.0 (where $root and $parent were introduced along with the change to pass the data as the first arg to click and event handlers), I used to use the first method that you suggested quite a bit. One thing that you can do there is actually not create the child property (this.parentSelectedItem) and just reference parentSelectedItem (that was passed as an argument) directly in the select method, as it will be available in the function because of the closure that is created.

like image 199
RP Niemeyer Avatar answered Oct 20 '22 12:10

RP Niemeyer