AngularJS: Summing nested objects from multiple objects - angularjs

Based in the following table I need to get the total of all subtotals.
I have tried to use the same sumByKey filter but I does't work.
Plus, the sum of all subtotals must be based on the result of the filter, it means, If we have two results (two categories) the sum of subtotals must be based on those objects. Any idea?
html
<table border="1">
<thead>
<tr>
<th>#</th>
<th>Category</th>
<th>Products with quantities</th>
<th>Subtotal of quantities</th>
</tr>
</thead>
<tbody align="center">
<tr data-ng-repeat="category in categories | filter:search">
<td>{{$index+1}}</td>
<td>{{category.name}}</td>
<td>{{category.products}}</td>
<td>{{category.products | sumByKey:'quantity'}}</td>
</tr>
</tbody>
<thead align="right">
<tr>
<td colspan="3"><strong>Total</strong></td>
<td></td>
</tr>
</thead>
</table>
angularjs
var app = angular.module("app", []);
app.controller("controllerApp", function($scope, $http){
$http.get("categories.json").success(function(data) {
$scope.categories = data;
});
});
app.filter('sumByKey', function () {
return function (data, key) {
if (typeof (data) === 'undefined' || typeof (key) === 'undefined') {
return 0;
}
var sum = 0;
for (var i = data.length - 1; i >= 0; i--) {
sum += parseInt(data[i][key]);
}
return sum;
}
});

May not be Angular solution. But you can also get total by pure JavaScript.
By keeping a total $scope varaible like this inside your controller
$scope.total = getTotal(data);
function getTotal(data){
var total = 0;
data.forEach(function(item){
item.products.forEach(function(product){
total += product.quantity;
})
});
return total;
}
Here is the updated Plunker.

It can be done with angular also.
Example : http://plnkr.co/edit/zhTtoOjW7J1oumktlvFP?p=preview
HTML:
<table border="1">
<thead>
<tr>
<th>#</th>
<th>Category</th>
<th>Products with quantities</th>
<th>Subtotal of quantities</th>
</tr>
</thead>
<tbody align="center">
<tr data-ng-repeat="category in filterValues=(categories | filter:search)">
<td>{{$index+1}}</td>
<td>{{category.name}}</td>
<td>{{category.products}}</td>
<td>{{category.products | sumByKey:'quantity'}}</td>
</tr>
</tbody>
<thead align="right">
<tr>
<td colspan="3"><strong>Total</strong></td>
<td>{{filterValues | sumOfValue:'quantity'}}</td>
</tr>
</thead>
</table>
JS:
// Code goes here
var app = angular.module("app", []);
app.controller("controllerApp", function($scope, $http) {
$http.get("categories.json").success(function(data) {
$scope.categories = data;
});
});
app.filter('sumByKey', function() {
return function(data, key) {
if (typeof(data) === 'undefined' || typeof(key) === 'undefined') {
return 0;
}
var sum = 0;
for (var i = data.length - 1; i >= 0; i--) {
sum += parseInt(data[i][key]);
}
return sum;
}
});
app.filter('sumOfValue', function() {
return function(data, key) {
if (angular.isUndefined(data) || angular.isUndefined(key)) {
return 0;
}
var sum = 0;
for (var i = 0; i < data.length; i++) {
var value = data[i];
if (!angular.isUndefined(value)) {
for (var j = 0; j < value.products.length; j++) {
sum += parseInt(value.products[j][key]);
}
}
}
return sum;
}
});

Related

How to Send multiple values from Asp.Net WebApi to Angularjs Controller?

WebApi Controller.. How to send this value to Angularjs controller (bill = q.TotalBill;)? I have send this (return Ok(gridlist);) into JSON form into angularjs controller
public static double bill; // this is static variable on top of a class
[System.Web.Http.Route("api/Products/gridpro/{id}")]
public IHttpActionResult GetGrid(int id)
{
var q = db.products.Find(id);
if (q != null)
{
var check = gridlist.Where(x => x.Id == id).FirstOrDefault();
if (check != null)
{
check.ProductQty += 1;
check.TotalAmount = check.ProductQty * check.ProductRate;
}
else
{
q.ProductQty = 1;
q.TotalAmount = q.ProductQty * q.ProductRate;
gridlist.Add(q);
}
q.TotalBill = gridlist.Sum(x => x.TotalAmount);
foreach (var item in gridlist)
{
item.TotalBill = q.TotalBill;
}
bill = q.TotalBill; //How to send this value to Angularjs controller
return Ok(gridlist);
}
else
{
return NotFound();
}
}
Anagularjs Code: I see all the data into the HTML using this ($scope.gridproducts) but I want to show (bill = q.TotalBill;) this single value into HTML code
$scope.OnProChange = function (Pro) {
var id = Pro.Id;
$http.get("/api/Products/gridpro/" + id).then(function (response) {
console.log(JSON.stringify(response.data))
$scope.gridproducts = response.data;
})
}
HTML code:How I can show total bill value I use {{gridproducts.TotalBill}} this but nothing works.
<tbody>
<tr ng-repeat="item in gridproducts">
<td>
<a class="delete"><i class="fa fa-times-circle-o"></i></a>
</td>
<td class="name">{{item.ProductName}}</td>
<td>{{item.ProductRate}}</td>
<td>
<input class="form-control qty" style="width:50px" onchange="UpdatePurchaseItem('36',this.value)" value="{{item.ProductQty}}">
</td>
<td>{{item.TotalAmount}}</td>
</tr>
<tr></tr>
<tfoot>
<tr>
<th colspan="2"></th>
<th colspan="2"><b>Total</b></th>
<th><b>{{gridproducts.TotalBill}}</b></th>
</tr>
<tr>
<th colspan="2"><b></b></th>
<th colspan="2"><b>Total Items</b></th>
<th><b>25</b></th>
</tr>
</tfoot>
</tbody>
if you want send multiple values to angularjs , you may create complex type for that.
for example,
in c# code
Public Class GridDataModel<T>
{
public T ItemList{get;set;}
public int TotalBill{get;set}
}
then when you return data to js
var gridData=new GridDataModel<products>()
{
ItemList=gridlist,
TotalBill=q.TotalBill
}
return Ok(gridData);
after doing this , you can create another propert for js scope
$http.get("/api/Products/gridpro/" + id).then(function (response) {
console.log(JSON.stringify(response.data))
$scope.gridproducts = response.data.ItemList;
$scope.totalBill = response.data.TotalBill;
})

Get filtered value from view to controller

I use ng-repeat to show my array of objects. One of the attributes is video duration, and I used filter to directly show sum of all meta_durations. here is filter
app.filter('sumProduct', function() {
return function (input) {
var i = input instanceof Array ? input.length : 0;
var a = arguments.length;
if (a === 1 || i === 0)
return i;
var keys = [];
while (a-- > 1) {
var key = arguments[a].split('.');
var property = getNestedPropertyByKey(input[0], key);
if (isNaN(property))
throw 'filter sumProduct can count only numeric values';
keys.push(key);
}
var total = 0;
while (i--) {
var product = 1;
for (var k = 0; k < keys.length; k++)
product *= getNestedPropertyByKey(input[i], keys[k]);
total += product;
}
return total;
function getNestedPropertyByKey(data, key) {
for (var j = 0; j < key.length; j++)
// data = dataDuration[key[j]];
data = data.meta_duration;;
return data;
}
}
})
and in view
<table>
<thead>
<tr>
<th>Media name</th>
<th>Media type</th>
<th>Media duration in sec</th>
<th>Media thumbnail</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="media in myMediaItems">
<td>{{media.name}}</td>
<td>{{media.type}}</td>
<td>>{{media.meta_duration}}</td>
<td><img src="{{media.thumbnail}}" alt="" /></td>
</tr>
</tbody>
<tr>
<td></td>
<td></td>
<td>Total duration {{ myMediaItems|sumProduct:'duration' }}</td> //here is where i use custom filter
<td></td>
</tr>
</table>
How to get total duration value in the controller?
Dynamically i add (push) media to list from another list with all media.
$scope.addToMediaList = function(item) {
var seconds = parseInt(item.meta_duration);
$scope.view_duration = Math.floor(moment.duration(seconds,'seconds').asHours()) + ':' + moment.duration(seconds,'seconds').minutes() + ':' + moment.duration(seconds,'seconds').seconds();($scope.view_duration_seconds, "HH:mm:ss");
$scope.myMediaItems.push(item);
$scope.sumDuration = $filter("sumProduct")(myMediaItems.meta_duration); //i try like this, set on $scope.sumDuration but in conole i get only number 0
$scope.myMediaItemsId.push(item.id);
$scope.input = "";
};
Thanks
I simplified your filter.
try this
app.filter('sumProduct', function() {
return function (input) {
var i = input instanceof Array ? input.length : 0;
var a = arguments.length;
if (i === 0)
return i;
var total = 0;
for(var x of input){
var duration = parseFloat(x.meta_duration);
if(isNaN(duration)){
throw 'filter sumProduct can count only numeric values';
}
total += duration;
}
return total;
}
});
You can use something like this
<div ng-init="sumDuration = myMediaItems|sumProduct:'duration'"> </div>
I solved this by forwarding the returned value from the custom filter to controller.
var filtered = $filter('sumProduct')($scope.myMediaItems);
console.log(filtered)
Filtered value is sum. thnx # Amaya San

angularjs custom sum filter doesn't work

im trying to do sum operation on my table Like
Item Price
abc 10
xyz 20
Ites=>2 Price=>30
For this i wrote simple custome filter
.filter('SumOfAmt', function () {
return function (data, key) {
if (angular.isUndefined(data) || angular.isUndefined(key))
return 0;
var sum = 0;
angular.forEach(data, function (value) {
sum = sum + parseInt(value[key]);
Html code
<form name="form1" ng-submit="SaveDb(form1.$valid)" novalidate>
<b>cost</b><input type="number" ng-model="cost" required />
</form>
This is my Table when user enter in form it display in table
<table class="table table-bordered">
<tr>
<td>Name</td>
<td>Price</td>
</tr>
<tr ng-repeat="Emp in EmployeeList">
<td>{{Emp.EmpName}}</td>
<td>{{Emp.Cost | currency}}</td>
</tr>
This is {{EmployeeList|SumOfAmt:'Cost'}}
</table>
And what's wrong?
With your custom filter you can get the right sum, you just need to return sum:
.filter('SumOfAmt', function () {
return function (data, key) {
if (angular.isUndefined(data) || angular.isUndefined(key))
return 0;
var sum = 0;
angular.forEach(data, function (value) {
sum = sum + parseInt(value[key]);
});
return sum;
};
});
But another alternative is:
.filter('SumOfAmt', function () {
return function (data, key) {
if (angular.isUndefined(data) || angular.isUndefined(key))
return 0;
return data.map(function(a){return parseInt(a[key])}).reduce((a, b) => a + b, 0);
};
});

how to add a row to ngTable

I am trying to add data from a typeahead selection to rows in a ngTable. I was using a regular bootstrap table for this but I needed to be able to edit the data after the row was added. INgTable has a table that can do this so I wanted to switch. I am not familiar with ngTable so any help would be great.
plunkr
//Add New POD Row
$scope.data = [];
$scope.addRow = function () {
$scope.data.push({
'JobItemName': $scope.masterItem.MLItemCode,
'JobItemDescription': $scope.masterItem.JobItemDescription,
});
$scope.masterItem.JobItemName = '';
$scope.masterItem.JobItemDescription = '';
};
//Remove POD Row
$scope.removeRow = function (JobItemName) {
var index = -1;
var comArr = eval($scope.data);
for (var i = 0; i < comArr.length; i++) {
if (comArr[i].JobItemName === JobItemName) {
index = i;
break;
}
}
if (index === -1) {
alert("Something gone wrong");
}
$scope.data.splice(index, 1);
};
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10 // count per page
}, {
total: $scope.data.length, // length of data
getData: function($defer, params) {
$defer.resolve($scope.data.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
Why use the ngTable directive and not something like this :
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in table" ng-click="editMyRow(row)">
<td ng-bind="row.column1"></td>
<td ng-bind="row.column2"></td>
</tr>
</tbody>
</table>
In this case, everytime you edit those values, your table will change in real time.
The controller would be something like :
angular.module('test').controller('blah', $scope){
$scope.table = {};
$scope.table.row = [];
$scope.table.row.add({
column1: '',
column2: ''
});
}

Angular pagination reverse jumping

I have this table:
Here is a code and Fiddle:
HTML
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th class="id">Id <a ng-click="sort_by('id')"><i class="icon-sort"></i></a></th>
<th class="name">Name <a ng-click="sort_by('name')"><i class="icon-sort"></i></a></th>
<th class="description">Description <a ng-click="sort_by('description')"><i class="icon-sort"></i></a></th>
<th class="field3">Field 3 <a ng-click="sort_by('field3')"><i class="icon-sort"></i></a></th>
<th class="field4">Field 4 <a ng-click="sort_by('field4')"><i class="icon-sort"></i></a></th>
<th class="field5">Field 5 <a ng-click="sort_by('field5')"><i class="icon-sort"></i></a></th>
</tr>
</thead>
<tfoot>
<td colspan="6">
<div class="pagination pull-right">
<ul>
<li ng-class="{disabled: currentPage == 0}">
<a href ng-click="prevPage()">« Prev</a>
</li>
<li ng-repeat="n in range(pagedItems.length, currentPage, currentPage + gap) "
ng-class="{active: n == currentPage}"
ng-click="setPage()">
<a href ng-bind="n + 1">1</a>
</li>
<li ng-class="{disabled: (currentPage) == pagedItems.length - 1}">
<a href ng-click="nextPage()">Next »</a>
</li>
</ul>
</div>
</td>
</tfoot>
<pre>pagedItems.length: {{pagedItems.length|json}}</pre>
<pre>currentPage: {{currentPage|json}}</pre>
<tbody>
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.description}}</td>
<td>{{item.field3}}</td>
<td>{{item.field4}}</td>
<td>{{item.field5}}</td>
</tr>
</tbody>
</table>
JS
function ctrlRead($scope, $filter) {
// init
$scope.sortingOrder = 'name';
$scope.gap = 5;
$scope.cached = 0;
$scope.reverse = false;
$scope.filteredItems = [];
$scope.groupedItems = [];
$scope.itemsPerPage = 5;
$scope.pagedItems = [];
$scope.currentPage = 0;
$scope.items = [
{"id":1,"name":"name 1","description":"description 1","field3":"field3 1","field4":"field4 1","field5 ":"field5 1"},
{"id":2,"name":"name 2","description":"description 1","field3":"field3 2","field4":"field4 2","field5 ":"field5 2"},
//....
];
var searchMatch = function (haystack, needle) {
if (!needle) {
return true;
}
return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};
// init the filtered items
$scope.search = function () {
$scope.filteredItems = $filter('filter')($scope.items, function (item) {
for(var attr in item) {
if (searchMatch(item[attr], $scope.query))
return true;
}
return false;
});
// take care of the sorting order
if ($scope.sortingOrder !== '') {
$scope.filteredItems = $filter('orderBy')($scope.filteredItems, $scope.sortingOrder, $scope.reverse);
}
$scope.currentPage = 0;
// now group by pages
$scope.groupToPages();
};
// calculate page in place
$scope.groupToPages = function () {
$scope.pagedItems = [];
for (var i = 0; i < $scope.filteredItems.length; i++) {
if (i % $scope.itemsPerPage === 0) {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)] = [ $scope.filteredItems[i] ];
} else {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)].push($scope.filteredItems[i]);
}
}
};
$scope.range = function (size,start, end) {
if( $scope.cached == start){
start = start - 4;
console.log('start',start);
}
$scope.cached = start;
var ret = [];
console.log(size,start, end);
if(size < 2){return ret;}
if (size < end) {
end = size;
start = size-$scope.gap;
}
for (var i = start; i < end; i++) {
if(i<0) continue;
ret.push(i);
}
console.log(ret);
return ret;
};
$scope.prevPage = function () {
if ($scope.currentPage > 0) {
$scope.currentPage--;
}
};
$scope.nextPage = function () {
if ($scope.currentPage < $scope.pagedItems.length - 1) {
$scope.currentPage++;
}
};
$scope.setPage = function () {
$scope.currentPage = this.n;
};
// functions have been describe process the data for display
$scope.search();
// change sorting order
$scope.sort_by = function(newSortingOrder) {
if ($scope.sortingOrder == newSortingOrder)
$scope.reverse = !$scope.reverse;
$scope.sortingOrder = newSortingOrder;
};
};
ctrlRead.$inject = ['$scope', '$filter'];
From the code you can see that we have 13 groups of 5 rows.
If I press in pagination on 5, the 5 button jumps to 1st place and last place is 9.
By this way I can "travel" quickly over all data.
My problem that I don't know how to make it wok to jump back a.e reverse.
For example if I stay on 13:
and I press on 9 I expect that 9 will jump to the end of pagination and 1st element in list will be 5.
How to achieve that?
Thank you,
In general you need to decouple the idea of your current page index and the indices of your page navigation links. There are lots of ways to do this though. I did this by adding a left and right gap to the range you are creating, which better control the indices of the quick nav buttons. So when you call setPage I just add a little check:
if (this.n <= $scope.currentPage) {
$scope.left_gap = $scope.gap-1;
$scope.right_gap = 1;
} else {
$scope.left_gap = 0;
$scope.right_gap = $scope.gap;
}
This way, when you click to the left of your current page, it will do the correct gaps so that the clicked index will be on the right, but the right clicking behavior still works. You just have to use:
<li ng-repeat="n in range(pagedItems.length, currentPage - left_gap, currentPage + right_gap) ">
This is not without it's bugs though, and you will still need to fix making sure you always keep 5 around, but I'll leave that to you. Here is the fiddle to play with.
Hope this helped!

Resources