使用Knockout.js将带有嵌套数组的JS对象扁平化为单个HTML表行

Flattening JS objects with nested arrays into a single HTML table row with Knockout.js

本文关键字:扁平化 对象 JS 单个 表行 HTML 数组 js Knockout 嵌套 使用      更新时间:2023-09-26

我有一个支付数据数组,看起来类似于:

[{
  amount: "$202.12",
  date: "10/13/2013",
  items: [{type: "Service", amount: "$190.00"}, {type: "Fee", amount: "12.12"}],
  status: "Paid"
},
// More of the same...
]

物品的type是任意的,每次付款都可能不同。

现在我想将数组中的每笔付款显示为表中的一行。但是,我希望items数组的元素在同一行上平展,这样表看起来就像

| Date       | Amount | Fee   | Service | Status |
|------------|--------|-------|---------|--------|
| 10/13/2013 | 202.12 | 12.02 | 190.00  | Paid   |
|------------|--------|-------|---------|--------|
| So on and so forth ....                        |

我使用knockout.js来生成这些表,但我无法弄清楚如何使用foreach绑定将对象平放成一行。

将每个项目type转换为支付对象的属性,如

var payment = {
  amount: "$202.12",
  date: "10/13/2013",
  items: [{type: "Service", amount: "$190.00"}, {type: "Fee", amount: "12.12"}],
  status: "Paid"
};
var flatPayment = {
  amount: payment.amount,
  status: payment.status,
  date: payment.date
};
for (var i = 0; i < payment.items.length; i++) 
  flatPayment[payment.items[i].type] = payment.items[i].amount;

可以工作,但是,我事先不知道项目类型,因此不能绑定每个表单元格的text

有什么建议吗?

首先在支付视图模型对象上为每个已知类型创建一个ko.computed。我

var PaymentType = function (data) {
    var self = this;
    self.type = ko.observable(data.type || '');
    self.amount = ko.observable(data.amount || 0);
    return self;
};
var Payment = function (data) {
    var self = this;
    self.types = ko.observableArray();
    self.amount = ko.observable(data.amoumt || 0);
    self.date = ko.observable(data.date || '');
    self.status = ko.observable(data.status || '');
    self.service = ko.computed(function () {
        var paymentType = ko.utils.arrayFirst(self.types(), function (item) {
            return item.type() === 'Service';
        });
        if (paymentType) {
            return paymentType.amount();
        }
        return 0;
    });
    self.fee = ko.computed(function () {
        var paymentType = ko.utils.arrayFirst(self.types(), function (item) {
            return item.type() === 'Fee';
        });
        if (paymentType) {
            return paymentType.amount();
        }
        return 0;
    });
    ko.utils.arrayForEach(data.items, function (item) {
        self.types.push(new PaymentType(item));
    });
    return self;
};
var vm = function () {
    var self = this;
    self.payments = ko.observableArray();
    ko.utils.arrayForEach(data, function (item) {
        self.payments.push(new Payment(item));
    });
    return self;
};

ko.applyBindings(new vm());

则可以引用Payment中的每个字段,它应该返回一个金额或一个0。

JSFiddle演示

这样做的一种方法是在数组中跟踪vm根目录中开发时未知的支付类型。这个数组可以在你的根虚拟机和你的支付行之间共享,用于绑定。

在下面的代码中,我有一个名为allPaymentTypes的数组,它在vm的根节点上公开,并且在每次付款时都公开。

在构建vm时,我通过将每个唯一的支付项添加到allPaymentTypes数组来处理每个支付,在每个支付上创建一个paymentAmountLook,并添加一个辅助函数来查找特定支付类型的每个支付金额。

var vm = function (payments) {
    var self = this;
    self.payments = payments;
    self.allPaymentTypes = [];
    ko.utils.arrayForEach(payments, processPayment);
    function processPayment(payment) {
        payment.allPaymentTypes = self.allPaymentTypes;
        payment.paymentAmountLookUp = {};
        ko.utils.arrayForEach(payment.items, function (paymentItem) {
            processPaymentItem(payment, paymentItem);
        });
        //Helper function to get the payment amount for a payment type
        //Will handle situations where a specific payment does not have a payment amount
        payment.getPaymentAmount = function (paymentType) {
            return payment.paymentAmountLookUp[paymentType] || '$0';
        };
    }
    function processPaymentItem(payment, paymentItem) {
        payment.paymentAmountLookUp[paymentItem.type] = paymentItem.amount;
        if (self.allPaymentTypes.indexOf(paymentItem.type) === -1) {
            self.allPaymentTypes.push(paymentItem.type);
        }
    }
    return self;
};

在下面的html标记中,我使用foreach绑定到Header和每个表行的allPaymentTypes数组,为每个动态支付类型动态创建th和td元素。

<table class="table">
    <thead>
        <tr>
            <th>Date</th>
            <th>Amount</th>
            <!-- ko foreach: allPaymentTypes -->
            <th data-bind="text: $data"></th>
            <!-- /ko -->
            <th>Status</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: payments">
        <tr>
            <td data-bind="text: date"></td>
            <td data-bind="text: amount"></td>
            <!-- ko foreach: allPaymentTypes -->
            <td data-bind="text: $parent.getPaymentAmount($data)"></td>
            <!-- /ko -->
            <td data-bind="text: status"></td>
        </tr>
    </tbody>
</table>

JSFiddle Demo

JSFiddle演示可观察属性和集合

仅供参考,我抄袭/改进了Nathan Fisher的文章