Refreshing a selected observable when parent is up

2019-07-28 10:15发布

问题:

I asked this question previously, but it didn't get much response. It was obvious that I wasn't very clear what I was trying to achieve, so I'll try again.

Here's the jsFiddle to play around with


I'm working on an app that will (at an interval) call a web method, which will read a database and build up a JSON string to return which is used to construct my view model. When my view model changes I need to mutate all selected observables with the new data.

Take this as an example underlying model.

var model = {
    people: [
        { forename: "Test", surname: "Person", numbers: [1,2,3] },
        { forename: "Another", surname: "Test", numbers: [4,5,6] }
    ]
};

Simple enough, a table of people that when a row is clicked on a modal will pop up showing a list of numbers the selected person has. While this modal is open, the data could be being updated behind the scenes so if [Test Person] is selected (and the modal pops up) and someone, somewhere adds [7,8,9] to their numbers then I need the modal to refresh showing these additions.

Here's a basic view model (using ko.mapping for brevity):

var viewModel = {
    people: ko.mapping.fromJS(model.people),
    selectedPerson: ko.observable(null),
    refresh: function () {
        ko.utils.arrayForEach(model.people, function (person) {
            var last = person.numbers[person.numbers.length - 1];
            var newNumber = last + 1;
            person.numbers.push(newNumber);
        });
        ko.mapping.fromJS(model.people, this.people);
        console.log(ko.toJSON(this));
    }
};

When a row is clicked on, the person is stored in selectedPerson which makes my modal become visible in the UI (I'm using Zurb Foundation):

<div id="modal" class="reveal-modal" data-bind="if: selectedPerson">

This modal shows a table of numbers with a button named Update Observable - this calls a function to emulate the changes to the underlying model (in this case I'm taking the last item in numbers incrementing it by 1 and pushing it on). When this is done I then re-create my view model (using ko.mapping for brevity) and expect everything on the UI to be updated to reflect the change - all fine apart from the modal.

Looking at the JSON of my view model after re-mapping I can see that the view model has been updated correctly, but the selectedPerson hasn't. I assumed selectedPerson was a reference, not a copy, but I obviously assumed wrong and we end up with this.

{
    "people": [
        {
            "forename": "Test",
            "surname": "Person",
            "numbers": [1,2,3,4,5,6,7,8]
        },
        {
            "forename": "Another",
            "surname": "Test",
            "numbers": [4,5,6,7,8,9,10,11]
        }
    ],
    "selectedPerson": {
        "forename": "Test",
        "surname": "Person",
        "numbers": [1,2,3]
    }
}

So I suppose my question is: how do I update selectedPerson (or in fact any other selected observable there may be) when the underlying model changes?

回答1:

Your problem is that the ko.mapping.fromJS(model.people, this.people) empties and recreates your people collection. But your selectedPerson still references one of the old person object.

You need to tell the mapping plugin how you want to identify your items so it will update them:

ko.mapping.fromJS(model.people, 
                 { key: function(item) { return item.forename; } }, 
                 this.people); 

Demo JSFiddle.

Or as an alternative solution you can reset your selectedPerson to one of the the newly created person object after the mapping:

this.selectedPerson(ko.utils.arrayFirst(this.people(), function(item){
        return item.forename() == this.selectedPerson().forename();
    }, this));

Demo JSFiddle.



标签: knockout.js