Joel Etherton
Joel Etherton

Reputation: 37533

Knockout mapping is not updating my model

I'm having trouble with a knockout model that is not binding on a subscribed update. I have a C# MVC page that delivers a model to the template which is parsed to Json and delivered raw as part of a ViewModel assignment for ko.applyBindings. I have a subscription to an observable that calls a method to perform an update of the viewModel's data. Irrelevant stuff pulled out and renamed for example usage:

var myViewModel = function (data) {
    var self = this;

    self.CurrentPage = ko.observable();
    self.SomeComplexArray= ko.observableArray([]);

    self.Pager().CurrentPage.subscribe(function (newPage) {
        self.UpdateMyViewModel(newPage);
    });

    self.UpdateMyViewModel= function (newPage) {
        var postData = { PageNumber: newPage };
        $.post('/Article/GetMyModelSearchByPage', postData, function (data) {
            ko.mapping.fromJS(data, {}, self);;
        });
};

When I perform logging, I can see all of the data, and it all looks correct. The same method is used to produce both the initial model and the updated model. I've used this technique on other pages and it worked flawlessly each time. In this case however, I'm looking for it to bind/update SomeComplexArray, and that's just not happening. If I attempt to do it manually, I don't get a proper bind on the array I get blank. I'm wondering if there is something obvious that I'm doing wrong that I'm just flat out missing.

Edit: I don't know that ko.mapping can be pointed to as the culprit. Standard model changes are also not affecting the interface. Here is something that is not working in a bound sense. I have a p element with visible bound to the length of the array and a div element with a click bound to a function that pops items off of SomeComplexArray. I can see in the console log that it is performing its function (and subsequent clicks result in 'undefined' not having that function). However, the p element never displays. The initial array has only 2 items so a single click empties it:

<p data-bind="visible: SomeComplexArray().length === 0">nothing found</p>


<div data-bind="click: function() { UpdateArray(); }">try it manually</div>


-- in js model
self.UpdateArray = function () {
        console.log(self.SomeComplexArray());
        console.log(self.SomeComplexArray().pop());
        console.log(self.SomeComplexArray());
        console.log(self.SomeComplexArray().pop());
        console.log(self.SomeComplexArray());
});

Edit 2: from the comment @Matt Burland, I've modified how the pop is called and the manual method now works to modify the elements dynamically. However, the ko.mapping is still not functioning as I would expect. In a test, I did a console.log of a specific row before calling ko.mapping and after. No change was made to the observableArray.

Upvotes: 3

Views: 761

Answers (2)

Joel Etherton
Joel Etherton

Reputation: 37533

I'm not really sure why, but it would seem that ko.mapping is having difficulty remapping the viewmodel at all. Since none of the fields are being mapped into self my assumption is that there is an exception occurring somewhere that ko.mapping is simply swallowing or it is not being reported for some other reason. Given that I could manually manipulate the array with a helpful tip from @MattBurland, I decided to backtrack a bit and update only the elements that needed to change directly on the data load. I ended up creating an Init function for my viewModel and using ko.mapping to populate the items directly there:

self.Init = function (jsonData) {
    self.CurrentPage(0);
    self.Items(ko.mapping.fromJS(jsonData.Items)());
    self.TotalItems(jsonData.TotalItems);
    // More stuff below here not relevant to question
}

The primary difference here is that the ko.mapping.fromJS result needed to be called as a function before the observableArray would recognize it as such. Given that this worked and that my controller would be providing an identical object back during the AJAX request, it was almost copy/past:

self.UpdateMyViewModel= function (newPage) {
    var postData = { PageNumber: newPage };
    $.post('/Article/GetMyModelSearchByPage', postData, function (data) {
        self.Items(ko.mapping.fromJS(JSON.parse(data).Items)());
    });
};

This is probably not ideal for most situations, but since there is not a large manipulation of the viewModel occurring during the update this provides a working solution. I would still like to know why ko.mapping would not remap the viewModel at the top level, but in retrospect it probably would have been a disaster anyway since there was "modified" data in the viewModel that the server would have had to replace. This solution is quick and simple enough.

Upvotes: 0

Bura Chuhadar
Bura Chuhadar

Reputation: 3751

I created a test of your knockout situation in JSFiddle.

You have to call your array function without paranthesis. I tested this part:

self.UpdateArray = function () {

        self.SomeComplexArray.pop();

};

It seems to be working on JSFiddle side.

Upvotes: 0

Related Questions