AngularJS Datatables insert Table-Footer with column totals - angularjs

Do you know how I can easily insert a simple footer (tfoot) into my datatable?
I have build my Datatable in my Angular-Controller like this:
function Controller(DTOptionsBuilder, DTColumnBuilder, $compile) {
var ctrl = this;
ctrl.totals = {};
var initDatatable = function() {
ctrl.dtInstance = {};
ctrl.dtColumns = [
DTColumnBuilder.newColumn('id', 'Id'),
DTColumnBuilder.newColumn('title', 'Name').renderWith(actionsHtml),
// ... some columns here
];
var renderDatatable = function() {
ctrl.dtOptions = DTOptionsBuilder
.newOptions()
.withFnServerData(serverData)
.withDataProp('data')
.withOption('processing', true)
.withOption('serverSide', true)
.withOption('createdRow', createdRow)
.withOption('fnFooterCallback', footerCallback);
function createdRow(row, data, dataIndex) {
$compile(angular.element(row).contents())($scope);
}
function serverData(sSource, aoData, fnCallback, oSettings) {
// get data from Server by ajax
// and then sum the total values
ctrl.total.col1 += data1;
ctrl.total.col2 += data2;
ctrl.total.col3 += data3;
var records = {
'draw': draw,
'recordsTotal': entries.length,
'recordsFiltered': entries.length,
'data': entries // filtered data entries
};
fnCallback(records);
}
function activateDatatable() {
initDatatable();
renderDatatable();
}
activateDatatable();
}
The Code is compressed by me to see the general structure.
Now I created the function "footerCallback()" to define the table footer, but datatables doesnt generate the <tfoot> automatically and because of that I have to create it manually into the template:
<table datatable="" dt-instance="ctrl.dtInstance" dt-options="ctrl.dtOptions" dt-columns="ctrl.dtColumns" class="table table-striped table-bordered">
<tfoot>
<tr>
<th></th>
</tr>
</tfoot>
</table>
After a long time of research I find out that I should be generate every th-tag into the footer but I couldn't get my total values from serverData() to insert in the cells.
Could everyone help me?

i know its an old one, but for completion:
to use the footerCallback you have to change this datatables plugin sum to angularjs way. (there is a similar way with drawCallback)
.withOption('footerCallback', function () {
var api = this.api();
total = api.column(5).data()
.flatten()
.reduce( function (a,b) {
if ( typeof a === 'string' ) {a = a.replace(/[^\d.-]/g, '') * 1;}
if ( typeof b === 'string' ) {b = b.replace(/[^\d.-]/g, '') * 1;}
return a + b;
}, 0);
$( api.columns(5).footer() ).html( total );
})
you even can use render for the footer, to keep number localization (and currency). for example with the help of the datatables plugin intl
var renderit = $.fn.dataTable.render.intlNumber('fr', {style: 'currency',currency: 'EUR'} );
$( api.columns(5).footer() ).html( renderit(total,'display') );

Related

Angular.forEach converting values to use in sort table

I'm trying to sort through my list in Angular, but it's not working.
The default sort order should be by Fiscal Year descending, but some 2019 values are being sorted after 2020 values:
https://i.imgur.com/1F9JM2V.png
Then numbers are being sorted incorrectly when you click on that column sort:
https://i.imgur.com/67fMJ8V.png
And the end date has no sorting structure. I am setting it to MM/dd/yyyy format in the view, not sure if that has any bearing:
https://i.imgur.com/dOcnFBt.png
Only tried reverse and descending orders. Not sure if there are any syntax or built-in ways of sorting.
Controller:
function init() {
$scope.loading = true;
$scope.rfrorder = {
Orderby: 'rfrFY',
descending: false
};
ContractsService.getRefRFRInformation()
.then(function (results) {
$scope.refRFRInfo = results.data;
angular.forEach($scope.refRFRInfo, function (value) {
value.edit = true;
value.editMode = false;
if (value.endDate == null) {
value.edit = false;
}
});
$scope.loading = false;
});
}
$scope.rfrSorting = function (column) {
var sort = $scope.rfrorder;
if (sort.Orderby == column) {
sort.descending = !$scope.rfrorder.descending;
} else {
sort.Orderby = column;
sort.descending = false;
}
};
$scope.rfrselected = function (column) {
if (column == $scope.rfrorder.Orderby) {
return ('tablesort-icon glyphicon glyphicon-arrow-' + (($scope.rfrorder.descending) ? 'down' : 'up'));
}
else {
return 'tablesort-icon glyphicon glyphicon-sort';
}
};
View:
<thead class="headercolor">
<tr class="thead">
<th ng-click="rfrSorting('rfrFY')"><div class="tablesort-header">RFR FY <i ng-class="rfrselected('rfrFY')"></i></div></th>
<th ng-click="rfrSorting('rfrNumber')"><div class="tablesort-header">RFR Number <i ng-class="rfrselected('rfrNumber')"></i></div></th>
<th ng-click="rfrSorting('rfrEffectiveDate')"><div class="tablesort-header">Effective Date <i ng-class="rfrselected('rfrEffectiveDate')"></i></div></th>
<th ng-click="rfrSorting('rfrEndDate')"><div class="tablesort-header">End Date <i ng-class="rfrselected('rfrEndDate')"></i></div></th>
<th ng-click="rfrSorting('rfrModifiedDate')"><div class="tablesort-header">Modified Date <i ng-class="rfrselected('rfrModifiedDate')"></i></div></th>
<th ng-click="rfrSorting('rfrModifiedBy')"><div class="tablesort-header">Modified By <i ng-class="rfrselected('rfrModifiedBy')"></i></div></th>
<th></th>
</tr>
</thead>
<tbody class="form-group form-group-sm">
<tr ng-repeat-start="rfrDetail in refRFRInfo | orderBy:rfrorder.Orderby:rfrorder.descending">
EDIT
I believe it has to do with the numbers coming back as string. I'm trying to find a way that will go through and convert the numbers, then put them back in an object I can display on my view.
ContractsService.getRefRFRInformation()
.then(function (results) {
$scope.refRFRInfo = results.data;
angular.forEach($scope.refRFRInfo, function (value) {
//$scope.model.refRFRInfo.rfrNumber = parseInt(value.rfrNumber);
//$scope.model.refRFRInfo.rfrFY = parseInt(value.rfrFY);
//$scope.model.refRFRInfo.endDate = Date.parse(value.endDate);
$scope.number.push(value.rfrNumber);
value.edit = true;
value.editMode = false;
//new Date(value.startDate).withoutTime() <= new Date().withoutTime() &&
if (value.endDate == null) {
// value.editMode = true;
value.edit = false;
}
});
$scope.loading = false;
});
I understand the basic principle as I have $scope.number for a validation, but I don't know how to iterate through the entire object and then create a new object with the proper values.
The problem is with your sorting code.
Try looking at this post:
AngularJS sorting rows by table header

highlighting previous row after ng-click

I have a dropdownlist which contains brand ids. acccording to the id im fetching corresponding products and showing it in a table. There are two buttons in each row that move the products up and down basically by interchanging the ranks. now i am able to do all the functionality of interchanging and re binding.The row is selected when it is clicked. my only problem is i am not able to select the row after it has moved up or down.
<div ng-app="myapp" ng-controller="prodctrl">
<select id="BrandDropdown" class="InstanceList" ng-change="GetBrandProd()" ng-model="Products">
<option>Select Brand</option> //Sample Data
<option value=1>Brand 1<option>
<option value=2>Brand 2<option>
</select>
<table id="prodtab" ng-model="Products">
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
<td>{{P.Id}}</td>
<td>{{P.Rank}}</td>
<td>{{P.Name}}</td>
<td>
<input type="button" value="Move Up" id="moveup" ng-click="getval(P,$index)" /></td>
<td>
<input type="button" value="Move Down" /></td>
</tr>
</table>
</div>
this is the angularjs code
<script>
var app = angular.module('myapp', []);
var prod = null;
var mveup = null;
var mvedwn = null;
var ind = null;
app.controller('prodctrl', function ($scope, $http) {
//getting products for each brand
$scope.GetBrandProd = function () {
cursel = "B";
var Id = $('#BrandDropdown').val();
fetchtype = Id;
brid = Id;
$http({
method: "GET",
url: "/Home/GetProdBrand",
params: {
id: Id
}
})
.success(function (response) {
var data = response;
$scope.Products = data;
prod = data;
});
};
//changing color of row when clicked
$scope.setselected = function (index) {
if ($scope.lastSelected) {
$scope.lastSelected.selected = '';
}
if (mveup == null) {
this.selected = 'trselected';
$scope.lastSelected = this;
}
else {
mveup = null;
//this.selected = '';
$(this).closest('tr').prev().prop('Class', 'trselected');
}
};
//function to move product up in ranking
$scope.getval = function (p, index) {
var Idcur = p.Id;
var Rankcur = p.Rank;
ind = index;
if ($scope.Products[index - 1] != null) {
var IdPrev=$scope.Products[index - 1].Id;
var Rankprev = $scope.Products[index - 1].Rank;
mveup = null;
$scope.lastSelected = this;
if (cursel == "B") {
fetchtype = brid;
}
else if (cursel == "C") {
}
mveup = true;
$http({
method: "GET",
url: "/Home/MoveProd",
params: {
Curid: Idcur,
CurRank: Rankcur,
ChngId: IdPrev,
ChngRnk: Rankprev,
Type: cursel,
Id: fetchtype
}
})
.success(function (response) {
// ranks are interchanged and the data is returned.
var data = response;
$scope.Products = data;
prod = data;
});
}
}
})
</script>
It seems, the way you are handling the row selection is not correct.
I have just changed the way of handling selection here.
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" ng-class="{selected: selectedIndex == $index}">
//JS
$scope.setselected = function(index) {
$scope.selectedIndex = index;
};
Also, I have done a plunker with some sample values to imitate your requirement, you can ask more, if it is not fit to your requirement.
Plunker
You already have the id of the product that was clicked on (I think from looking at your code, it's Idcur), so you could loop over your results in the success block of the /Home/MoveProd GET request and set the record with the matching id to selected? Something like
var products = $scope.Products.filter(function(product) {
return product.id == Idcur;
})
if (products && products.length > 0) {
products[0].selected = 'trselected';
}
then, in your page, just update the ng-repeat slightly to pick the selected class from the product, instead of the scope, so:
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
becomes
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{P.selected}}">
or something like that :)

How Exporting a table to Excel?

what is the best way of this? I have table and it's data coming from api. I used angularJs for data. Here my source codes:
Here's my table in Index:
<div style="width:1100px; max-height:480px; overflow:scroll; border-top:1px solid #808080">
<table class="table">
<tr>
<th>AdaNo</th>
<th>ParselNo</th>
<th>CiltNo</th>
<th>SayfaNo</th>
<th>ZeminTip</th>
<th>İl</th>
<th>İlçe</th>
<th>Mahalle</th>
<th>Kurum</th>
<th>Alan</th>
<th>Pay</th>
<th>Nitelik</th>
<th>EdinmeSebep</th>
<th>Ad</th>
<th>SoyAd</th>
<th>TcKimlikNo</th>
<th>HissePay</th>
<th>HissePayda</th>
</tr>
<tr data-ng-repeat="zemin in Zeminler">
<td>{{zemin.AdaNo}}</td>
<td>{{zemin.ParselNo}}</td>
<td>{{zemin.CiltNo}}</td>
<td>{{zemin.SayfaNo}}</td>
<td>{{zemin.ZeminTip.Ad}}</td>
<td>{{zemin.Il.Ad}}</td>
<td>{{zemin.Ilce.Ad}}</td>
<td>{{zemin.Mahalle.Ad}}</td>
<td>{{zemin.Kurum.Ad}}</td>
<td>{{zemin.AnaTasinmaz.Alan}}</td>
<td>{{zemin.KatMulkiyeti.ArsaPay}}/{{zemin.KatMulkiyeti.ArsaPayda}}</td>
<td>{{zemin.AnaTasinmaz.Nitelik}}</td>
<td>{{zemin.Hisseler[0].EdinmeSebep}}</td>
<td>{{zemin.Hisseler[0].Kisi.GercekKisi.Ad}}{{zemin.Hisseler[0].Kisi.TuzelKisi.Ad}}</td>
<td>{{zemin.Hisseler[0].Kisi.GercekKisi.SoyAd}}</td>
<td>{{zemin.Hisseler[0].Kisi.GercekKisi.TcKimlikNo}}</td>
<td>{{zemin.Hisseler[0].HissePay}}</td>
<td>{{zemin.Hisseler[0].HissePayda}}</td>
</tr>
</table>
</div>
Here's my Apis for this Data:
[Route("api/TapuZeminApi/GetZemins")]
[HttpPost]
public string GetZeminsFromZeminArg(object arg)
{
ZeminArg zemArg = SConvert.DeserializeJSON<ZeminArg>(arg.ToString());
List<TapuZeminModel> zeminList = TapuModule.GetZeminListFromArgs(zemArg);
string jsonResult = SConvert.SerializeJSON(zeminList);
return jsonResult;
}
// GET api/<controller>/5
public string GetZeminsFromTcNo(long id)
{
List<TapuZeminModel> zeminList = TapuModule.GetZeminListFromTcNo(id.ToString());
string jsonResult = SConvert.SerializeJSON(zeminList);
return jsonResult;
}
public string GetZeminsFromKurumId(long id)
{
List<TapuZeminModel> zeminList = TapuModule.GetZeminListKurumId(id);
string jsonResult = SConvert.SerializeJSON(zeminList);
return jsonResult;
}
Article
I used it in my solution like:
First made ExportToExcelService.Js:
angular.module('TapuAppModule')
.factory('Excel', function tapuExcelFactory($window) {
var uri = 'data:application/vnd.ms-excel;base64,',
template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>',
base64 = function (s) { return $window.btoa(unescape(encodeURIComponent(s))); },
format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) };
return {
tableToExcel: function (tableId, worksheetName) {
alert('Olcak olcak')
var table = $(tableId),
ctx = { worksheet: worksheetName, table: table.html() },
href = uri + base64(format(template, ctx));
return href;
}
};
})
and here my controller tapuController.js:
$scope.exportToExcel = function (tableId) {
$scope.exportHref = Excel.tableToExcel(tableId, 'sheet name');
$timeout(function () { location.href = $scope.fileData.exportHref; }, 100); // trigger download
};
and my button like this in Index:
<button class="btn btn-link" ng-click="exportToExcel('#dataTable')">
<span class="w-icon-page-excel"></span> Export to Excel
</button>
When I click button I'm getting alert that in ExportToExcelService.Js 'Olcak Olcak'. So I think it can be triggered method in ExportToExcelService.Js. Then I got this error like at console in Chrome:
TypeError: Cannot read property 'exportHref' of undefined
at tapuController.js:81
at angular.js:17682
at completeOutstandingRequest (angular.js:5387)
at angular.js:5659
Why I'm getting this error? What should I do?
Try to change the location.href value by $scope.exportHref; without fileData.
$timeout(function () { location.href = $scope.exportHref; }, 100); // trigger download
that works for me.

Angular binding updates when service provides unfiltered array but doesn't when using Underscore

I have an angular service which contains two function to return an array of objects. The first returns the entire array, and the second a filtered array based on an ID. When I use the first function returning all the objects, data-binding works automatically and my front end updates as soon as I push a new object to the array. However when I use the function that returns a filtered list using underscore, my frontend doesn't update automatically.
I've done some research and the only thing similar to this that I have seen is in relation to async requests and using promises, but I'm not sure if this is appropriate in this case as I'm only using in service objects currently.
Service
angular.module('RankingsApp')
.service('results', function() {
var uid = 2;
var results = [
{
"id":1,
"fencer":1,
"competition":1,
"placing":1,
"points":50
}
];
this.getResults = function()
{
return results;
}
this.getResultsForCompetition = function (_id)
{
var resultsForCompetition = _.filter(results, function(x){ return x.competition == _id});
return resultsForCompetition;
};
this.insertResult = function (result) {
result.id = uid++;
results.push(result);
};
});
Controller
angular.module('RankingsApp')
.controller('CompetitionCtrl', function ($scope, competitions,fencers,results, $routeParams) {
$scope.getResults = function()
{
return results.getResultsForCompetition($routeParams.competitionID);
}
$scope.competition = competitions.getCompetition($routeParams.competitionID);
$scope.fencers = fencers.getFencers();
$scope.compResults = results.getResultsForCompetition($routeParams.competitionID);
function getNextPlacing()
{
return $scope.compResults.length + 1;
}
$scope.getFencerFromResult = function(result)
{
return fencers.getFencer(result.fencer);
}
$scope.getCompFromResult = function(result)
{
return competitions.getCompetition(result.competition);
}
$scope.addNewResult = function(fencer)
{
var result = { "fencer": fencer.id, "competition":$routeParams.competitionID, "placing": getNextPlacing(), "points":50 };
results.insertResult(result);
$scope.selectedFencer = null;
}
});
View
<table style="width: 100%">
<thead>
<tr>
<th>Placeing</th>
<th>Fencer</th>
<th>Comp</th>
<th>Points</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='result in compResults'>
<td>{{result.placing}}</td>
<td>{{getFencerFromResult(result).firstname}} {{getFencerFromResult(result).lastname}}</td>
<td>{{getCompFromResult(result).shortName}}</td>
<td>{{result.points}}</td>
<td><a>Edit</a></td>
</tr>
</tbody>
</table>
It's because your method (with _.filter()) returns another array than what your view in the frontend was bind to (as binding is done by reference in case of an Array or an Object).
To solve this, you may place filtering logic in views and use ng-repeat.
If it's not an option, you should directly modify the results variable in the service by using pop()/push() methods.
Update:
<tr ng-repeat='result in compResults'>
should be
<tr ng-repeat='result in compResults | filter{ competition: _id }'>
where
$scope.compResults = results.getResults();
and
$scope._id = $routeParams.competitionID;
found here
Using the advice posted by #Mironor I was able to come up with the following solution which solves my issue. By changing the ng-repeat to call the function directly the list updates itself when I push a new value to the service.
View
<tr ng-repeat='result in getResultsForCompetition(competitionID)'>

AngularJS + Datatables + Dropdownlist

I'm using AngularJS to populate my datatable. What I want to know is how can I populate the datatable based on the dropdownlist
This is my dropdownlist
<div>
Get Users with Role:
<select id="ddlRole" data-ng-model="selectedRole" data-ng-change="populateDataTable()" data-ng-options="v.name for (k,v) in roles"></select>
<input type="hidden" value="{{selectedRole}}" />
</div>
This is my angular code
$scope.roles = [
{name: 'XXX' },
{name: 'YYY' }
];
$scope.selectedRole = $scope.roles[0];
//onchange event
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
RefreshDataTable(); //TODO
};
How can I change this to make an ajax call to retreive the data, populate the datatable based on the dropdownlist value and retain the value of dropdownlist as well.
I'm sure we can do this using JQuery but I dont want to mix these and make a mess. Is there any way I can acheive this using AngularJS?
Here is a simple data table directive:
appModule.directive('dataTable', [function () {
return function (scope, element, attrs) {
// apply DataTable options, use defaults if none specified by user
var options = {};
if (attrs.myTable.length > 0) {
options = scope.$eval(attrs.myTable);
} else {
options = {
"bStateSave": true,
"iCookieDuration": 2419200, /* 1 month */
"bJQueryUI": true,
"bPaginate": false,
"bLengthChange": false,
"bFilter": false,
"bInfo": false,
"bDestroy": true
};
}
// Tell the dataTables plugin what columns to use
// We can either derive them from the dom, or use setup from the controller
var explicitColumns = [];
element.find('th').each(function (index, elem) {
explicitColumns.push($(elem).text());
});
if (explicitColumns.length > 0) {
options["aoColumns"] = explicitColumns;
} else if (attrs.aoColumns) {
options["aoColumns"] = scope.$eval(attrs.aoColumns);
}
// aoColumnDefs is dataTables way of providing fine control over column config
if (attrs.aoColumnDefs) {
options["aoColumnDefs"] = scope.$eval(attrs.aoColumnDefs);
}
if (attrs.fnRowCallback) {
options["fnRowCallback"] = scope.$eval(attrs.fnRowCallback);
}
// apply the plugin
var dataTable = element.dataTable(options);
// watch for any changes to our data, rebuild the DataTable
scope.$watch(attrs.aaData, function (value) {
var val = value || null;
if (val) {
dataTable.fnClearTable();
dataTable.fnAddData(scope.$eval(attrs.aaData));
}
});
if (attrs.useParentScope) {
scope.$parent.dataTable = dataTable;
} else {
scope.dataTable = dataTable;
}
};
}]);
Then initialize it in your controller. Override fnServerData method, append your selected value (selected role) and filter data on server side.
$scope.overrideOptions = {
"bStateSave": true,
"iDisplayLength": 8,
"bProcessing": false,
"bServerSide": true,
"sAjaxSource": 'Data/Get',
"bFilter": false,
"bInfo": true,
"bLengthChange": false,
"sServerMethod": 'POST', ,
"fnServerData": function(sUrl, aoData, fnCallback, oSettings) {
var data = {
dataTableRequest: aoData,
selectedDropDownValue: $scope.selectedRole
};
$http.post(sUrl, data).success(function (json) {
if (json.sError) {
oSettings.oApi._fnLog(oSettings, 0, json.sError);
}
$(oSettings.oInstance).trigger('xhr', [oSettings, json]);
fnCallback(json);
});
}
};
var columnDefs = [
{
"mData": "id",
"bSortable": false,
"bSearchable": false,
"aTargets": ['tb-id']
},
{
"mData": "data",
"aTargets": ['tb-data']
}
];
Refresh the datatable on select change.
$scope.populateDataTable = function () {
if ($scope.dataTable) {
$scope.dataTable.fnDraw();
}
};
Html markup:
<table class="display m-t10px" data-table="overrideOptions" ao-column-defs="columnDefs">
<thead>
<tr>
<th class="tb-id"></th>
<th class="tb-data></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Hope above your code is in controller.
Inject $http and make a $http get or post call
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
$http.get('api/controller', function(result){
//response from the service call in result
});
};

Resources