Performing partial updates with KnockoutJS mapping plugin
Asked Answered
C

2

6

Right now, I'm using this JSON with the KO Mapping plugin and it's working fine:

{
  "Controls": [
    {
      "Fields": [
        {
          "Name": "emailField", 
          "Text": "email", 
          "Visible": true
        }, 
        {
          "Name": "hiddenField", 
          "Text": "text", 
          "Visible": true
        }
      ], 
      "Name": "form2", 
      "Type": "Form"
    }, 
    {
      "Data": [
        [
          "Federico Aloi", 
          20
        ], 
        [
          "Andres Lopez", 
          31
        ], 
        [
          "Pablo Perez", 
          32
        ]
      ], 
      "Fields": [
        {
          "Name": "nameField", 
          "Text": "Nombre", 
          "Visible": true
        }, 
        {
          "Name": "ageField", 
          "Text": "Edad", 
          "Visible": true
        }
      ], 
      "Name": "datagrid1", 
      "Type": "Datagrid"
    }
  ], 
  "Name": "pagina1", 
  "Title": "Probando el KO"
}

Now my requirement is to perform "partial updates". Some scenarios when I'd like to do that:

  • I need to change the Data array in the second control.
  • I need to update only one Control and not the whole Page (this is the class I'm serializing, the root in this JSON).
  • I need to add another Control to my Page.

Perhaps another workaround would be recreating the original object with ko.mapping.toJS(viewModel), change it and then re-map it again... but I believe that you will come out with something better.


EDIT: I tried with ko.mapping.fromJS(updatedControl, viewModel.Controls()[0]) but it didn't work, here is my code:

function (control) {
    $.getJSON($.format('api/control/{0}/{1}', viewModel.Name(), control.Name()), function (response) {
        ko.mapping.fromJS(response, viewModel.Controls()[0]);
    });
},

response:

{
  "Fields": [
    {
      "Name": "emailField", 
      "Text": "email", 
      "Visible": true
    }, 
    {
      "Name": "hiddenField", 
      "Text": "text", 
      "Visible": true
    }
  ], 
  "Name": "form2", 
  "Type": "Form"
}

EDIT2: check it out at http://jsfiddle.net/faloi/4FcAy/10/

Crary answered 18/4, 2012 at 15:48 Comment(1)
Can you replace the code in this jsfiddle with your code: jsfiddle.net/JasonMore/LKS5b I bet I can help you once I see the JS running.Athos
P
6

I think this is now the single most asked question re the mapping plugin. The plugin can only currently do partial updates of properties. E.g if you were to remap the below data to your object

{ "Name": "pagina1 foo", "Title": "Probando el KO bar" }

It would update just those properties. However the collections are treated differently so if you were to remap with the below.

{ "Name": "pagina1 foo", "Title": "Probando el KO bar", 
  Controls: [ { // snip some updated object } ] }

It would remove the non-matching item in your Controls collection instead of updating the matching item. The only way to get this to work is to loop through the collection and map each individual item you wish to update. Your code is almost on the right track I believe it should be

ko.mapping.fromJS(updatedControl, viewModel.Controls()[0])

EDIT

You were missing the middle mapping argument on the fromJS call.

http://jsfiddle.net/3CtFq/

The mapping object is usually optional but I guess if you are mapping a sub object as is your case, the plugin can't tell that this sub items parent was originally mapped with no options.

Hope this helps.

Painstaking answered 18/4, 2012 at 22:16 Comment(3)
I tried with ko.mapping.fromJS(updatedControl, viewModel.Controls()[0]) but didn't work... it's weird because viewModel.Controls()[0] actually returns the first mapped controlCrary
@Crary - if you can transfer to a jsfiddle it would make it easier for us to fix what's wrong.Painstaking
and the second most asked question is 'will this behavior ever be changed?'Embryotomy
E
0

I thought that was the whole point of having the 'key' option. I realize there are caveats to this kind of feature, but I'm surprised there's no support for it at all.

Anyway - this is (regrettably) what I've ended up having to do to partially update a list of orders - which is to loop through and replace items.

data is my main AJAX/JSON model from the server and contains data.orders which is ko.observableArray([]) of OrderViewModel items. It also contains all kinds of other stuff data.products, data.foo that I want to initially map.

I have a mapping rule to create new OrderViewModel objects automatically during the mapping process.

            var mapping =
            {
                'orders':
                {
                    key: function (item)
                    {
                        return ko.utils.unwrapObservable(item.orderId);
                    },

                    create: function (options)
                    {
                        return new OrderViewModel(options.data);
                    }
                }
            };

When I get the data back from the server I initially want to do a complete mapping of my model. So I set initialUpdate=true.

When I want to refresh a single order I have initialUpdate=false.

     if (initialUpdate) 
     {
         // standard initial mapping 
         ko.mapping.fromJS(data, mapping, this);
     }
     else
     {
         // merge orders
         var newModel = ko.mapping.fromJS(data, mapping);

         // go through all orders in the updated list
         for (var i = 0; i < newModel.orders().length; i++)
         {
             var newOrder = newModel.orders()[i];

             // find order with same key in existing ko model
             var match = ko.utils.arrayFirst(this.orders(), function (item)
             {
                 return newOrder.orderId() === item.orderId();
             });

             if (match == null)
             {
                 // TODO: insert new order                            
             }

             // replace original order in list
             this.orders.replace(match, newOrder);
         }
     }
Embryotomy answered 16/7, 2014 at 1:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.