使用KnockoutJS映射插件执行部分更新

时间:2012-04-18 15:48:06

标签: knockout.js knockout-mapping-plugin

现在,我正在将这个JSON与KO Mapping插件一起使用,它运行良好:

{
  "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"
}

现在我的要求是执行“部分更新”。有些情况我想这样做:

  • 我需要在第二个控件中更改数据数组。
  • 我只需要更新一个Control而不是整个页面(这是我要序列化的类,这个JSON中的根目录。)
  • 我需要在页面中添加另一个控件。

也许另一种解决方法是使用ko.mapping.toJS(viewModel)重新创建原始对象,更改它然后再重新映射...但我相信你会得到更好的东西。


编辑:我尝试使用ko.mapping.fromJS(updatedControl, viewModel.Controls()[0]),但它没有用,这是我的代码:

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

响应:

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

EDIT2:http://jsfiddle.net/faloi/4FcAy/10/

查看

2 个答案:

答案 0 :(得分:6)

我认为这是映射插件中最受欢迎的问题。该插件目前只能对属性进行部分更新。例如,如果您要将以下数据重新映射到您的对象

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

它只会更新这些属性。但是,如果您使用以下内容重新映射,则对集合进行不同的处理。

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

它将删除Controls集合中的不匹配项,而不是更新匹配项。实现此功能的唯一方法是遍历集合并映射您要更新的每个项目。您的代码几乎在正确的轨道上我相信它应该是

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

修改

你错过了fromJS调用的中间映射参数。

http://jsfiddle.net/3CtFq/

映射对象通常是可选的,但我想如果你像你的情况一样映射一个子对象,插件就不能告诉这个子项父对象最初是没有选项映射的。

希望这有帮助。

答案 1 :(得分:0)

我认为这就是拥有“关键”的重点。选项。我意识到这种功能有一些警告,但我很惊讶那里根本没有支持它。

无论如何 - 这是(遗憾的是)我最终必须做的部分更新订单清单 - 这是循环和替换项目。

data是来自服务器的主要AJAX / JSON模型,包含data.orders ko.observableArray([])OrderViewModel个项目。它还包含我想要初始映射的所有其他内容data.productsdata.foo

我有一个映射规则,可以在映射过程中自动创建新的OrderViewModel对象。

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

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

当我从服务器获取数据时,我最初想要对我的模型进行完整的映射。所以我设置了initialUpdate=true

当我想刷新单个订单时,我有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);
         }
     }