Reputation: 9009
I have a composite view model, with other view models inside, that introduce nested bindings. It is thus possible one nested sub-model to alter another sub-model's observable, or one owned by its parent.
An example is available on the following JsFiddle
There, I am modifying an observable object (itemContainer
) that itself exposes an observable array property (items
). I want to be able to change the bindings each time I modify the items
property, or the itemContainer
itself. However, both seem not to have any effect on the dom, while in reality the bindings occur as expected - I am modifying a span with jQuery to assert the actual count of elements after my operations (see "Actual count" value).
So, I have made another JsFiddle with the main difference being that itemContainer
is no longer an observable property. This time it works almost as I expect to.
However, I need to be able to have the dom automatically update whenever I alter either the itemContainer
or its items
property. How should I go about this?
Also, in both cases, the viewModel.actions.count
computed member's binding is not re-evaluated. I'd appreciate any insights on why it does not pick the changes.
Upvotes: 1
Views: 673
Reputation: 12295
Updated working fiddle here: http://jsfiddle.net/hh233r3q/1/
The problem was in your template. You bind contents.itemContainer.items.
<!--ko foreach: contents.itemContainer.items -->
However, itemContainer is initialized to null, and items is not a property of null.
itemContainer: ko.observable(null)
Instead, you can use the ko with binding.
<!-- ko with: contents.itemContainer -->
<!-- ko foreach: items -->
The ko with binding automatically unwraps itemContainer and uses the template within if it is defined. See here: http://knockoutjs.com/documentation/with-binding.html
Also, note that even if itemContainer was defined, you would still need to invoke the observable to get the underlying object.
<!--ko foreach: contents.itemContainer().items -->
Upvotes: 3
Reputation: 7641
Does this help (jsfiddle) ?
markup:
<p id="target">
<!--ko if: contents.itemContainer() -->
<!--ko foreach: contents.itemContainer().items -->
<!--ko template: 'itemTemplate', data: $data -->
<!--/ko -->
<!--/ko -->
<!--/ko -->
</p>
<p id="actions">
<button data-bind="click: actions.add">Add</button>
<button data-bind="click: actions.reset">Reset</button>
</p>
<p>Databound count:<span data-bind="text: actions.count"></span></p>
<p>Actual count:<span id="actualCount"></span></p>
<script type="text/html" id="itemTemplate">
<span data-bind="text: $data"></span>
</script>
code:
var unwrap = ko.unwrap;
var viewModel = {
contents: {
itemContainer: ko.observable(null)
},
actions: {
add: function () {
if (!viewModel.contents.itemContainer()) {
viewModel.contents.itemContainer({items: ko.observableArray([])});
}
if (viewModel.contents.itemContainer().items) {
viewModel.contents.itemContainer().items.push("new item");
}
$("#actualCount").html(viewModel.contents.itemContainer().items().length);
},
reset: function () {
viewModel.contents.itemContainer({items: ko.observableArray(["item 1", "item 2"])});
$("#actualCount").html(viewModel.contents.itemContainer().items().length);
},
count: ko.computed(function() {
if (!viewModel) {
return -1;
}
return viewModel.contents.itemContainer().items().length;
})
}
};
ko.applyBindings(viewModel);
Upvotes: 1