Michael
Michael

Reputation: 33

Adding and Updating observables in a part of the ViewModel with Knockout Mapping plugin - Not working

Though this might look like many of the similar questions about knockout mapping plugin and partial view model updation, I did not see the issue im facing here reported in all my searching.

I will detail with an example below but in short this is what I am trying to do.

First I create View Model from a Javascript Object using KO Mapping plugin's ko.mapping.fromJS. Now I want to update only a part of the view model with the data that I receive from the server. So.

  1. I convert the viewModel to a JavaScript object using ko.mapping.toJS
  2. Update the part of the JS object with the data from server
  3. Use the updated JS object to update the view model using ko.mapping.fromJS

The problems

  1. Values of observables when first created from JS object through mapping remain the same even after update
  2. If new properties are present in the server data object, they show-up with the values they came with, in the updated view model - but then will not update to new values in consequent updates.

Here is a simplified example.

The View

<div>
  <label data-bind="text: mainTitle"></label>
  <div data-bind="with: layer0">
    <label>Name - </label>
    <label data-bind="text: name"></label>
    <label>Type - </label>
    <label data-bind="text: type"></label>
  </div>
</div>

Data model which will be used to make the ViewModel

  var dataModel =  {
     mainTitle: "Main Title",
     layer0:{
              itemDetails:{
                            name:"item1name",
                            type:"type1"
                          },
              otherDetails:{
                             prop1: "prop1Value",
                             prop2: "prop2Value"
                           }
    }

This JS object dataModel is converted to KO View Model by

var viewModel = ko.mapping.fromJS(dataModel);

and applied to the view above by applyBindings and it displays data as expected

Now I receive this JS object from the server

var newJSobjectFromServer =  {
         name: "item2Name",
         description: "item2Description"   // new property that was not on first model
         type: "type2"
        }

Now I cannot do

viewModel.layer0.itemDetails(newJSobjectFromServer);

because in the viewModel which was created by the mapping plugin itemDetails is a property and not a method only name and type are methods

So I first extract a JS object from the viewModel by

var viewModelJS = ko.mapping.toJS(viewModel);

Then I update a part of the viewModelJS with new server data

viewModelJS.layer0.itemDetails = newJSobjectFromServer;

so viewModelJS becomes

{
 mainTitle: "Main Title",
 layer0:{
          itemDetails:{
                       name: "item2Name",
                       description: "item2Description"   // new property 
                       type: "type2"
                      }, 
          otherDetails:{ 
                         prop1: "prop1Value",
                         prop2: "prop2Value"
                       }
        }
}

Then I try to update the viewModel by

ko.mapping.fromJS(viewModelJS , viewModel);

also tried

ko.mapping.fromJS(viewModelJS , {}, viewModel);

but the result is

layer0.itemDetails.name() is still "item1" and layer0.itemDetails.type() is still "type1" while the new observable layer0.itemDetails.description() is present and has the value "item2Description"

now if I repeat the updating with another server returned object

{
   name: "item3Name",
   description: "item3Desctiption" 
   type: "type3"
}

this time all the values in viewModel remain same as last time that is layer0.itemDetails.name() is still "item1" and layer0.itemDetails.type() is still "type1" and layer0.itemDetails.description() is still "item2Description" and thus nothing changes in the view.

The complexities of the objects handled make it necessary for me to use the mapping plugin only the example is simplified but does completely portray the problem I am facing. I am really reaching out for someone to guide me how to get that part of the viewModel updated with the data from the server in the above exact scenario. If that is solved I can apply it to the more complex real world scenario that I am facing. Thanks in advance.

Upvotes: 0

Views: 1012

Answers (1)

Mark B
Mark B

Reputation: 1186

Your UI is still bound to the same ViewModel and running the mapping again doesn't refresh it.

You can use a different approach where you create a parent view model and set your "viewModel" as an observable member of the parent ViewModel. Check out this fiddle for a very rushed (but working) example of this behavior

http://jsfiddle.net/barryman9000/4SKNd/2/

var parentViewModel = function (data) {
    var _self = this;
    _self.Data = ko.observable('');
    _self.LoadData = function () {
        _self.Data(new viewModel(data));
    };
    _self.GetNewData = function (data, item) {
        _self.Data(new viewModel(newJSobjectFromServer));
    };
    _self.LoadData();
};
var viewModel = function (data) {
    var _self = this;
    ko.mapping.fromJS(data, {}, _self);
    _self.Data = ko.observable(data);
};
ko.applyBindings(new parentViewModel(dataModel));

Also, if the GetNewData() function makes an ajax call to get the data, this will still refresh your UI - that's how I use this "pattern."

UPDATE

Some ideas: 1) restructure your data so it's uniform in all cases. Otherwise you'll be building each of the viewModels very differently which is just going to cause readability headaches later. http://jsfiddle.net/barryman9000/Q5RRr/1/

2) If you can't restructure the data, ditch the mapping plugin and do everything manually. I'd say this yields much cleaner markup and gives you more control of the data. http://jsfiddle.net/barryman9000/5DD27/

Upvotes: 1

Related Questions