I have the folowing table in my view:
<table border="0" id="tbl">
<tr class="te">
<th>DATE</th>
<th>METHOD</th>
<th>DEPOSIT</th>
<th>WITHDRAWAL</th>
<th>MEMO</th>
</tr>
<!-- ko foreach: account -->
<tr>
<td><span data-bind="text: transactionDate"></span></td>
<td><span data-bind="text: tranType"></span> </td>
<td><span data-bind="text: deposit"></span></td>
<td><span data-bind="text: withdrawal"></span> </td>
<td></td>
</tr>
<!-- /ko -->
<tr class="last">
<td> </td>
<td> </td>
<td>***TOTAL DEPOSIT --- SHOULD GO HERE***</td>
<td>***TOTAL WITHDRAWAL --- SHOULD GO HERE***</td>
<td>""</td>
</tr>
</table>
See below my Account Knockout object:
var Account = function (data) {
this.transactionDate = ko.observable(data.transactionDate);
this.payorPayee = ko.observable(data.payorPayee);
this.amount = ko.observable(data.amount);
this.isDebit = ko.observable(data.isDebit);
this.tranType = ko.observable(data.tranType);
this.deposit = ko.pureComputed(function () {
//some code
});
this.withdrawal = ko.pureComputed(function () {
//some code
});
}
As you can see, I loop through the account object and display the information on a table.
the JSON with the Account info looks like this:
[{
"transactionDate": "1/1/2016",
"payorPayee": "AAAAA",
"amount": "111",
"isDebit": false,
"tranType": "qqqq"
}, {
"transactionDate": "1/1/2016",
"payorPayee": "BBBBB",
"amount": "222",
"isDebit": false,
"tranType": "wwww"
}, {
"transactionDate": "1/1/2016",
"payorPayee": "CCCCC",
"amount": "333",
"isDebit": false,
"tranType": "eeee"
}]
What I need to do is loop through the rows, sum the deposit fields and display the total on the Total Deposit field (last 'tr'), then repeat the process for the Withdrawal fields.
I doubt it if it makes more sense to do it using Jquery on page load, or using Knockout.
Any idea would be appreciated.
Just like what you did in your deposit and withdrawal computed, you should also make a computed observable in your viewmodel for the total deposit and total withdrawal. You just need to loop through your account array and extract the sum of the deposits and withdrawals.
for example:
this.totalDeposit = ko.computed(function(){
var sum = 0;
//i don't know if account is observableArray or just plain array
this.account.forEach(function(account){
sum += Number(account.deposit());
});
return sum;
});
And so on and so forth, just create another computed for totalWithdrawal.
Related
Hey im working on a project and i can't seem to get the hang of this. I want to loop through my nested array object "products" so that i can display it all and not just the last index.
// jquery getting our json order data from firebase
$.get("http://localhost:8888/orderslist", (data) => {
// i is for the index of the array of orders
let i = 0;
//for each loop through our array list
$.each(data, function () {
//console.log(data)
//console.log(i);
// is how we arrange the data and show it to the frontpage
$(`<table id = order_table_layout>
<tr>
<th>Customer</th>
<th>Date</th>
<th>Time</th>
<th>Total</th>
<th>Order</th>
<th>Order Status</th>
</tr>
<tr>
<td>${data[i].customer_name}</td>
<td>${data[i].date}</td>
<td>${data[i].time}</td>
<td>${data[i].total} Kr.</td>
<td>
${data[i].products[i].name}
${data[i].products[i].price} Kr.
</td>
<td>
</td>
</tr>
</table>`
).appendTo("#frontpage_new_ordertable");
// counts 1 up for each loop to go through list
i++;
//console.log(i);
});
});
Edit:
An example of the json data I'm working with look like this:
[
{
id: "4WQITi6aXvQJsKilBMns",
customer_name: "Susanne",
date: "22-12-2002",
time: "12:43:19",
total: 222,
products: [
{ name: "product name", price: 100 },
{ name: "product name2", price: 20 }
]
There's a couple of issues in your code. Firstly you're creating a brand new table for every object in the data array. It makes far more sense to instead create a new row in the table for each item.
Also, it appears that you want to loop through the child products array. As such you need an inner loop to create the HTML string for those elements outside of the template literal.
However it's worth noting that it's not good practice to have that much HTML in your JS. A better approach is to have a hidden template tr in your HTML which you can clone, update with the data from the data array, then append to the DOM in the tbody of the table.
With that said, try this:
//$.get("http://localhost:8888/orderslist", (data) => {
// mock response:
let data = [{id:"4WQITi6aXvQJsKilBMns",customer_name:"Susanne",date:"22-12-2002",time:"12:43:19",total:222,products:[{name:"product name",price:100},{name:"product name2",price:20}]},{id:"asjdkjk21ijjjew",customer_name:"Foo Bar",date:"10-05-2020",time:"16:46:16",total:68,products:[{name:"Lorem ipsum",price:50},{name:"Fizz buzz",price:18}]}];
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
$clone.find('.customer-name').text(item.customer_name);
$clone.find('.date').text(item.date);
$clone.find('.time').text(item.time);
$clone.find('.total').text(item.total + ' Kr.');
let products = item.products.map(prod => `${prod.name}: ${prod.price} Kr.`);
$clone.find('.products').html(products.join('<br />'));
return $clone;
});
$("#frontpage_new_ordertable tbody").append(rows);
//});
tfoot {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="frontpage_new_ordertable">
<tbody>
<tr>
<th>Customer</th>
<th>Date</th>
<th>Time</th>
<th>Total</th>
<th>Order</th>
<th>Order Status</th>
</tr>
</tbody>
<tfoot>
<tr>
<td class="customer-name"></td>
<td class="date"></td>
<td class="time"></td>
<td class="total"></td>
<td class="products"></td>
<td></td>
</tr>
</tfoot>
</table>
<td>${data[i].total} Kr.</td>
<td>
${data[i].products[i].name}
${data[i].products[i].price} Kr.
maybe that's what's wrong?
is the number of the order similar to the number of product in products array?
Might be the end of the work day, might be the lack of coffee, but I just can't seem to come up with the correct idea for this.
I have an array with +- 180 rows of order data.
Now I would like to calculate the totals of the numbers to show them in the footer of my grid.
Can someone help me out?
Array looks something like this, yet much bigger:
[{title: 'title', description: 'description', someNumber: 'someNumber', otherNumber: 'otherNumber'},{title: 'title', description: 'description', someNumber: 'someNumber', otherNumber: 'otherNumber'}]
Here is my code:
var totalsRow = [];
this.someService.data().subscribe(newData => {
this.data = newData;
for (let row of newData) {
for (let col of this.cols) { //this.cols are all the fields in my array
if (this.isNumber(row[col.field])) { //This generates row[someNumber]
//Calculate total and push field to totalsRow
//So that I can loop through it in my HTML file.
} else {
//Push empty field to totalsRow
}
}
}
});
this.cols looks something like this:
[{field: 'title', header: 'Nice header title', visible: true, isNumber: false},
{field: 'someNumber', header: 'Nice header title', visible: true, isNumber: true}]
This is my HTML. I want to add a TFOOT with the totals of each field which is a number:
<thead>
<tr>
<ng-container *ngFor="let col of cols">
<th *ngIf="col.visible" class="text-uppercase small-label" style="white-space: nowrap;">
<div style="resize:both; overflow: auto; display:block">
{{col.header}}
</div>
</th>
</ng-container>
</tr>
</thead>
<tbody>
<tr *ngFor="let rowData of data" (click)="onRowSelect(rowData)">
<ng-container *ngFor="let col of cols">
<td *ngIf="col.visible" [ngClass]="{'text-center': col.isCentral}">
<span>{{rowData[col.field]}}</span>
</td>
</ng-container>
</tr>
</tbody>
What I want to achieve:
<tfoot>
<tr *ngFor="let rowData of totalsRow">
<td *ngIf="col.visible">
<span>{{rowData[col.field]}}</span>
</td>
</tr>
</tfoot>
Thanks in advance.
You have to bring out the totalsRow definition. If you declare it inside the subscribe the scope stays there and you can't access it.
...
var totalsRow = [];
this.someService.data().subscribe(newData => {
...
If the key is always someNumber
totalsRow = 0;
...
newData.forEach(datum => {
if(datum.someNumber){
this.totalsRow += datum.someNumber;
}
})
Ah well, found a solution myself.
I don't know if this would be the most neat answer, but it works for now:
I added a property total to my array of columns, and calculated the total per column.
for (let col of this.cols) {
if (col.isNumber) {
col.total = newData.reduce((a, b) => (a + b[col.field]), 0);
}
<tfoot>
<tr>
<ng-container *ngFor="let col of cols">
<td *ngIf="col.visible">
<span *ngIf="col.isNumber">{{col.total | currency: 'EUR'}}</span>
</td>
</ng-container>
</tr>
</tfoot>
I am working on a web application using Asp.Net Mvc in which I used knockout Js to retrieve and send data to Html View by performing some operation on the data.
For example I have the following data in my array named datainput
Grade Type Volume Price
A Buy 1000 10
A Sell 1200 10
B Sell 100 100
I am computing an array which such that when displaying on a html page it just takes one distinct value of Grade and groups the data under that grade like
A
Buy 1000 10
Sell 1200 10
B
Sell 100 100
Used the above function as follows
self.uniqueSelect = ko.dependentObservable(function () {
var Transactiontypes = ko.utils.arrayMap(self.datainput(), function (item) { return item.Grade })
return ko.utils.arrayGetDistinctValues(Transactiontypes);
});
I dont think there is an issue with above script, not sure though but I went ahead and used the data obtained from the above script and tried to populate it to html as follows
<!--ko foreach: uniqueSelect-->
<tr>
<td class="clientproductHeader" data-bind="text: $data"></td>
<td class="clientproductHeader" colspan="10"></td>
</tr>
<tbody data-bind="foreach: ko.observableArray($root.datainput()).extendsdistinct('Grade').index.Grade()[$data]">
<tr>
<script type="text/html" id="read-template">
<td data-bind="text: Type"></td>
<td data-bind="text: Volume"></td>
<td data-bind="text: (typeof Price() === 'number') ? Price().toFixed(2) : '' "></td>
</script>
</tr>
</tbody>
<!--/ko-->
</table>
The above script for some reasons display duplicate data like if a Grade is repeated two times as in the example data it displays the same data of that grade two times and if it repeated three times then displays it thrice and so on.
I thought the error was with <!--ko foreach: uniqueSelect--> because it just loops the data depending on the number of similar grades.
results on html page are like
A
Buy 1000 10
Sell 1200 10
B
Sell 100 100
A
Buy 1000 10
Sell 1200 10
In the above data Grade A is repeated twice so all the data with grade A is repeated twice where as grade B has just one entry and so, it happened only once
screen of data I received
No clue how to deal with this
I went through and wrote a version that does what I think you are trying to accomplish. It uses the method PÄ“teris described. See it in action.
You want to keep the view code as simple as possible, and also be careful about reaching back into $root. If you are positive you would like to do it with a filter, make one that does a similar grouping as below, and then iterate over each of its entries/children. Also, try to avoid declaring observables in your views.
Finally, you could combine groupByGrade() and getGrades() into a single function that returns an object with a property for each of the results. It would save an iteration cycle.
The view:
<tr>
<td class="clientproductHeader" data-bind="text: 'Grade ' + $data + ' (' + $root.log()[$data].length + ' trades)'"></td>
<td class="clientproductHeader" colspan="10"></td>
</tr>
<tbody data-bind="foreach: $root.log()[$data]">
<tr>
<td data-bind="text: type"></td>
<td data-bind="text: volume"></td>
<td data-bind="text: (typeof price === 'number') ? price.toFixed(2) : '' "></td>
<td class="actioncells">
<a class="btn btn-success" title="Edit" data-bind="click: $root.edit">Edit</a>
</td>
<td class="actioncells">
<a class="btn btn-danger" title="Delete " data-bind="click: $root.remove">Delete</a>
</td>
</tr>
</tbody>
<!--/ko-->
And the JavaScript:
function testViewModel() {
// Underscore and Lo-dash both have a function to group
// an object by key. That's what you want. Here is a
// really simple version.
function groupByGrade(data) {
return data.reduce(function(last, next) {
if (last[next.grade]) {
last[next.grade].push(next);
} else {
last[next.grade] = [next];
}
return last;
}, {});
}
// Get a unique list of the grades in the data to iterate over
function getGrades(data) {
return data.reduce(function(last, next) {
return !!~last.indexOf(next.grade) ? last : last + next.grade;
}, '').split('');
}
var rawData = [
{
grade: 'A',
type: 'Buy',
volume: 1000,
price: 10
}, {
grade: 'A',
type: 'Sell',
volume: 1200,
price: 10
}, {
grade: 'B',
type: 'Sell',
volume: 100,
price: 100
}
];
console.log(getGrades(rawData));
this.log = ko.observable(groupByGrade(rawData));
this.grades = ko.observableArray(getGrades(rawData));
}
ko.applyBindings(new testViewModel());
I am getting some data from an external service and I am trying to show it on a table. The problem is that the data I get from service will be with dynamic columns, some times there will be 5 column another time 8. I don't know how I could handle it in ng-repeat. and using things like ng-grid won't be a good solution I think as there will be only 10 rows to display. for this If I use any external solution that will be a overhead. Is there any angular method to achieve this? if not what is the best option for this small data.
Note: Column names will also be dynamic
My code
<div ng-app='myApp' ng-controller="MainCtrl">
<div ng-repeat="prdElement in packageElement track by $index" class="package-grid">
<table class="hovertable">
<thead>
<tr>
<th>Line #</th>
<th>Quantity in Plt</th>
<th>Allready Packed</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="data in prdElement.Data" ng-init="data.newquantity = 0">
<td>{{data.itemId}}</td>
<td>
{{data.quantity}}
</td>
<td>{{data.packed}}</td>
</tr>
</tbody>
</table>
</div>
angular.module('myApp', []).controller('MainCtrl', function ($scope) {
var counter = 0;
$scope.packageElement = [{
name: counter,
show: true,
Data: [{
name: 'item 1',
itemId: '284307',
quantity: '100',
packed: 0
}, {
name: 'item 2',
itemId: '284308',
quantity: '200',
packed: 0
}]
}];
});
Will there be the same number of columns for all data items? If so, I think you can do this.
1. Define a function on your scope that gives you the object keys:
$scope.keys = function(obj) {
var key;
var keys = [];
for (key in obj) {
if (key === "$$hashKey") break; //angular adds new keys to the object
if (obj.hasOwnProperty(key)) keys.push(key);
}
return keys;
}
2. use a repeater on the table header (if the objects can have different properties, you need to find the object with the highest number of properties/columns)
<th ng-repeat="key in keys( prdElement.Data[0] )">{{key}}</th>
3. use a repeater on the table cell
<td ng-repeat="key in keys( prdElement.Data[0] )">{{ data[key] }}</td>
I have a table and I need to get values for each row (in array), something like:
[{1,'test'}, {2,'another test'}, {3, 'nothing'}], but the only I can get is:
["1 test", "2 another test","3 nothing"]. I need to send this values by ajax as json object ... and create another table - thus I need an array of values ...
var table = $("table");
var rowsAll = table.find("tbody tr");
rowsAll.each(function(i,l) {
var rows = [];
$(this).each(function(t,l){
rows[rows.length] = cells[t]= $(this).text();
return rows; //returns rows, but I need cells of each row
}).serialize();//or get()
});
the html is something like this:
<table id="deal">
<thead>
<tr>
<th> num </th>
<th> string </th>
</tr>
</thead>
<tbody>
<tr> <td> 1 </td> <td> test </td> </tr>
<tr> <td> 2 </td> <td> another test </td> </tr>
<tr> <td> 3 </td> <td> nothing </td> </tr>
</tbody>
</table>
I really need help ...
This form of data makes no sense as it's very hard to read with javascript:
[{1,'test'}, {2,'another test'}, {3, 'nothing'}]
I'm going to assume that what you want is this:
{"1": "test", "2": "another test", "3": "nothing"}
To get that second form, you could use this code:
var results = {};
$("#deal tbody tr").each(function() {
var cols = $(this).find("td");
results[$.trim(cols.eq(0).text())] = $.trim(cols.eq(1).text());
});
Working example here: http://jsfiddle.net/jfriend00/WjgrC/.