如何使用Knockout动态更新对象的属性?

时间:2014-02-12 21:44:32

标签: knockout.js

在附件jsfiddle example中,我正在尝试创建一个页面,用于比较两架飞机的统计数据,用户可以从选择菜单中选择每个飞机。

我使用构造函数通过ID号为每个平面构建统计数据,并从不可编辑数据数组中提取“specs”(属性)。这适用于页面加载,但是当我更改ID的值时,例如“myPlaneId”,对象“MyPlaneSpecs”不会更新。

我应该以不同的方式接近这个吗?这是我的第一个Knockout项目,所以我知道我可能错过了一些明显的东西。

请参阅JS Fiddle demo

在该示例中,您可以看到planeId正在更新,但整体平面规格不是。

<h1>Plane Comparison</h1>
  Your plane: <select data-bind="options: planes, optionsValue: 'id', value: myPlaneId,  optionsText: 'name'"></select>
  <br>
  Their plane: <select data-bind="options: planes, optionsValue: 'id', value: theirPlaneId, optionsText: 'name'"></select>

  <h2>Your plane specs: </h2>
  ID: <span data-bind="text: myPlaneId"></span> <br>
  Name: <span data-bind="text: myPlaneSpecs().planeName"></span> <br>
  Max Speed:    <span data-bind="text: myPlaneSpecs().speed"></span> <br>
  Climb Rate:    <span data-bind="text: myPlaneSpecs().climbrate"></span>
  <br><br>
  <h2>Their plane specs: </h2>
  ID: <span data-bind="text: theirPlaneId"></span> <br>
  Name: <span data-bind="text: theirPlaneSpecs().planeName"></span> <br>
  Max Speed:    <span data-bind="text: theirPlaneSpecs().speed"></span> <br>
  Climb Rate:    <span data-bind="text: theirPlaneSpecs().climbrate"></span>


       <script>

    // Class to represent a chosen plane
    function PlaneSpecs(specsArray) {
      this.specs = ko.observable(specsArray);
      this.planeId = this.specs().id;
      this.planeName = this.specs().name;
      this.speed = this.specs().speed;
      this.climbrate = this.specs().climbrate;
    }

    // Overall viewmodel for this screen
    function DogfightViewModel() {
      var self = this;
      self.myPlaneId = ko.observable(0);
      self.theirPlaneId =  ko.observable(1);

      // Non-editable catalog data
      self.planes = [
        { id: 0, name: "P-47D Thunderbolt", speed: 690, climbrate: 16, turntime: 25.8 },
        { id: 1, name: "BF 109 F4", speed: 660, climbrate: 18, turntime: 20.2 },
        { id: 2, name: "F6F-3 Hellcat", speed: 639, climbrate: 22.8, turntime: 21.8 }
      ];

      myPlaneSpecs = ko.observable(
         new PlaneSpecs(self.planes[self.myPlaneId()])   //This doesn't get updated when the ID changes
      );
      theirPlaneSpecs = ko.observable(
        new PlaneSpecs(self.planes[self.theirPlaneId()])
      );

    }

    ko.applyBindings(new DogfightViewModel());

  </script>

2 个答案:

答案 0 :(得分:1)

您应该使用ko.computed来实现您想要的效果。如果您要创建一个函数来通过id获取该平面,如下所示(我的下面的版本没有任何错误处理或检查以确保该平面实际存在):

function getPlaneById(id){
    var selectedPlane = self.planes.filter(function(plane){ return plane.id === id })[0];
    return new PlaneSpecs(selectedPlane);
}

然后你可以改变你的myPlaneSpecs和他们的PlanApecs来计算:

self.myPlaneSpecs = ko.computed(function(){
    return getPlaneById(self.myPlaneId());
});
self.theirPlaneSpecs = ko.computed(function(){
    return getPlaneById(self.theirPlaneId());
});

此外,您并没有真正使用您在PlaneSpecs构造函数中创建的observable,所以我建议删除它,而是执行以下操作:

function PlaneSpecs(specs) {
    this.planeId = specs.id;
    this.planeName = specs.name;
    this.speed = specs.speed;
    this.climbrate = specs.climbrate;
}

您还可以使用with绑定简化您的平面信息绑定,如下所示(可以对myPlaneSpecstheirPlaneSpecs同时完成):

<div data-bind="with:myPlaneSpecs">
    <h2>Your plane specs: </h2>
    ID: <span data-bind="text: planeId"></span> <br>
    Name: <span data-bind="text: planeName"></span> <br> 
    Max Speed:    <span data-bind="text: speed"></span> <br>
    Climb Rate:    <span data-bind="text: climbrate"></span>
</div>

编辑:添加淘汰模板的使用

由于您以相同的方式显示两个平面规格,您还可以为平面规格显示创建模板,如下所示:

<script type="text/html" id="planeTemplate">    
    ID: <span data-bind="text: planeId"></span> <br>
    Name: <span data-bind="text: planeName"></span> <br>    
    Max Speed:    <span data-bind="text: speed"></span> <br>
    Climb Rate:    <span data-bind="text: climbrate"></span>
</script>

之后,您可以使用该模板显示myPlaneSpecstheirPlaneSpecs的平面规格,并带有以下标记:

<h2>Your plane specs: </h2>
<div data-bind="template:{ name: 'planeTemplate', data: myPlaneSpecs}"></div>

<h2>Their plane specs: </h2>
<div data-bind="template:{ name: 'planeTemplate', data: theirPlaneSpecs}"></div>

_编辑:添加指向更新的jsfiddle的链接

我已使用上述代码更新了您的小提琴,包括模板用法,您可以在http://jsfiddle.net/ar6dY/6/

找到

答案 1 :(得分:1)

选项绑定集有几个怪癖(“options”,“optionsValue”,“optionsText”和“value”)。

如果使用optionsValue绑定,则“value”绑定的observable(myPlaneId)将使用每个选项的值设置(在您的情况下为“id”属性的值)。

当MyPlaneId值发生变化时,您没有任何内容会重新计算myPlaneSpecs observable上的属性。对于任何其他可观察到的变化,观察者不会改变。这就是计算的可观察量。

如果您不提供optionsValue绑定,那么整个对象文字将存储在后备observable中。

考虑到这一点,你可以大大简化这个例子

  1. 停止使用optionsValue并存储整个所选元素的对象文字。
  2. 将self.myPlaneId重命名为self.myPlane并调整值绑定
  3. 将self.theirPlaneId重命名为self.theirPlane并调整值绑定
  4. 您不需要PlaneSpecs类,只需直接绑定到对象文字。使用“with”虚拟绑定使HTML更容易阅读(并注意myPlane和他们的平面之间可能重用html。
  5. (可选)将self.planes转换为observableArray。如果动态设置数组源(如来自AJAX响应),它将在未来更容易。
  6. 如果您不喜欢使用虚拟绑定,请将下面的HTML更改为将myPlane()或itsPlane()添加到每个相应的范围。

    <span data-bind="text: id"></span>
    

    <span data-bind="text: myPlane().id"></span>
    

    <h1>Plane Comparison</h1>
      Your plane:
      <select data-bind="options: planes, value: myPlane,  optionsText: 'name'"></select>
      <br>
      Their plane: <select data-bind="options: planes, value: theirPlane, optionsText: 'name'"></select>
    
      <h2>Your plane specs: </h2>
      <!-- ko with: myPlane -->
      ID: <span data-bind="text: id"></span> <br>
      Name: <span data-bind="text: name"></span> <br>
      Max Speed:    <span data-bind="text: speed"></span> <br>
      Climb Rate:    <span data-bind="text: climbrate"></span>
      <!-- /ko -->
      <br><br>
      <h2>Their plane specs: </h2>
    
      <!-- ko with: theirPlane -->
      ID: <span data-bind="text: id"></span> <br>
      Name: <span data-bind="text: name"></span> <br>
      Max Speed:    <span data-bind="text: speed"></span> <br>
      Climb Rate:    <span data-bind="text: climbrate"></span>
      <!-- /ko -->
    

        // Overall viewmodel for this screen
        function DogfightViewModel() {
          var self = this;
    
          // Non-editable catalog data
          self.planes = ko.observableArray( [
            { id: 0, name: "P-47D Thunderbolt", speed: 690, climbrate: 16, turntime: 25.8 },
            { id: 1, name: "BF 109 F4", speed: 660, climbrate: 18, turntime: 20.2 },
            { id: 2, name: "F6F-3 Hellcat", speed: 639, climbrate: 22.8, turntime: 21.8 }
          ] );
    
          self.myPlane = ko.observable(self.planes()[0]);
          self.theirPlane =  ko.observable(self.planes()[1]);
        }
    
        ko.applyBindings(new DogfightViewModel());