Reputation: 683
I have an array of payment data that looks similar to:
[{
amount: "$202.12",
date: "10/13/2013",
items: [{type: "Service", amount: "$190.00"}, {type: "Fee", amount: "12.12"}],
status: "Paid"
},
// More of the same...
]
The type
of the items are arbitrary and can differ for each payment.
Now I want to display each payment in the array as a row in a table. However, I want the elements of the items array to be flattened out on the same row, so that the table would look like
| Date | Amount | Fee | Service | Status |
|------------|--------|-------|---------|--------|
| 10/13/2013 | 202.12 | 12.02 | 190.00 | Paid |
|------------|--------|-------|---------|--------|
| So on and so forth .... |
I am using knockout.js to generate these tables, but I cannot figure out how to flatten the objects into a single row using the foreach
binding.
Turning each item type
into a property of the payment object like
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;
could work, but however, I do not know the item types beforehand and therefore cannot bind the text
of each table cell.
Any suggestions?
Upvotes: 1
Views: 1446
Reputation: 5762
A way to do this is to keep track of the payment types that are unknown at development time in the root of your vm in an array. This array can then be shared between your root vm and your payment rows to be used for binding.
In the following code I have an array called allPaymentTypes that is exposed at the root of the vm and each at each payment.
When building up the vm I process each payment by adding each unique payment item to the allPaymentTypes array, create a paymentAmountLook on each payment and add a helper function to find each payment amount for a specific payment type.
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;
};
In the following html markup I use foreach bindings to the allPaymentTypes array both in the Header and for each table row to dynamically create the th and td elements for each dynamic payment type.
<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 with observable properties and collections
FYI, I plagiarized/improved upon Nathan Fisher's post.
Upvotes: 0
Reputation: 7941
Start out by creating a ko.computed
for each on the known types on a payment viewmodel object.
I
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());
then you can reference each field off the Payment and it should either return an amount or a 0.
Upvotes: 1