Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KnockoutJS: Update/Insert data to a viewModel using mapping

I've been trying to figure this out for quite some time now. I couldn't find anything that addresses this problem, but please correct me if I'm wrong.

The problem: I have data from a JSON API comming in, with an nested array/object structure. I use mapping to initially fill the model with my data. To update this, I want to extend the model if new data arrives, or update the existing data.

As far as I found out, the mapping option key, should do this trick for me, but I might have misunderstood the functionality of the mapping options.

I've boiled down the problem to be represented by this example:

var userMapping = {
    key: function(item) {
        return ko.utils.unwrapObservable(item.id);
    }
};

// JSON call replaced with values
var viewModel = {
    users: ko.mapping.fromJS([], userMapping)
};

// Should insert new - new ID?
ko.mapping.fromJS([{"id":1,"name":"Foo"}, {"id":2,"name":"Bar"}], userMapping, viewModel.users);

// Should only update ID#1 - same ID?
ko.mapping.fromJS([{"id":1,"name":"Bat"}], userMapping, viewModel.users);

// Should insert new - New ID?
ko.mapping.fromJS([{"id":3,"name":"New"}, {"id":4,"name":"New"}], userMapping, viewModel.users);

ko.applyBindings(viewModel);​

Fiddle: http://jsfiddle.net/mikaelbr/gDjA7/

As you can see, the first line inserts the data. All good. But when I try to update, it replaces the content. The same for the third mapping; it replaces the content, instead of extening it.

Am I using it wrong? Should I try to extend the content "manually" before using mapping?

Edit Solution:
I solved this case by having a second helper array storing all current models. On new data i extended this array, and updated the view model to contain the accumulated items.

On update (In my case a WebSocket message), I looped through the models, changed the contents of the item in question, and used method valueHasMutated() to give notice of changed value to the Knockout lib.

like image 904
mikaelb Avatar asked Mar 09 '12 15:03

mikaelb


2 Answers

From looking at your example code the mapping plugin is behaving exactly as I would expect it to. When you call fromJS on a collection you are effectively telling the mapping plugin this is the new contents of that collection. For example:

On the second line, How could it know whether you were updating or whether you had simply removed id:2?

I can't find any mention of a suitable method that treats the data as simply an update, although you could add one. Mapped arrays come with some helpful methods such as mappedIndexOf to help you find particular items. If you receive an update data set simply loop through it, find the item and update it with a mapping.fromJS call to that particular item. This can easily be generalized into reusable method.

like image 70
madcapnmckay Avatar answered Oct 04 '22 22:10

madcapnmckay


You can use ko.mapping.updateFromJS() to update existing values. However, it does not add new values so that would be a problem in your instance. Take a look at the link below for more details.

Using updateFromJS is replacing values when it should be adding them

like image 39
Mark Robinson Avatar answered Oct 04 '22 22:10

Mark Robinson