Calculate totals of Angular array - javascript

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>

Related

how to loop a nested array object in javascript with jquery

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?

Angular6 multiple ngFor for single row - How to pass two values in one ngFor

In table head I have dynamic columns and search sorting functions. but for particular columns I need to disable sorting function.
In table
<thead>
<tr class="table-head">
<th *ngFor="let colName of reportColHeader; let i = index">
{{colName}}
<i [class]="sortIcons[colName]" (click)="toggleSort(colName, i)"></i>
</th>
</tr>
</thead>
In constructor I have passing values as array. Not from server its static only
this.reportColHeader = ['ID', 'LABEL 1', 'LABEL 2', 'More'];
so the result will be like below
<tr>
<th>ID <i></i></th>
<th>LABEL 1 <i></i></th>
<th>LABEL 2 <i></i></th>
<th>More <i></i></th>
</tr>
Here I want to disable (sorting function) from particular column. Not only last column or nth column.
My assumption is, is possible to have one more array like
this.reportColHeaderOptions = ['true', 'true', 'false', 'false'];
by this values can show or hide the <i></i>
So how can I pass this inside ngFor.
Note: here I can't use index since the index will change.
but I can follow this.reportColHeader and this.reportColHeaderOpions in same order.
Why can't you use index ? You can do this :
<th *ngFor="let colName of reportColHeader; let i = index">
{{colName}}
<i *ngIf="reportColHeaderOpions[i]=='true'"></i>
</th>

orderBy not working with pagination and filters

<table>
<thead>
<tr>
<th class="col-md-3" ng-click="sortDirection = !sortDirection">Created At</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="food in foods | filter:foodFilter | itemsPerPage:pageSize | orderBy:'created_at_date'">
<td class="col-md-"> {{food.created_at_date}} </td>
</tbody>
</table>
<dir-pagination-controls
max-size= 7
boundary-links="true">
</dir-pagination-controls>
This is only a snippet of my code but its too large to put up. Everything is working except only some of the created_at_date is in order. When I click on a different filter to add in or remove data depending on that filter, only some of it is entered into the correct place. My main question is: is there someway to sort all of the dates properly while still allowing the everything else function as well? All help is welcome, Thanks
(function () {
"use strict";
App.controller('foodsController', ['$scope'],
function($scope) {
$scope.sortDirection = true;
In your controller you can add the method to order the array before you loop over them.
Assuming your foods array has an array of objects, each with a key of created_at_date and a value:
App.controller('foodsController', function($scope) {
$scope.foods = [{
created_at_date: 6791234
}, {
created_at_date: 9837245
}, {
created_at_date: 1234755
}];
// create a method exposed to the scope for your template.
$scope.orderBy = function(key, array) {
// now you've received the array, you can sort it on the key in question.
var sorted = array.sort(function(a, b) {
return a[key] - b[key];
});
return sorted;
}
});
Now on your template, you have a method available to sort your values for you:
<table>
<thead>
<tr>
<th class="col-md-3" ng-click="sortDirection = !sortDirection">Created At</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="food in orderBy('created_at_date', foods) | filter:foodFilter | itemsPerPage:pageSize">
<td class="col-md-"> {{food.created_at_date}} </td>
</tr>
</tbody>
</table>
The orderBy method which we've created on your controller returns an array, but it's just sorted by the key that's sent in as the first argument of the function. The second argument is the original array you're trying to sort.
At least this way you can check if you remove all your other filters to see if it's ordered correctly, if then after you add them back in it changes it's because those filters are also changing the order.

How to sum a databound column using knockout or jquery?

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.

Bind dynamic columns to a table using angularJS

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>

Categories

Resources