使用写入函数时,Knockout JS 可写计算值不会触发另一个计算值

Posted

技术标签:

【中文标题】使用写入函数时,Knockout JS 可写计算值不会触发另一个计算值【英文标题】:Knockout JS writable computed value doesn't fire another computed value when writing function is used 【发布时间】:2021-06-18 06:25:21 【问题描述】:

我用了几个月的 Knockout Js。但我遇到了一个问题。我有 2 个视图模型。一种是整体账单视图模型,其中包含账单的小计和总计的税款。该法案的主要视图模型的一部分是:

    function BillViewModel() 
         var self = this;
         self.timesheets = ko.observableArray([]);
         self.items = ko.observableArray([]);
         self.amountdummy = ko.observable();
         self.subtotal = ko.computed(function()
             self.amountdummy();
             var total = 0;
             for(var i = 0; i < this.timesheets().length; i++)
             
                 var totalLine = this.timesheets()[i].amount();
                 total += parseFloat((totalLine != '' && totalLine !== null && !isNaN(totalLine) ? totalLine: 0));

             for(var i = 0; i < this.items().length; i++)
             
                 var totalLine = this.items()[i].amount();
                 total += parseFloat((totalLine != '' && totalLine !== null && !isNaN(totalLine) ? totalLine : 0));
            
             
             return total;
         , self);

    ;

账单的每一行都由另外 2 个视图模型表示,他们是:

    function BillItemViewModel(item) 
         var self = this;
         self.parent = item.parent;
         self.quantity = ko.observable(item.quantity);
         self.price = ko.observable(item.price);
         self.amount = ko.computed(
             read: function()
                 var quantity = getNumberFromFormattedValue(self.quantity());
                 if (quantity !== null)  quantity = parseFloat(quantity); 
                 var price = getNumberFromFormattedValue(self.price());
                 if (price !== null)  price = parseFloat(price); 
                 if (!(isNaN(quantity) || isNaN(price) || quantity === null || price === null || quantity === '' || price === '')) 
                     var newValue = quantity * price;
                     return newValue;
                 
                 // Don't change the value
             ,
             write: function(value)
                 var newValue = getNumberFromFormattedValue(value);
                 if (newValue !== null)  newValue = parseFloat(newValue); 
                 var quantity = getNumberFromFormattedValue(self.quantity());
                 if (quantity !== null)  parseFloat(quantity); 
                 if (!(isNaN(newValue) || isNaN(quantity) || newValue === null || quantity === null || newValue === '' || quantity === '')) 
                     self.price( newValue / quantity );
                 
                 self.parent.amountdummy.notifySubscribers();
             ,
             owner: self
         );
         self.quantity.subscribe(function()
            if (self.price() === '' || self.price() === null) 
                self.amount(0);
             
         );
         self.amount(item.amount);

    ;

第二个视图模型很像这个。除了用于输入时间和费率以及计算添加到小计中的金额。

html代码是:

       <table class="table item" data-bind="visible: items().length > 0">
            <tbody data-bind="foreach: items">
                 <tr>
                       <td class="qty"><input type="text" data-bind="value: quantity, name: function(data, event)  return 'qty_'+ $index(); ()"></td>
                       <td class="price"><input type="text" data-bind="value: price, name: function(data, event)  return 'price_'+ $index(); ()"></td>
                       <td class="amount"><input type="text" data-bind="value: amount, name: function(data, event)  return 'amount_'+ $index(); ()"></td>
                 </tr>
           </tbody>
       </table> 
       
       <div>
           <label for="subtotal">Subtotal</label>
           <input id="subtotal" data-bind="value: subtotal" type="text" name="subtotal" readonly>
       </div>

页面的行为是当用户输入数量和价格时,自动计算该行的金额。但是如果用户不能输入数量和价格,他可以直接输入数量。

完整示例请参见JsFiddle

一切正常。但是当用户只是在行上输入一个金额时,小计不会更新。

编辑:

我删除了有关税收的所有内容。我遵循了 Josh 通过这个 link 给出的提示。但它不再起作用了。

看到这个JsFiddle

任何帮助将不胜感激

【问题讨论】:

尝试将 pureComputed 更改为 computed。 还尝试避免混合使用thisself,当我尝试使用小提琴并只输入数量而不输入数量和数量时,计算的输入返回 NaN,所以我猜它的工作原理? @Nathan - 这不起作用 @johnSmith - 这也不起作用。显示 NaN 是因为我简化了代码:我没有花时间管理错误 是的,但我的意思是它显示 NaN 是证明小计已更新/计算已触发? 【参考方案1】:

我发现了如何继续计算小计。秘诀:在每行的数量旁边放置一个单独的可观察对象,并在数量更新时更新此值。接下来,小计应该计算那些隐藏的 observables 的总和,而不是计算的数量。

感谢乔希,他通过post 向我展示了如何解决它。

HTML 代码:

<html>
     <body>
         <form id="billForm">
             <table class="table timesheet">
                 <thead>
                     <tr>
                         <th>Time</th>
                         <th>Rate</th>
                         <th>Amount</th>
                     </tr>
                 </thead>
                 <tbody data-bind="foreach: timesheets">
                     <tr>
                         <td class="time"><input type="text" data-bind="value: time, name: function(data, event)  return 'time_'+ $index(); ()"></td>
                         <td class="rate"><input type="text" data-bind="value: rate, name: function(data, event)  return 'rate_'+ $index(); ()"></td>
                         <td class="amount"><input type="text" data-bind="value: amount, name: function(data, event)  return 'amount_'+ $index(); ()"></td>
                     </tr>
                 </tbody>
                 <tfoot>
                     <button type="button" data-bind="$root.addTimesheet">Add timesheet</button>
                 </tfoot>
            </table> 
   

            <table class="table item">
                <thead>
                    <tr>
                        <th>Qty</th>
                        <th>Price</th>
                        <th>Amount</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach: items">
                    <tr>
                        <td class="qty"><input type="text" data-bind="value: quantity, name: function(data, event)  return 'qty_'+ $index(); ()"></td>
                        <td class="price"><input type="text" data-bind="value: price, name: function(data, event)  return 'price_'+ $index(); ()"></td>
                        <td class="amount"><input type="text" data-bind="value: amount, name: function(data, event)  return 'amount_'+ $index(); ()"></td>
                    </tr>
                </tbody>
                <tfoot>
                    <button type="button" data-bind="$root.addItem">Add item</button>
                </tfoot>
            </table> 
   
            <div>
               <label for="subtotal">Subtotal</label>
               <input id="subtotal" data-bind="value: subtotal" type="text" name="subtotal" readonly>
            </div>
   
        </form>
    </body>
</html>

JS代码:

function getNumberFromFormattedValue(value) 
    if (value != '' && value !== null) 
        return value.toString().replace(/[^0-9.]*/g,'');

    
    return value;


function BillTimesheetViewModel(item) 
     var self = this;
     self.time = ko.observable(item.time);
     self.rate = ko.observable(item.rate);
     self.total = ko.observable(item.amount);
     self.amount = ko.computed(
         read: function()
             var time = getNumberFromFormattedValue(self.time());
             if (time !== null)  time = parseFloat(time); 
             var rate = getNumberFromFormattedValue(self.rate());
             if (rate !== null)  rate = parseFloat(rate); 
             if (!(isNaN(time) || isNaN(rate) || time === null || rate === null || time === '' || rate === '')) 
                 var newValue = time * rate;
                 self.total(newValue);
                 return newValue;
             
             // Don't change the value
         ,
         write: function(value)
             var newValue = getNumberFromFormattedValue(value);
             if (newValue !== null)  newValue = parseFloat(newValue); 
             var time = getNumberFromFormattedValue(self.time());
             if (time !== null)  parseFloat(time); 
             if (!(isNaN(newValue) || isNaN(time) || newValue === null || time === null || newValue === '' || time === '')) 
                 self.rate( newValue / time );
             
             self.total(value);
         ,
         owner: self
     );
     self.time.subscribe(function()
        if (self.time() === '' || self.time() === null || self.rate() === '' || self.rate() === null) 
            self.total('');
            self.amount('');
         else 
                var time = self.time();
                var rate = self.rate();
            if (time !== '' && time !== null && rate !== '' && rate !== null) 
                    var total = time * rate; 
                    self.amount(total);
                self.total(total);
            
         
     );
         
     self.amount(item.amount);




function BillItemViewModel(item) 
     var self = this;
     self.quantity = ko.observable(item.quantity);
     self.price = ko.observable(item.price);
         self.total = ko.observable(item.amount);
     self.amount = ko.computed(
         read: function()
             var quantity = getNumberFromFormattedValue(self.quantity());
             if (quantity !== null)  quantity = parseFloat(quantity); 
             var price = getNumberFromFormattedValue(self.price());
             if (price !== null)  price = parseFloat(price); 
             if (!(isNaN(quantity) || isNaN(price) || quantity === null || price === null || quantity === '' || price === '')) 
                 var newValue = quantity * price;
                 self.total(newValue);
                 return newValue;
             
             // Don't change the value
         ,
         write: function(value)
             var newValue = getNumberFromFormattedValue(value);
             if (newValue !== null)  newValue = parseFloat(newValue); 
             var quantity = getNumberFromFormattedValue(self.quantity());
             if (quantity !== null)  parseFloat(quantity); 
             if (!(isNaN(newValue) || isNaN(quantity) || newValue === null || quantity === null || newValue === '' || quantity === '')) 
                 self.price( newValue / quantity );
             
             self.total(value);
         ,
         owner: self
     );
     self.quantity.subscribe(function()
        if (self.quantity() === '' || self.quantity() === null || self.price() === '' || self.price() === null) 
            self.total('');
            self.amount('');
         else 

           var quantity = self.quantity();
           var price = self.price();
           if (quantity !== '' && quantity !== null && price !== '' && price !== null) 
             var total = quantity * price; 
             self.amount(total);
             self.total(total);
           
       
     );

     self.amount(item.amount);


function BillViewModel() 
     var self = this;
     self.timesheets = ko.observableArray([]);
     self.items = ko.observableArray([]);
     self.subtotal = ko.computed(function()
         var total = 0;
         for(var i = 0; i < this.timesheets().length; i++)
         
             var totalLine = this.timesheets()[i].total();
             total += parseFloat((totalLine != '' && totalLine !== null && !isNaN(totalLine) ? totalLine: 0));

        
         

                     for(var i = 0; i < this.items().length; i++)
         
             var totalLine = this.items()[i].total();
             total += parseFloat((totalLine != '' && totalLine !== null && !isNaN(totalLine) ? totalLine : 0));
        
         
         return total;
     , self);

     
     self.addTimesheet = function(item) 
        var timesheet = new BillTimesheetViewModel(
            time: item.time,
            rate: item.rate,
            amount: item.amount,
        );

        self.timesheets.push(timesheet);

             ;
     
     self.addItem = function(item)

        var item = new BillItemViewModel(
            quantity: item.quantity,
            price: item.price,
            amount: item.amount,
        );
        
        self.items.push(item);
           ;
     
        self.addTimesheet(
        time: 2,
      rate: 50,
      amount: 100
    );     
    
    self.addItem(
        quantity: 3,
      price: 75,
      amount: 125
    );
    

ko.applyBindings(new BillViewModel(), document.getElementById("billForm"));

见JsFiddle

【讨论】:

以上是关于使用写入函数时,Knockout JS 可写计算值不会触发另一个计算值的主要内容,如果未能解决你的问题,请参考以下文章

仅从 Knockout ViewModel 复制可写项目

在 Knockout.js 中单击按钮时检查选择控件是不是具有值

Knockout js - 如何在两个 javascript 文件之间传递值

在函数中使用Knockout push会返回错误

使用 knockout-kendo.js 和breeze.js 时不显示DropDownList 值

多个布尔值上的 Knockout.js“如果绑定”