AngularJS sum of rows ng-repeat - javascript

I add dynamically rows in my table with ng-repeat, coming from an array.
Now I want to get the sum of all sums per row (group.sum * group.perc / 100.0). I need it in a variable because I need this value for further calculations. Thank you
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}}</td>
</tr>
SCRIPT
var taxTotals = 0;
var taxTotals =
for (i=0; i<group.length; i++) {
taxTotal = taxTotal + group[i].taxes;
};
console.log(taxTotals);
};

Create a Filter:
app.filter('sumFilter', function() {
return function(groups) {
var taxTotals = 0;
for (i=0; i<groups.length; i++) {
taxTotal = taxTotal + groups[i].taxes;
};
return taxTotals;
};
});
Use the $filter service:
app.controller('myController', function($scope, $filter) {
$scope.groups = [...];
var taxTotals = $filter('sumFilter')($scope.groups);
console.log(taxTotals);
});
Use it in your HTML:
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ groupsArr | sumFilter | currency }}
</tr>

An addition for best answer... I am using filter in my very huge table, so it is how to implement with dynamic filters.
THE FILTER
app.filter('sumStatusFilter', function(){
return function (items, filtersStatus, filterLocations){
var filtered = [];
var filtered1 = [];
var total = 0;
if (typeof filtersStatus != 'undefined') {
angular.forEach(items, function(item) {
for(i = 0; i < filtersStatus.length; i ++){
if(filtersStatus[i] == item.status_message)
filtered.push(item);
}
});
}
if (typeof filterLocations != 'undefined') {
angular.forEach(filtered, function(item) {
for(i = 0; i < filterLocations.length; i ++){
if(filterLocations[i] == item.office_location)
filtered1.push(item);
}
});
filtered = [];
filtered = filtered1;
}
if (filtered.length == 0) {
filtered = this.jobs
}
angular.forEach(filtered, function(value, key){
total += value.restoration_reserve
});
return total;
}
});
in HTML
<tr><td>Total: {{ report_controller.items | sumStatusFilter:report_controller.status_message_selections:report_controller.office_selections | currency }}</td></tr>

UPDATE AFTER ANSWER coming from pixelbits
Thanks to pixelbits. Here is my filter, which works perfect within the view.
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ groupsArr | sumFilter | currency }}
</tr>
Filter
angular.module('App.filters', []).filter('sumFilter', [function () {
// filter for tax sum
return function(groups, lenght) {
var taxTotal = 0;
for (i=0; i < groups.length; i++) {
taxTotal = taxTotal + ((groups[i].perc * groups[i].sum) / 100);
};
return taxTotal;
};
}]);
If I want to access from my controller, it doesn´t work: I cannot get the variable taxTotals *Cannot read property 'length' of undefined
As mentioned, in the view it works.
Filter Service
var taxTotal = $filter('sumFilter')($scope.groups);
console.log(taxTotal);

Or use Map Reduce!
Controller
$scope.mappers = {
tax: function(m){
return group.sum * group.perc / 100.0;
}
}
$scope.sum = function(m){
if($scope.groupsArr.length == 0) return;
return $scope.groupsArr.map(m).reduce(function(p, c){
return p + c;
}) || 0;
};
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ sum(mappers.tax) }}
</tr>

Related

Angularjs, show/hide row's table with ng-repeat

Sorry, i'm new to ng-repeat. How can i show/hide row table that is using ng-repeat? And the most bottom row is shown if dropdown value is 1.
var i ;
$scope.names = [];
$scope.tmp = [];
for(i=0;i<=10;i++){
$scope.tmp[i] = i;
$scope.names[i] = "name "+ i;
}
$scope.isShow = true
html
<select>
<option ng-repeat="x in tmp">{{x}}</option>
</select>
<table>
<tr ng-show='isShow' ng-repeat="name in names">
<td>{{name}}</td>
</tr>
</table>
May be you must add property isShow for each name in names?
Or create array with visible status for each name.
angular.module('app', [])
.directive('appDir', appDir);
angular.bootstrap(
document.getElementById('root'), ['app']
);
function appDir() {
return {
template: `
<table>
<tr
ng-repeat="name in names"
ng-show="name.isShow"
>
<td>
{{name.title}}
</td>
</tr>
</table>
<select
ng-model="selectedName"
ng-options="x as x for x in tmp"
ng-change="hiddenName()"
>
`,
link: appDirLink
}
}
function appDirLink($scope) {
$scope.names = [];
$scope.tmp = [];
$scope.hiddenName = hiddenName;
for (var i = 0; i < 10; i++) {
$scope.names[i] = {
id: i,
title: 'name_' + i,
isShow: true
};
$scope.tmp[i] = i;
}
function hiddenName() {
$scope.names.map((name, index) => {
name.isShow = (index < $scope.selectedName) ? true : false;
});
}
}
<div id="root">
<app-dir></app-dir>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.5/angular.min.js"></script>

Isolating events in Angular 1.5 Component Instances

I've created a component which is being used to add "data grid" functionality to HTML tables. The headers are clickable to allow sorting of the data (ascending/descending) on that column. So far it's working great unless I have two instances of the component on the same page. When I click a header in one table, it affects both tables.
Is there a way I'm missing to isolate the component's events to only affect that instance?
Component:
angular.module('app')
.component('datagrid', {
templateUrl:'components/datagrids/datagrids.component.html',
controller:DatagridController,
})
Controller (Work in progress, I know It's a bit of a mess at the moment!):
function DatagridController($filter, datagridService){
var ctrl = this;
ctrl.today = new Date();
ctrl.sortBy = null;
ctrl.fields = [];
ctrl.data = [];
ctrl.update = function(){
var service = datagridService;
console.log(datagridService);
var updatedFields = [];
console.log(datagridService.fields);
for(var i = 0; i < datagridService.fields.length; i++){
var fieldName = datagridService.fields[i];
var fieldDirection = (ctrl.fields.length === 0) ? 'ascending' : ctrl.fields[i].direction;
updatedFields.push({name:fieldName, direction:fieldDirection});
}
ctrl.fields = updatedFields;
console.log(ctrl.fields)
if (ctrl.sortBy == null){ ctrl.sortBy = $filter('toCamelCase')(ctrl.fields[0].name); }
ctrl.data = datagridService.data.sort(ctrl.sortData(ctrl.sortBy));
ctrl.today = new Date();
};
ctrl.sortData = function(field, reverse, primer){
console.log(field + ' | ' + reverse)
var key = primer ?
function(x) {return primer(x[field])} :
function(x) {return x[field]};
reverse = !reverse ? 1 : -1;
ctrl.sortBy = field;
return function (a, b) {
return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
}
};
ctrl.toggleSortDirection = function(index){
console.log(index);
var field = ctrl.fields[index];
console.log(field);
var fieldName = field.name;
console.log(fieldName);
var direction = ctrl.fields[index].direction;
console.log(direction);
var reverse = (direction == 'ascending') ? true : false;
console.log(reverse);
var direction = (direction === 'ascending') ? 'descending' : 'ascending';
console.log(direction);
for(var i = 0; i < ctrl.fields.length; i++){
ctrl.fields[i].direction = 'ascending';
}
ctrl.fields[index].direction = direction;
ctrl.data.sort(ctrl.sortData($filter('toCamelCase')(fieldName), reverse));
};
ctrl.validDatetime = function(dt){
//this should probably be a service
console.log(dt);
var rx = /([0-9]{4})\-([0-9]{2})\-([0-9]{2})/;
if(dt.match(rx)){ console.log(dt); }
return (dt.match(rx)) ? true : false;
};
ctrl.$onInit = ctrl.update();
}
DatagridController.$inject = ['$filter', 'datagridService'];
Template:
<table ng-if="$ctrl.data.length > 0" class="datagrid">
<caption ng-if="$ctrl.caption">{{ $ctrl.caption }}</caption>
<colgroup ng-if="$ctrl.colgroup.length > 0">
<col ng-repeat="col in $ctrl.colgroup">
</colgroup>
<thead ng-if="$ctrl.hasHeader = true">
<tr>
<th ng-repeat="field in $ctrl.fields" ng-click="$ctrl.toggleSortDirection($index)" data-sortable="true">{{ field.name }}<div ng-class="field.direction"></div></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in $ctrl.data">
<td ng-repeat="field in $ctrl.fields">
<span ng-if="!$ctrl.validDatetime(record[(field.name|toCamelCase)])"><a>{{ record[(field.name|toCamelCase)] }}</a></span>
<span ng-if="$ctrl.validDatetime(record[(field.name|toCamelCase)])"><a>{{ record[(field.name|toCamelCase)] | date: 'dd-MMM-yyyy' }}</a></span>
</td>
</tr>
</tbody>
<tfoot ng-if="$ctrl.hasFooter = true">
<td colspan="{{ $ctrl.fields.length }}">Last Updated: {{ $ctrl.today | date: 'dd-MMM-yyyy' }}</td>
</tfoot>
</table>
Component Tag:
<datagrid></datagrid>
Components are isolated by default, which means there is its own $ctr for every instance.
Thing is that data is shared through service. For example you do datagridService.data.sort in first instance => it changes data in service => it gets reflected in all instances of your component (there is one data object in memory, that you are trying to access).
One fix might be, to make copies of data for every component instance.
ctrl.data = Object.assign([], datagridService.data);
Dont do any manipulation directly on datagridService.data, but use ctrl.data instead

Populate new Array on ng-repeat

$scope an array consisting Code and Amount on controller. When calculating summary on a function, browser gets Uncaught Error: 10 $digest() iterations reached. Aborting! error which caused from infinite loop (Strange but it is working).
Is there any proper way to combine new Array while ng-repeat without getting infinite loop errors?
Any help would be appreciated
jsFiddle Link
Update: Lines variables are not static, can be added, modified or removed.
jsFiddle Line for Update
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},
{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},
{Code:'Y', Amount:'1'}];
$scope.Sums = function(){
var sums = new Array();
for (var i = 0; i < $scope.Lines.length; i++) {
var added = false;
for (var j = 0; j < sums.length; j++) {
if (sums[j].Code == $scope.Lines[i].Code) {
sums[j].Amount = parseFloat( sums[j].Amount) + parseFloat($scope.Lines[i].Amount);
added = true;
break;
}
}
if (!added) {
sums.push( { Code:$scope.Lines[i].Code, Amount: $scope.Lines[i].Amount } );
}
}
return sums;
}
}
Html:
<div ng-controller="MyCtrl">
<table style="border: 1px solid black;">
<tr ng-repeat="line in Lines">
<td>{{ line.Code }}</td>
<td>{{ line.Amount }}</td>
</tr>
</table>
Summary
<table style="border: 1px solid black;">
<tr ng-repeat="sum in Sums()">
<td>{{ sum.Code }}</td>
<td>{{ sum.Amount }}</td>
</tr>
</table>
</div>
Does this still happen if you do this:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},{Code:'Y', Amount:'1'}];
$scope.Sums = [];
calculate();
var calculate = function(){
$scope.Sums.length = 0;
for (var i = 0; i < $scope.Lines.length; i++) {
var added = false;
for (var j = 0; j < $scope.Sums.length; j++) {
if ($scope.Sums[j].Code == $scope.Lines[i].Code) {
$scope.Sums[j].Amount = parseFloat( $scope.Sums[j].Amount) + parseFloat($scope.Lines[i].Amount);
added = true;
break;
}
}
if (!added) {
$scope.Sums.push( { Code:$scope.Lines[i].Code, Amount: $scope.Lines[i].Amount } );
}
}
}
}
Note that it is important to never create a new array once Sums is watched by angular. Use $scope.Sums.length = 0 instead if you need to empty it.
In your view: <tr ng-repeat="sum in Sums">
As Andre Kreienbring pointed out the problem is because your sum() is returning an object.
I would suggest using filters to accomplish what you need, like so
HTML
<div ng-controller="MyCtrl">
<table style="border: 1px solid black;">
<tr ng-repeat="line in Lines">
<td>{{ line.Code }}</td>
<td>{{ line.Amount }}</td>
</tr>
</table>
Summary
<table style="border: 1px solid black;">
<tr ng-repeat="sum in Lines | unique : 'Code'">
<td>{{ sum.Code }}</td>
<td>{{ Lines | filter: { Code: sum.Code } : true | sum: 'Amount' }}</td>
</tr>
</table>
</div>
Script
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},{Code:'Y', Amount:'1'}];
}
myApp.filter('unique', function() {
return function(input, key) {
var unique = {};
var uniqueList = [];
for(var i = 0; i < input.length; i++){
if(typeof unique[input[i][key]] == "undefined"){
unique[input[i][key]] = "";
uniqueList.push(input[i]);
}
}
return uniqueList;
};
});
myApp.filter('sum', function() {
return function(input, key) {
var sum = 0;
for(var i = 0; i < input.length; i++){
sum += Number(input[i][key]);
}
return sum;
};
});
The unique filter is from https://stackoverflow.com/a/18382680/360067
Fiddle - http://jsfiddle.net/34od99sz/
I'd rather put a $watch on $scope.Lines which will create an array and populate it as $scope.sums such that ng-repeat doesn't have to call the method again and again. See Fiddle
Edit (added missing parameter for deep watching):
Fiddle
if I am not mistaken in the line
for (var j = 0; j < sums.length; j++)
you try to take the length of sums whitch is a new array. it has no length though. Also why do you use
var sums = new Array();
to initialize the array and not
var sums = [];

jQuery count of specific class an element contains

I have a table and each row in the table has one or more classes depending on the region.
Here is what my table looks like:
<table>
<thead>
<th>Title</th>
<th>Name</th>
</thead>
<tbody>
<tr class="emea apac">
<td>Testing</td>
<td>Bob</td>
</tr>
<tr class="americas">
<td>Testing2</td>
<td>Jim</td>
</tr>
<tr class="emea">
<td>Testing 3</td>
<td>Kyle</td>
</tr>
<tr class="emea americas">
<td>Testing 3</td>
<td>Kyle</td>
</tr>
<tr class="emea apac americas">
<td>Testing 3</td>
<td>Kyle</td>
</tr>
<tr class="apac">
<td>Testing 3</td>
<td>Kyle</td>
</tr>
</tbody>
I am trying to now count specifically how many rows there are where the class is equal to my condition.
For example:
How many rows have ONLY .APAC = 1
How many rows have all 3 of the possible classes? = 1
I started this jsFiddle but couldn't really think of how to approach it from this point: http://jsfiddle.net/carlhussey/gkywznnj/4/
Working from your fiddle (updated)...
$(document).ready(function () {
var apac = 0,
emea = 0,
americas = 0,
combo = 0,
all = 0;
$('table tbody tr').each(function (i, elem) {
var classes = elem.className.split(' '),
hasApac = classes.indexOf('apac') > -1,
hasEmea = classes.indexOf('emea') > -1,
hasAmericas = classes.indexOf('americas') > -1;
apac += (hasApac && !hasEmea && !hasAmericas) ? 1 : 0;
emea += (hasEmea && !hasApac && !hasAmericas) ? 1 : 0;
americas += (hasAmericas && !hasApac && !hasEmea) ? 1 : 0;
if (((hasApac && hasEmea) || (hasApac && hasAmericas) || (hasEmea && hasAmericas)) && classes.length === 2) {
combo += 1;
}
if (hasApac && hasEmea && hasAmericas) {
all += 1;
}
});
$('span[name="apac"]').text(apac);
$('span[name="emea"]').text(emea);
$('span[name="americas"]').text(americas);
$('span[name="combo"]').text(combo);
$('span[name="all"]').text(all);
});
UPDATE
I'm pretty sure jQuery's hasClass method works with IE8, so you could change the .each callback to:
function (i, elem) {
var row = $(elem),
hasApac = row.hasClass('apac'),
hasEmea = row.hasClass('emea'),
hasAmericas = row.hasClass('americas');
apac += (hasApac && !hasEmea && !hasAmericas) ? 1 : 0;
emea += (hasEmea && !hasApac && !hasAmericas) ? 1 : 0;
americas += (hasAmericas && !hasApac && !hasEmea) ? 1 : 0;
if (((hasApac && hasEmea) || (hasApac && hasAmericas) || (hasEmea && hasAmericas)) && elem.className.split(' ').length === 2) {
combo += 1;
}
if (hasApac && hasEmea && hasAmericas) {
all += 1;
}
}
Updated fiddle: http://jsfiddle.net/gkywznnj/6/
var rows = $('tr'),
class = 'americas',
counter = 0;
rows.each(function () {
//If current element have .americas increment counter
if($(this).hasClass(class)) {
counter +=1
}
});
console.log(counter);
http://jsfiddle.net/gkywznnj/8/
Object.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
function countOnlyClass(classFindA)
{
var $trA=$('table tbody tr');
var count=0;
if(classFindA.length>0){
$trA.each(function(){
var c=0;
var m=0;
var $tr=$(this);
var classA = $tr.attr('class').split(' ');
$.each(classA,function(i,cl){
if(classFindA.indexOf(cl)>-1) c++; else m++;
})
if(c>0 && c==classFindA.length && m==0) count++;
})
}
return count;
}
function comboOnlyClass(comboCount)
{
var $trA=$('table tbody tr');
var count=0;
$trA.each(function(){
var countClass = {};
var $tr=$(this);
var classA = $tr.attr('class').split(' ');
$.each(classA,function(i,cl){
if(!cl in countClass )
countClass.cl=1;
})
if(Object.size(classA )==comboCount) count++;
})
return count;
}
var a=countOnlyClass(['apac'])
$('#apac').html(a);
var a=countOnlyClass(['emea'])
$('#emea').html(a);
var a=countOnlyClass(['americas'])
$('#americas').html(a);
var a=countOnlyClass(['apac','emea','americas'])
$('#all').html(a);
var a=comboOnlyClass(2);
$('#combo').html(a);
//var a=comboOnlyClass(1); onlu one class

How to show and hide rows using several select buttons

I have problem with showing and hiding rows, when I select two select buttons at once.
Using only one button, I can show/hide the correct rows.
Using both buttons at once, no rows will be displayed.
Where is the logical error in the code?
Please check: http://jsfiddle.net/xEyJZ/83/
<div ng-controller="MyCtrl">
<p>
<input type="checkbox" ng-init="showNew=false" ng-click="showNew =! showNew"><span> Show new only</span> <br>
<input type="checkbox" ng-init="showOld=false" ng-click="showOld =! showOld"><span> Show old only </span>
</p>
<table border="1">
<tr ng-repeat="person in persons" ng-class="{'newp':person.newp, 'oldp':person.oldp}"
ng-hide="(!person.newp && showNew) || (!person.oldp && showOld)">
<td>{{ person.id }}</td>
<td>{{ person.name }}</td>
<td>{{ person.city }}</td>
</tr>
</table>
</div>
You can use comparator in your filter:
ng-repeat="person in persons | filter:{newp:showNew, oldp:showOld}:showTest"
and in controller set comparison function:
$scope.showTest = function(actual, expected){
if(!$scope.showNew && !$scope.showOld)
return true;
return angular.equals(expected, actual);
}
http://jsfiddle.net/D79F7/2/
You have error in your boolean expression:
Correctly as follows:
ng-hide="(!person.newp && showNew ) || (!person.oldp && showOld)"
http://jsfiddle.net/ZWy93/2/
Using a custom filter:
<input type="checkbox" ng-model="showNew">..Show new only..
<input type="checkbox" ng-model="showOld">..Show old only..
..
<tr ng-repeat="person in persons | personsFilter : showNew : showOld" ..>
JS
myApp.filter("personsFilter",function(){
return function(input, showNew, showOld){
if(!showNew && !showOld){
return input;
}
else if(showNew && !showOld){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].newp && !input[i].oldp)
temp.push(input[i]);
}
return temp;
}
else if(showOld && !showNew){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].oldp && !input[i].newp)
temp.push(input[i]);
}
return temp;
}
else if(showOld && showNew){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].oldp || input[i].newp)
temp.push(input[i]);
}
return temp;
}
}
});
Fiddle

Categories

Resources