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?
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.