Knockout js计算出运行平衡无限循环

时间:2013-10-20 23:01:35

标签: knockout.js computed-observable

有一组可观察的银行交易,其中包含其他金额。

我试图将运行平衡保持为可计算但我似乎陷入无限循环和各种各样的不良。

这是我能想到的最简单的小提琴 - http://jsfiddle.net/Nnyxx/2/

JS:

var transactions = [{Amount: -100}, {Amount: 125}, {Amount: 10}, {Amount: 25}, {Amount: -125}, {Amount: 400}];

var ViewModel = function() {
    this.OpeningBalance = ko.observable(1000);
    this.RunningBalance = ko.observable(this.OpeningBalance());
    this.ClosingBalance = ko.observable(this.RunningBalance());

    this.UpdateRunningBalance = ko.computed({
        read: function() {
            return this.RunningBalance();
        },
        write: function(amount) {
            this.RunningBalance(this.RunningBalance() + amount);

            return amount;
        }
    }, this);

    this.Transactions = ko.observableArray(ko.mapping.fromJS(transactions)());
}

var model = new ViewModel();

ko.applyBindings(model);

HTML:

<table>
    <thead>
        <tr>
            <th width="150"></th>
            <th width="150">Money Out</th>
            <th width="150">Money In</th>
            <th>Balance</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="3"><strong>Opening Balance</strong></td>
            <th>
                <span data-bind="text: OpeningBalance"></span>
            </th>
        </tr>
        <!-- ko foreach: Transactions -->
        <tr>
            <td></td>
            <!-- ko if: Amount() < 0 -->
            <td data-bind="text: Amount"></td>
            <td></td>
            <!-- /ko -->
            <!-- ko if: Amount() > 0 -->
            <td></td>
            <td data-bind="text: Amount"></td>
            <!-- /ko -->
            <td>
                <span data-bind="text: $root.UpdateRunningBalance(Amount())"></span>
            </td>
        </tr>
        <!-- /ko -->
        <tr>
            <td colspan="3"><strong>Closing Balance</strong></td>
            <th>
                <span data-bind="text: ClosingBalance"></span>
            </th>
        </tr>
    </tbody>
</table>

它仍然不完整,因为我最终绕圈子讨论如何显示和重新计算期初余额。

运行余额需要是可观察的,因此如果期初余额或交易发生变化,它将重新计算。

结算余额也需要是最终的运行余额。

2 个答案:

答案 0 :(得分:3)

我认为你在这里缺少必要的东西。

Knockout中没有运行平衡这样的东西。 屏幕上显示的每个数据点必须绑定到视图模型中的实时值。

没有“仅在构建当前行时存在”的值,类似于PHP在循环期间构建HTML时所做的事情。如果您需要10个“运行值”行,则在视图模型中有10个不同的值。

比较一下:

function Transaction(amount, previousBalance) {
    this.amount = amount;
    this.balance = previousBalance + amount;
}

function Account(openingBalance, transactions) {
    var self = this;

    // properties
    self.openingBalance = openingBalance;
    self.transactions = ko.observableArray();
    self.closingBalance = ko.computed(function () {
        var transactions = self.transactions(),
            lastTransaction = transactions[transactions.length - 1];
        return lastTransaction ? lastTransaction.balance : openingBalance;
    });

    // methods
    self.addTransaction = function (amount) {
        var previousBalance  = self.closingBalance();
        self.transactions.push(new Transaction(amount, previousBalance));
    };

    // init    
    ko.utils.arrayForEach(transactions, function (t) {
        self.addTransaction(t.Amount);
    });
}

var transactions = [{Amount: -100}, {Amount: 125}, {Amount: 10}, 
                    {Amount: 25}, {Amount: -125}, {Amount: 400}];

ko.applyBindings(new Account(1000, transactions));

(请点击此处http://jsfiddle.net/Nnyxx/5/

请注意每个表行在视图模型中具有实际存在的当前运行值的相应值。

Knockout不是客户端页面处理库。它是一个数据绑定库。它只能显示页面生命周期中确实存在的数据。

此外,并非Knockout中的每个值都需要包含在一个可观察的值中,只有您期望在页面生命周期内更改的值。 Account.openingBalanceTansaction.amount是不太可能更改的属性,可以将其解包。

不相关,但尝试遵循JS约定的约定:仅对构造函数和构造函数使用PascalCaseNames,所有其他变量(成员函数,属性,局部变量)将获得camelCaseNames

还有一件事 - 如果你使用货币价值,请注意IEEE 754浮点运算的缺陷。在其他任何地方使用它们之前,所有操作都应该正确舍入。如果您在没有任何中间舍入的情况下拖动运行值(如上面的代码示例中所示),则最终可能会因内部数字表示问题而关闭值。

答案 1 :(得分:1)

您也可以使用普通函数而不是计算的可观察函数来执行此操作。

请参阅this fiddle

<强> JS:

var transactions = [{
    Amount: -100
}, {
    Amount: 125
}, {
    Amount: 10
}, {
    Amount: 25
}, {
    Amount: -125
}, {
    Amount: 400
}];

var ViewModel = function () {
    var self = this;
    self.Transactions = ko.observableArray(ko.mapping.fromJS(transactions)());
    self.OpeningBalance = ko.observable(1000);
    self.runningBalance = self.OpeningBalance();

    self.RunningBalance = function (index) {
        return ko.computed(function () {
            var total = parseInt(self.OpeningBalance());
            for (i = 0; i <= index; i++) {
                total += parseInt(self.Transactions()[i].Amount());
            }
            return total;
        });
    };
    self.ClosingBalance = ko.computed(function () {
        var total = parseInt(self.OpeningBalance());
        ko.utils.arrayForEach(self.Transactions(), function (item) {
            total += parseInt(item.Amount());
        });
        return total
    });

}

var model = new ViewModel();

ko.applyBindings(model);

<强> HTML

Opening balance
<input data-bind="value: OpeningBalance" />
<table>
    <thead>
        <tr>
            <th width="150"></th>
            <th width="150">Money Out</th>
            <th width="150">Money In</th>
            <th>Balance</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="3"><strong>Opening Balance</strong>

            </td>
            <th>    <span data-bind="text: OpeningBalance"></span>

            </th>
        </tr>
        <!-- ko foreach: Transactions -->
        <tr>
            <td></td>
            <!-- ko if: Amount() < 0 -->
            <td data-bind="text: Amount"></td>
            <td></td>
            <!-- /ko -->
            <!-- ko if: Amount()> 0 -->
            <td></td>
            <td data-bind="text: Amount"></td>
            <!-- /ko -->
            <td>    <span data-bind="text: $root.RunningBalance($index())"></span>

            </td>
        </tr>
        <!-- /ko -->
        <tr>
            <td colspan="3"><strong>Closing Balance</strong>

            </td>
            <th>    <span data-bind="text: ClosingBalance"></span>

            </th>
        </tr>
    </tbody>
</table>