Totaling a Summary Line in Data Table - sum

I hve a sumField method which totals values in a given column:
sumField (key) {
let total = 0
const sum = this.tableName.reduce((acc, cur) => {
return (total += +cur[key])
}, 0)
return sum
}
Inside my data table I call sumField to produce a rolling total of the values in a specific column of my data table:
<template v-slot:[`body.append`]="{headers}">
<tr class="summary">
<td v-for="(header,i) in headers" :key="i">
<div v-if="header.value == 'COL HEADER 1'">
{{ sumField('COL HEADER 1') }}</div>
<div v-else-if="header.value == 'COL HEADER 2'">
{{ sumField('COL HEADER 2') }}</div>
</td>
</tr>
</template>
This is presented on screen as an additional line of the data table, and the values change depending on the filters applied to the table.
Is there a way to sum the values calculated, and show this as a rolling total value also?

Got a solution which I hope might provide some help to others in the future!
Started by creating a new function:
sumTot (col1, col2) {
var one = col1
var two = col2
var tot = col1 + col2
return tot
}
Then I called sumTot giving the arguments of the individual sumField calls:
{{ sumTotal(sumField ('COL HEADER 1'), sumField ('COL HEADER 2')) }}
That gives me a dynamically updated running total value.

Related

Dynamically calculate percentage in a datatable

How can I dynamically calculate a percentage in a datatable when a search is applied ?
The result should be like this :
<tr>
<td>10%</td><td>100</td>
<td>90%</td><td>900</td>
</tr>
The percentage should be based on the total of rows shown.
The following example shows you a basic approach.
The end result looks like this - note you may get a small rounding error when summing the percentages:
The code is as follows:
UPDATE - There was a problem with my original solution - it used jQuery to populate DataTable cells with the calculated percentages. That meant DataTables did not respond to sorting/filtering for the percentages column. It's better to use DataTables functions to populate the data cells.
<body>
<div style="margin: 20px;">
<table id="example" class="display dataTable cell-border" style="width:100%">
<thead>
<tr><th>ID</th><th>Amount</th><th>Percent</th></tr>
</thead>
<tbody>
<tr><td>1</td><td>123.4</td><td class="percent"></td></tr>
<tr><td>2</td><td>234.5</td><td class="percent"></td></tr>
<tr><td>3</td><td>543.21</td><td class="percent"></td></tr>
<tr><td>4</td><td>76</td><td class="percent"></td></tr>
<tr><td>5</td><td>87</td><td class="percent"></td></tr>
</tbody>
</table>
</div>
<script type="text/javascript">
$(document).ready(function() {
var table = $('#example').DataTable({
// wait for table and data to finish being initialized:
"initComplete": function( settings, json ) {
populatePercentages();
}
});
function populatePercentages() {
var total = 0.0;
// first calcuate the total:
$('#example').DataTable().rows().every( function ( rowIdx, tableLoop, rowLoop ) {
// assumes amounts are not null, all numeric, and are in column 2:
total = total + Number(this.data()[1]);
});
// then calculate the percentage for each row:
$('#example').DataTable().rows().every( function ( rowIdx, tableLoop, rowLoop ) {
var amount = this.data()[1];
// calculate percent to 2 decimal places:
var percentage = Math.round((amount / total) * 10000) / 100;
var cells = $('#example').DataTable().cells(rowIdx, 2);
cells.every( function () {
this.data(percentage);
});
});
}
});
</script>
</body>
Points to note:
1) It uses a "percent" class for the cells where percentages will be shown. This makes selection easier.
2) You can do something similar as (1) for the amounts cells also. I did not, here.
3) The code loops through the table data twice: Once to calculate the grand total amount; and again to calculate the percentages.
4) You would need to adjust assumptions about column offsets to fit your table design.
5) There is more validation which should really be added, to handle missing or non-numeric data, to make the code less brittle.
Handling the case when a search is applied:
Filtering:
The above solution can be enhanced to re-calculate percentages when data is filtered.
1) There are two places where the rows() function is used. In both places change this to the following: rows( { search: 'applied' } ). This will ensure only visible rows are considered when percentages are calculated.
2) Add a new section of code to handle keyup events for the search (filter) box. In my case the selector for this input control is as shown below - but this will need to be edited for other datatables with different IDs (my example table's ID is example):
<script type="text/javascript">
$(document).ready(function() {
var table = $('#example').DataTable({
...
});
function populatePercentages() {
...
}
// catch filter events and re-calculate percentages accordingly:
$('div#example_filter.dataTables_filter > label > input').keyup(function (e) {
populatePercentages();
});
});
</script>
With these changes, a filtered list will look like this:

Filtering Data Table in PrimeNG

How can I get the number of rows after filtering using PrimeNG's default filters in data table.
[totalRecords]="totalRecords" always shows the records count which is fetched initially.
Even though after filtering, totalRecords value remains same and does not change after filtering.
Example:
initially totalRecords is 50 and after filtering, no.of records it shows in data table is 15. But I cannot get the value 15, instead getting 50.
Is there any way ?
Supposing you have a reference to your datatable component, just ask totalRecords property on it :
<p-dataTable [value]="cars" [rows]="10" [paginator]="true" [globalFilter]="gb" #dt>
...
</p-dataTable>
{{ dt.totalRecords }} records
Demo
The above answer is correct and I'm adding up a little thing to it.
If you want to bind the totalRecords value to your typescript .ts file, then use an (onFilter) event and trigger a function with parameters as $event and dt.totalRecords
In my case, i have given
<p-table #dt [value]="personListData" [columns]="columns" (onPage)="onPageChange($event)" [resizableColumns]="true" [paginator]="true" [rows]="rowsCount" selectionMode="multiple" [(selection)]="selected_data" [loading]="loading" [totalRecords]="totalRecords" class="table table-hover table-responsive table-bordered" [responsive]="true" (onFilter)="handleFilter($event,dt.totalRecords)">
In short,
(onFilter)="handleFilter($event,dt.totalRecords)"
Function in .ts file ,
handleFilter(e,filteredRecordCount){
console.log("filteredRecordCount");
}
NOTE: If you want to use the filtered records count value, then you
can assign it to any variable and use anywhere in your typescript
file.
I'm on Angular 8, and my table is not paginated. dt.totalRecords is always the full amount. So if I have 20 rows, and I get it filtered down to 2 on-screen, dt.totalRecords still = 20.
What I ended up doing was using the onFilter, passing in the entire dt, then using dt.filteredValue:
onFilter(event: any, table: any): void {
if(!!table.filteredValue) {
this.visibleRows$.next(table.filteredValue.length);
}
}
You have to check for null, because if you change the filter but don't filter out any additional rows, filteredValue is null.
html:
<p-dataTable #dt (onFilter)="handleFilter()" [value]="cars" [rows]="10" [paginator]="true" >
...
</p-dataTable>
{{ dt.totalRecords }} records
ts:
#ViewChild('dt', { static: true }) dt: Table;
handleFilter() {
if (this.dt.filteredValue != null)
this.dt.totalRecords = this.dt.filteredValue.length;
else
this.dt.totalRecords = this.cars.length;
}

How to traverse through Dynamic Object of Arrays in VueJS

I have an object with arrays which is nothing but JSON reply from server which I converted into Object and now it looks like this (but lot of values into it):-
Object_return:{
name:[1,25,2,24,3,78],
age:[2,34,4,78]
}
here name and age is dynamic coming from the server, so I do not know what exact values coming there so I can not refer it while iterating through the for loop
<th v-for = "item in Object_return.name">
and also I want to show this in a DataTable so the first row should looks like this
------------------
1 25
-------
name 2 24
-------
3 78
--------------------
second row
---------------------
2 34
-------
age 4 78
------------------------
and so on and so forth for all the values coming from the server
Does someone have an idea how to do this
You can iterate over an object and get the key value as the second argument.
<tr v-for="val, key in Object_return">
Here, key will be the name of the property.
Then, since you want to group the arrays in pairs, I suggest a computed property to massage the data into the format you want.
Here is a working example.
console.clear()
new Vue({
el: "#app",
data:{
serverData: {
name:[1,25,2,24,3,78],
age:[2,34,4,78]
}
},
computed:{
massaged(){
return Object.keys(this.serverData).reduce((acc, val) => {
// split each array into pairs
const copy = [...this.serverData[val]]
let pairs = []
while (copy.length > 0)
pairs.push(copy.splice(0, 2))
// add the paired array to the return object
acc[val] = pairs
return acc
}, {})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<table>
<tr v-for="val, key in massaged">
<td>{{key}}</td>
<td>
<table>
<tr v-for="pair in val">
<td>{{pair[0]}}</td>
<td>{{pair[1]}}</td>
</tr>
</table>
</td>
</tr>
</table>
</div>

Bootstrap media object cannot align properly

I am using Bootstrap 3 media objects and I have this code here dynamically I want to have 4 columns in one row,but the problem is that the last row will have spaces inbetween to the second row.
here is my code.
//content.php
for(var i=0;i<12;i++){
mymdedia+=
'<div class="media col-sm-3">'+
'<div class="media-left">'+
'<a href="#">'+
'<img src=".....">'+
'</a>'+
'</div>'+
'<div class="media-body">'+
'<h4 class="media-heading">'John Doe'</h4>'+
'<p>Live: '+data[i].address+'</p>'+
'</div>'+
'</div>';
}
$('#mycontainer').append(mymdedia);
//index.php
<div class="someclass">
<div class="row" id="mycontainer">
</div>
</div>
and this is the result looks like
I believe you are placing all the media objects in one row ... and they don't fit.
In every row that you've opened ( <div class='row'>...</div> ) you can place only 4 <div class='media col-sm-3'></div> ... because each col size = 3 ... ( 4x3 = 12 )
So in the for loop, after each 4 media objects - you should close the current row div and create a new <div class='row'> ...
See plunker: http://plnkr.co/edit/BJa3qdbKcYMDwhW809gp?p=preview
Right after the for loop:
for(var i=0;i<12;i++){
if ( i && i % 4 == 0 )
mymdedia += '</div><div class="row">'; // close row and create a new one
... your original code ...
}
mymdedia += '</div>' // close the last row outside the for loop ...

Laravel Display a record details while grouped by year or month

I just moved to the laravel framework and am starting to migrate some legacy sites and I have hit a problem with SQL or blade - dunno which.
I have to display a load of rows 'sports classes' which are grouped by year and then month. each needs to show attendance etc.
I am unsure which way to proceed.
I am able to display all rows and sort by date - easy squeezy
I am able to groupBy year AND month - fiddly but sorted it.
These are all displayed in an accordian.
Click the month - the individual rows drop down - you get the idea
I can get a number of rows per month/year
What I am unable to figure out is how to actually display the rows.
The groupBy is this:
$LinkClasses = DB::table('classes_lists')
->select('id, class, teacher, size')
->select(DB::raw('YEAR(date) AS year, MONTH(date) AS month, MONTHNAME(date) AS month_name, COUNT(*) post_count'))
->groupBy('year')
->groupBy('month')
->orderBy('year', 'desc')
->orderBy('month', 'desc')
->orderBy('id', 'desc')
If the code you provided is within your controller, then you can append ->get() after your last ->orderBy(). This will return a Collection. You can then do whatever you want with the Collection (http://laravel.com/api/master/Illuminate/Support/Collection.html), including conversion to an array using ->toArray(), but I think it would be best to utilize the Eloquent ORM if possible.
Anyway, once you have it in the format you want, just pass it to the view like so:
return view('your.view', compact('LinkClasses'));
Then, inside the your.view blade template, you can access this by using the following:
#foreach ($LinkClasses as $currentRow)
<tr>
<td>{{ $currentRow['id'] }}</td>
<td>{{ $currentRow['class'] }}</td>
<td> ... </td>
</tr>
#endforeach
Best guess I can offer without seeing the blade template to get a better idea of what you're doing. Hope that helps!
UPDATE BASED ON OP FEEDBACK:
Since you are only receiving a single record, it seems as though the issue lies in your query. I suggest you simplify your query to fetch all records and then do your sorting within an array. Something like this in your controller:
$allClasses = DB::table('classes_lists')->all();
foreach ($allClasses as $currentClass) {
$yearMonth = date('Y-m', $currentClass['date']);
$classesByYearMonth[$yearMonth][] = $currentClass;
}
ksort($classesByYearMonth);
/* now you have an array of all classes sorted by year-month like this:
// $classesByYearMonth[2014-01] = array(
// [0] => array(1, 'class name', 'teacher name', 23),
// [1] => array(2, 'another class', 'different teacher', 25),
// ...
// );
//
// $classesByYearMonth[2014-02] = ...
*/
return view('your.view', compact('classesByYearMonth'));
Then, inside your blade template:
#foreach ($classesByYearMonth as $yearMonth => $classListArray)
Found {{ sizeof($classListArray) }} classes for {{ $yearMonth }}
#foreach ($classListArray as $currentClass)
<div>
<div>ID: {{ $currentClass['id'] }}</div>
<div>Class: {{ $currentClass['class'] }}</div>
<div>Teacher: {{ $currentClass['teacher'] }}</div>
<div>Size: {{ $currentClass['size'] }}</div>
</div>
#endforeach
#endforeach
I will leave it to you to fix the formatting to make your accordion work. But hopefully that will get you on the right path.
DNoe - thank you so much.
Your reply put me on exactly the right track.
I had to mod some bits due to laravel ambiguities and add the strtotime but the logic was all there.
foreach ($allClasses as $currentClass) {
$ym = $currentClass['date'];
$yearMonth = date("Y-m",strtotime($ym));
$classesByYearMonth[$yearMonth][] = $currentClass;
}
krsort($classesByYearMonth);
return View::make('classes.index', compact('classesByYearMonth'));
The css is simple from here.
I owe you some beers. And thanks for helping me take my head from my butt!
Send me a pm and i would be very very happy to forward beer donation :o
Great work and thank you again. :)
Also, part of the problem was that the results were throwing an stdObject rather than an array.
Being able to compare your code with my own has enabled me to create a dbquery with multiple joins from which meaningfull data is selected and then converted to an array.
$classes = DB::table('table2')
->join('table1', 'table2.id', '=', 'table1.id2' )
->join('table3', 'table1.id3', '=', 'table3.id' )
->orderBy('classes_lists.date','DESC')
->get(array('table1.id', 'teacher', 'date', 'size', 'students', 'fname', 'classname', 'table1.notes'));
$cfr = count($classes);
foreach($classes as $object)
{
$arrays[] = (array) $object;
}
foreach ($arrays as $currentClass){
$ym = $currentClass['date'];
$yearMonth = date("Y-m",strtotime($ym));
$clazByYearMonth[$yearMonth][] = $currentClass;
}
krsort($clazByYearMonth);
This was the output into blade:
Not formatted :
#foreach ($clazByYearMonth as $yearMonth => $classListArray)
Found {{ sizeof($classListArray) }} classes for {{ $yearMonth }}
#foreach ($classListArray as $currentClass)
<div>
date: {{ $currentClass['date'] }} | class: {{ $currentClass['classname'] }} | Size: {{ $currentClass['size'] }} Teacher: {{ $currentClass['fname'] }} |
</div>
#endforeach
#endforeach