使用写入函数时,Knockout JS 可写计算值不会触发另一个计算值
Knockout JS writable computed value doesn't fire another computed value when writing function is used
我用了几个月的 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
任何帮助将不胜感激
我找到了如何计算小计。秘诀:在每一行的金额旁边放置一个单独的可观察对象,并在更新金额时更新此值。接下来,小计应该计算那些隐藏的可观察量的总和,而不是计算的数量。
感谢 Josh 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"));
我用了几个月的 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
任何帮助将不胜感激
我找到了如何计算小计。秘诀:在每一行的金额旁边放置一个单独的可观察对象,并在更新金额时更新此值。接下来,小计应该计算那些隐藏的可观察量的总和,而不是计算的数量。
感谢 Josh 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"));