Graham
Graham

Reputation: 1517

How to Refresh Knockout ViewModel Using Mapping Plugin

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

Answers (2)

Brad Christie
Brad Christie

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

TSV
TSV

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

Related Questions