Search code examples
javascriptknockout.jsknockout-mapping-plugin

knockout mapping fromJS fromJSON isn't working inside a collection?


I've got my root knockout model that holds a collection of VisitEntries, each of these entries has an object on it called RoomProfile. When a user creates a visit entry (adds a room to their visit list), the room's profile data should be loaded and displayed to the user inside that collection. But I can't update the associated model. I've done this before in other areas no problem. I think there is a problem because I am making a copy of VisitEntryToAdd on my root mappedModel, or because it's inside a collection. I'm not sure, but those are the only differences from other times when I've done this.

mappedModel.addVisitEntry = function () {

        var visitEntryToAdd = ko.mapping.fromJS(ko.mapping.toJS(mappedModel.VisitEntryToAdd));

        $.ajax({
            url: '/GetRoomProfileForVisit',
            type: 'POST',
            data: ko.mapping.toJSON({ Id: visitEntryToAdd.SelectedRoom.Id() }),
            dataType: "json",
            contentType: 'application/json; charset=utf-8',
            success: function (data) {

                if (data !== null) {
                    console.log("Checking entry before assigning room profile data: \n" + ko.mapping.toJSON(visitEntryToAdd));
                    console.log("Room Profile Data Loaded:\n" + ko.mapping.toJSON(data));
                    ko.mapping.fromJS(data, {}, visitEntryToAdd.RoomProfile);
                    console.log("Checking entry after assigning room profile data: \n" + ko.mapping.toJSON(visitEntryToAdd));
                }

                mappedModel.VisitEntries.push(visitEntryToAdd);

            }
        });
    }

Going through the above code:

First console.log shows that visitEntryToAdd has all the correct fields including RoomProfile which it set to null before I add the room profile data.

Second console.log confirms my ajax method did return data and it is appropriately formed.

Third console.log shows that same visitEntryToAdd, and RoomProfile on it is still null.

I was able to get this working by doing:

visitEntryToAdd.RoomProfile(ko.mapping.fromJS(data));

But I am not sure why it doesn't work using the fromJS command only, as that it's usage. If someone can provide an explanation I will mark that as the answer.


Solution

  • If visitEntryToAdd.RoomProfile is null before the mapping, you are effectively saying ko.mapping.fromJS(data, {}, null) and there is nothing tying the newly mapped data to the viewmodel.

    You can solve this by initializing RoomProfile on the visit entry to {} instead of null, or executing the mapping as:

    ko.mapping.fromJS({RoomProfile: data}, {}, visitEntryToAdd);
    

    This way mapping will begin from the root of the viewmodel and the RoomProfile field on it is assignable. If it makes sense to initialize the field to {} instead, it will work because the RoomProfile field on the viewmodel is not replaced, only fields on it are updated and so the link from visitEntryToAdd -> RoomProfile is preserved.