Introduction
If you are still using AngularJS ng-grid and not planning to move to ui-grid soon due to IE 9 support or any other reason, this small tip may help you for smooth export to excel functionality in all browser without showing iritating Save dialog box for IE. I am using MVC controller on server side that can be easily replaced with other server side language if you are PHP, Java or some other technology developer.
Please remeber, this solution is good for only thousand or more records cuase session or any temporary container is needed to be use to hold CSV string.
Background
You can easily find ng-grid plugin for Export to CSV functionality in google or from following blog that I found best so far for my application:
http://www.code-sample.com/2014/12/export-ng-grid-data-to-excel-csv-and.html
The code in above given blog works perfectly in Firefox and Chrome but not in IE 9.
To make it works in IE, code from following post can be used that is using execCommand
for CSV export. You need to detect browser and if it is IE use code from following blog:
https://github.com/angular-ui/ng-grid/issues/2312
The final output in IE will end up showing dialog box asking user to Save or Cancel exported CSV whereas in Firefox it would immediately download without prompting.
This is how you specify plugin for ng-grid in case you do not know already:
http://stackoverflow.com/questions/18894356/exporting-ng-grid-data-to-csv-and-pdf-format-in-angularjs
Lets Start
The basic code is taken from above mention URL, thanks to Anil Singh. Following is my updated code with description as comments:
ng-grid-csv-export.js
var CSVDataIE = '';
var baseURL = '';
function ngGridCsvExportPlugin(opts) {
var self = this;
self.grid = null;
self.scope = null;
self.init = function (scope, grid, services) {
self.grid = grid;
self.scope = scope;
baseURL = scope.baseUrl;
function showDs() {
var keys = [];
var displays = [];
var cellfilter = [];
for (var cf in grid.config.columnDefs) { cellfilter.push(grid.config.columnDefs[cf].cellFilter); }
for (var d in grid.config.columnDefs) { displays.push(grid.config.columnDefs[d].displayName); }
for (var f in grid.config.columnDefs) { keys.push(grid.config.columnDefs[f].field); }
var csvData = '';
function csvStringify(keyName, str) {
if (str == null) {
return '';
}
if (typeof (str) === 'number') {
if (keyName == "currency")
return "$" + CurrencyFormat(str);
else if (keyName == "percentage:2")
return (str * 100) + "%";
else
return str;
}
if (typeof (str) === 'boolean') {
return (str ? 'TRUE' : 'FALSE');
}
if (typeof (str) === 'string') {
if (keyName == 'date:"M/dd/yyyy"') {
return str.substring(0, 10);
}
else {
return str.replace(/"/g, '""');
}
}
return JSON.stringify(str).replace(/"/g, '""');
}
function swapLastCommaForNewline(str) {
var newStr = str.substr(0, str.length - 1);
return newStr + "\n";
}
for (var d in displays) {
csvData += '"' + csvStringify("", displays[d]) + '",';
}
csvData = swapLastCommaForNewline(csvData);
var gridData = grid.data;
var cntr = 0;
for (var gridRow in gridData) {
for (k in keys) {
var curCellRaw;
if (opts != null && opts.columnOverrides != null && opts.columnOverrides[keys[k]] != null) {
curCellRaw = opts.columnOverrides[keys[k]](gridData[gridRow][keys[k]]);
}
else {
curCellRaw = gridData[gridRow][keys[k]];
}
csvData += '"' + csvStringify(cellfilter[cntr], curCellRaw) + '",';
cntr++;
}
cntr = 0;
csvData = swapLastCommaForNewline(csvData);
}
var fp = grid.$root.find(".ngFooterPanel");
var csvDataLinkPrevious = grid.$root.find('.ngFooterPanel .csv-data-link-span');
if (csvDataLinkPrevious != null) { csvDataLinkPrevious.remove(); }
var csvDataLinkHtml = "<span class=\"csv-data-link-span\">";
csvDataLinkHtml += "<br><a onclick='ExportJsontoCSV()'><button type='button' class='btn btn-primary'><span class='glyphicon glyphicon-export'></span>Export</button></a></br></span>";
CSVDataIE = csvData;
fp.append(csvDataLinkHtml);
}
setTimeout(showDs, 0);
scope.catHashKeys = function () {
var hash = '';
for (var idx in scope.renderedRows) {
hash += scope.renderedRows[idx].$$hashKey;
}
return hash;
};
scope.$watch('catHashKeys()', showDs);
function isDate(date) {
return ((new Date(date) !== "Invalid Date" && !isNaN(new Date(date))));
}
};
}
function ExportJsontoCSV() {
$.ajax({
url: baseURL + "CSVExport/ExporttoCSV/",
type: "POST",
data: { 'data': CSVDataIE },
success: function () {
window.location = baseURL + "CSVExport/DownloadCSVFile/";
}
});
}
function CurrencyFormat(number) {
var decimalplaces = 2;
var decimalcharacter = ".";
var thousandseparater = ",";
number = parseFloat(number);
var sign = number < 0 ? "-" : "";
var formatted = new String(number.toFixed(decimalplaces));
if (decimalcharacter.length && decimalcharacter != ".") { formatted = formatted.replace(/\./, decimalcharacter); }
var integer = "";
var fraction = "";
var strnumber = new String(formatted);
var dotpos = decimalcharacter.length ? strnumber.indexOf(decimalcharacter) : -1;
if (dotpos > -1) {
if (dotpos) { integer = strnumber.substr(0, dotpos); }
fraction = strnumber.substr(dotpos + 1);
}
else { integer = strnumber; }
if (integer) { integer = String(Math.abs(integer)); }
while (fraction.length < decimalplaces) { fraction += "0"; }
temparray = new Array();
while (integer.length > 3) {
temparray.unshift(integer.substr(-3));
integer = integer.substr(0, integer.length - 3);
}
temparray.unshift(integer);
integer = temparray.join(thousandseparater);
return sign + integer + decimalcharacter + fraction;
}
Following is code in MVC controller:
public void ExporttoCSV(string data)
{
Session["ExportData"] = data;
}
[HttpGet]
public ActionResult DownloadCSVFile()
{
try
{
var result = File(ASCIIEncoding.ASCII.GetBytes(Session["ExportData"].ToString()), "text/csv", "ExportCSV_" + Guid.NewGuid() + ".csv");
Session["ExportData"] = null;
return result;
}
finally
{
Session["ExportData"] = null;
}
}
Points of Interest
Its fast, easy and do not open new window like other code available in different blogs that redirect or use window.open for downloading CSV file. I still waiting when Microsoft will abandon IE so that we could so much time in fixing these kind of issues only for IE :)
History
Created: 6/10/2015