为什么Knockout.js数据绑定到checkedValue会覆盖observable?

时间:2014-09-11 05:15:35

标签: javascript knockout.js knockout-mapping-plugin observable

我遇到了使用knockout mapping插件的问题。 我有一个 ShippingOptions 数组,我使用选中的值将数据绑定到单选按钮,并将checked属性设置为我的视图模型中名为 ShippingChoice 的另一个对象。

我有一个计算的可观察总数,它是我的小计 ShippingChoice.Price

的总和

当选择其中一个收音机选项时,它会正确地将ShippingChoice设置为所选的送货选项,但会导致 ShippingChoice 不再是可观察的并且会破坏我的计算总数

控制台显示错误“类型错误:数字不是函数”,因为我在计算机中访问 ShippingChoice()。价格。一旦设置了值,它似乎用普通对象覆盖我的observable。

我尝试使用自定义映射将Shipping选项设置为可观察但是没有帮助。

Jsfiddle演示: http://jsfiddle.net/on3al/2dv33vor/9/

任何见解或建议都会受到极大的影响。我在这个问题上的表现已经太久了。

HTML

      <!-- ko foreach: ShippingOptions -->
        <div class="radio">
            <input type="radio" name="optionsRadios" 
                  data-bind="checkedValue: $data ,checked: $root.ShippingChoice" /> 
            <span data-bind="text: Carrier"></span>
            <span data-bind="text: Service"></span>
            <span data-bind="money: Price"></span> 
        </div>
        <!-- /ko -->
    </div>
    <div class="col-xs-4">
         <h5 class="text-right">Subtotal <strong data-bind="money: SubTotal"></strong></h5>
         <h5 class="text-right" data-bind="money: ShippingChoice().Price">Shipping </h5>
         <h4 class="text-right">Total <strong data-bind="money: Total"></strong></h4>
    </div>

的Javascript

   var mapping = {
         'ShippingChoice': {
             create: function (options) {
                 return ko.observable(options.data);
             }
         }
     };

     CartViewModel = function (data) {
         var self = this;
         ko.mapping.fromJS(data, mapping, self);

         self.SubTotal = ko.computed(function () {
             var subTotal = 0;
             for (var i = 0; i < self.Items().length; i++) {
                 var item = self.Items()[i];
                 subTotal += item.Quantity() * item.Product.Price();
             }
             return subTotal;
         }, self);

         self.Total = ko.computed(function () {
             return self.SubTotal()+ self.ShippingChoice().Price;
         }, self);

         self.decreaseQty = function (item) {
             var currQty = item.Quantity();
             if (currQty > 0) {
                 item.Quantity(currQty - 1);
             }
             self.updateQty(item);
         };

         self.increaseQty = function (item) {
             var currQty = item.Quantity();
             item.Quantity(currQty + 1);
             self.updateQty(item);
         };
     };

     ko.bindingHandlers.money = {
         update: function (element, valueAccessor, allBindingsAccessor) {
             var value = ko.utils.unwrapObservable(valueAccessor());
             var positions = 2;
             var formattedValue = value.toFixed(positions);
             var finalFormatted = '$' + formattedValue;

             ko.bindingHandlers.text.update(element, function () {
                 return finalFormatted;
             });
         },
         defaultPositions: 2,
     };

     var cartViewModel = new CartViewModel({
         "$id": "1",
             "Id": "540eb73cff622605c4f45b39",
             "Items": [{
             "$id": "2",
                 "Product": {
                 "$id": "3",
                     "Status": "In Stock",
                     "Price": 19.99,
                     "QuantityInStock": 12,
                     "Brand": "Dummy Brand",
                     "Weight": 8.0,
                     "Width": 12.0,
                     "Length": 14.0,
                     "Height": 3.0,
                     "UrlSlug": "dummy-slug",
                     "Title": "Dummy Product",
             },
                 "Quantity": 7,
         }],
             "Country": "CA",
             "PostalCode": null,
             "ShippingOptions": [{
             "$id": "4",
                 "Carrier": "USPS",
                 "Price": 22.1,
                 "Service": "FirstClassPackageInternationalService"
         }, {
             "$id": "5",
                 "Carrier": "USPS",
                 "Price": 32.85,
                 "Service": "PriorityMailInternational"
         }, {
             "$id": "6",
                 "Carrier": "USPS",
                 "Price": 46.96,
                 "Service": "ExpressMailInternational"
         }],
             "ShippingChoice": {
             "$id": "7",
                 "Carrier": "",
                 "Price": 8.0,
                 "Service": ""
         }
     });
     ko.applyBindings(cartViewModel);

3 个答案:

答案 0 :(得分:3)

您也可以通过在地图选项中“复制”ShippingOptions来解决您的问题:

var mapping = {
  'ShippingChoice': {
    create: function (options) {
      return ko.observable(options.data);
    }
  },
  copy: [ 'ShippingOptions' ]
};

它将导致普通对象数组,价格将是数字而不是可观察数据。

http://jsfiddle.net/2dv33vor/12/

答案 1 :(得分:2)

你几乎拥有它。您的映射对象应该稍微更改一下:

var mapping = {
    'ShippingChoice': {
        create: function (options) {
            return ko.observable(ko.mapping.fromJS(options.data));
        }
    }
};

在您的视图模型中:

...
self.Total = ko.computed(function () {
    return self.SubTotal()+ self.ShippingChoice().Price(); // I've added () to the Price, since it's an observable now.
}, self);

事情是,当您的ShippingOptions数组被映射时,它的所有属性都被视为可观察的。因此,您也应该使您的ShippingChoice属性可观察。

Working demo.

答案 2 :(得分:1)

更改您的Total计算自:

 self.Total = ko.computed(function () {
    return self.SubTotal() + self.ShippingChoice().Price;
 }, self);

为:

 self.Total = ko.computed(function () {
    return ko.unwrap(self.SubTotal) + ko.unwrap(self.ShippingChoice().Price);
 }, self);

当您在原始代码中向console.log(value)装订处理程序添加money时,您会在选择送货时立即看到此内容:

"307.65000000000003function d(){if(0<
arguments.length)return d.Pa(c,arguments[0])&&(d.X(),c=arguments[0],d.W()),this;a.k.Jb(d);return c}"

这表明一个observable与+运算符连接到一个不可观察的。

您的Total功能是唯一可能发生这种情况的地方。