Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

AngularJS ng-grid Export to CSV in IE Workaround

5.00/5 (1 vote)
10 Jun 2015CPOL2 min read 17.5K  
AngularJS ng-grid export to CSV workaround for Internet Explorer to avoid showing Save dialog box.

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

JavaScript
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;
        //You can ignore and remove baseURL if you are using some other path
        baseURL = scope.baseUrl;
        function showDs() {
            var keys = [];
            var displays = [];
            var cellfilter = [];

            //Storing all cells filters in array to format them while exporting to CSV e.g. Date, Currency, Number etc
            for (var cf in grid.config.columnDefs) { cellfilter.push(grid.config.columnDefs[cf].cellFilter); }

            //Storing all Cells title displayed in Grid in array to export in CSV
            for (var d in grid.config.columnDefs) { displays.push(grid.config.columnDefs[d].displayName); }
            
            //Storing all Cells keys (database table column bind with ng-grid) 
            for (var f in grid.config.columnDefs) { keys.push(grid.config.columnDefs[f].field); }
            var csvData = '';
 
            //Function to format the data depend on Cell format e.g. Currency, Date
            function csvStringify(keyName, str) {

                if (str == null) { // we want to catch anything null-ish, hence just == not ===
                    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);
            }

            //Creating HTML that would be added at end of ng-grid in footer with Export Button
            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 to call MVC Action that will store data in session or any other container and send OK
//Response, once response come back it will other function that will download CSV file.
function ExportJsontoCSV() {

    $.ajax({
        //Double check baseURL if you have it, otherwise use your own code, this is simple JQuery POST
        url: baseURL + "CSVExport/ExporttoCSV/",
        type: "POST",
        data: { 'data': CSVDataIE },

        success: function () {
            window.location = baseURL + "CSVExport/DownloadCSVFile/";
        }

    });

}

//Function borrowed from www.willmaster.com/library/generators/currency-formatting.php to format Currency
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:

C#
 //Storing CSV data sent from client into Session variable, this is only good for small data  
 public void ExporttoCSV(string data)
        {
            Session["ExportData"] = data;
        }

//Once data is successfully saved in Session, control would go back to JQuery Post call with
//OK response where window.location has DownloadCSVFile action URL. 
   [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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)