Reputation: 1517
I have the following Knockout ViewModel:
var EditorViewModel = function () {
var self = this;
self.addData = function (_data) {
ko.mapping.fromJS(_data, {}, self);
};
};
which is used as such:
var viewModel = new EditorViewModel();
viewModel.addData(model);
ko.applyBindings(viewModel);
For info, the data for viewModel.addData()
comes from an AJAX call.
The View/ViewModel is populated with no problem the first time round when ko.applyBindings
is called. However, if I later pull new data from the server via AJAX and then call either:
viewModel.addData(someNewAjaxData)
or
ko.mapping.fromJS(someNewAjaxData, {}, viewModel);
Then Knockout does not update the view with the new data. Hopefully this is a trivial problem...?!
Upvotes: 0
Views: 287
Reputation: 101604
KnockoutJS Mapping has the ability to specify a key for an item. This lets KnockoutJS know when an item needs to be created versus updated.
var ItemViewModel = function(d){
var self = this;
self.id = ko.observable(d.id);
self.text = ko.observable(d.text);
// We assign it when the object's created to demonstrate the difference between an
// update and a create.
self.lastUpdated = ko.observable(new Date().toUTCString());
}
var EditorViewModel = function(){
var self = this;
self.items = ko.observableArray();
self.addData = function(d){
ko.mapping.fromJS(d, {
'items': {
'create': function(o){
return new ItemViewModel(o.data);
},
'key': function(i){
return ko.utils.unwrapObservable(i.id);
}
}
}, self);
console.info(self);
}
self.addMoreData = function(){
self.addData({
'items': [
{id:1,text:'Foo'},
{id:3,text:'Bar'}
]
});
}
}
var viewModel = new EditorViewModel();
viewModel.addData({
items:[
{id:1,text:'Hello'},
{id:2,text:'World'}
]
});
ko.applyBindings(viewModel);
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Hello, world!</h3>
</div>
<ul class="list-group">
<!-- ko foreach:items -->
<li class="list-group-item">
<span class="text-muted" data-bind="text:id"></span>
<span data-bind="text:text"></span>
<span class="text-danger pull-right" data-bind="text:lastUpdated"></span>
</li>
<!-- /ko -->
<li class="list-group-item" data-bind="visible:!items().length">
<span class="text-muted">No items.</span>
</li>
</ul>
<div class="panel-footer">
<a href="#" data-bind="click:addMoreData">Update</a>
</div>
</div>
</div>
</div>
</div>
Observe, in the attached snippet, how the first item's lastUpdated value is assigned when it's created, but is left unchanged when it's updated. This is because the mapper sees the ID already exists in items
and simply updated the properties. But, for item with the id 3, a new model is created.
Upvotes: 1
Reputation: 7641
All knockout bibdings were subscribed to the first created model. You recreate the bound model on every ajax response. Your problem seems similar that was discussed in this stackoverflow article. The same osbervable should be bound to the markup.
Try this (may be you should support an empty model binding):
// in the start of app
var observableModel = ko.observable();
ko.applyBindings(observableModel);
// on every ajax call
var viewModel = new EditorViewModel();
viewModel.addData(model);
observableModel(viewModel);
Upvotes: 0