ClearPeople logo

Export data to CSV with Typescript without format issues

31 January 2020
  

On many occasions, users need to export the information shown in a table or list on our website and without a doubt, it is our responsibility to facilitate this task. And what better way than allowing to export the information to CSV with a single click.

 

Therefore, today I will teach you to create a very simple and reusable control based on Typescript to be able to generate CSV documents directly from any browser.

Let's get to the point, the method we have created is this:

export const exportToCsv = (filename: string, rows: object[], headers?: string[]): void => {
if (!rows || !rows.length) {
return;
}
const separator: string = ",";

const keys: string[] = Object.keys(rows[0]);

let columHearders: string[];

if (headers) {
columHearders = headers;
} else {
columHearders = keys;
}

const csvContent =
"sep=,\n" +
columHearders.join(separator) +
'\n' +
rows.map(row => {
return keys.map(k => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k];

cell = cell instanceof Date
? cell.toLocaleString()
: cell.toString().replace(/"/g, '""');

if (cell.search(/("|,|\n)/g) >= 0) {
cell = `"${cell}"`;
}
return cell;
}).join(separator);
}).join('\n');

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // In case of IE 10+
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
if (link.download !== undefined) {
// Browsers that support HTML5 download attribute
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}

In summary, this method received 3 parameters:
  • filename: A string that specifies the name of the file that we are going to generate (don't forget includes the .csv extension)

  • rows: An object array with the information that we are going to export. Although it is an array of objects, I recommend that they all be of the same class and not complex. Keep in mind that all the attributes of that object will be exported to the csv.

  • headers: This is an optional parameter, which allows us to indicate the name that each column of the final document will have. It is important that this array of strings is ordered so that the headers match the values. If we do not include this parameter, the name of the row attributes will be used as headers


OK. That is all? Well ... not exactly. 

This code will help you in most cases for almost all browsers, however, there is a case that will give you problems: Internet Explorer and date formats.

 

And if our data includes a formatted date, downloading it from most browsers will look correctly:

 

Export data to CSV with Typescript without format issues 1

However, from Internet Explorer we will get this:

 

Export data to CSV with Typescript without format issues 2

It seems that Internet Explorer adds some extra characters when encoding formatted dates. The explanation of this issue with Internet Explorer is complex, so if you are interested in knowing more I recommend you read this interesting article.

 

Ok… And what is the solution?

 

After a long investigation on how Internet Explorer encodes the dates, I must say that the best solution I have found for this issue (apart from ignoring it), is to eliminate the invisible characters that Internet Explorer adds to the dates. To do this, just modify the above method a bit and add this:

if (navigator.msSaveBlob) {
cell = cell.replace(/[^\x00-\x7F]/g, ""); //remove non-ascii characters
}

 

This piece of code will eliminate all non-ascii characters, making sure that all those unexpected features added by Internet Explorer are eliminated.

 

Therefore, the final code will be:

 

 

 

export const exportToCsv = (filename: string, rows: object[], headers?: string[]): void => {
if (!rows || !rows.length) {
return;
}
const separator: string = ",";

const keys: string[] = Object.keys(rows[0]);

let columHearders: string[];

if (headers) {
columHearders = headers;
} else {
columHearders = keys;
}

const csvContent =
"sep=,\n" +
columHearders.join(separator) +
'\n' +
rows.map(row => {
return keys.map(k => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k];

cell = cell instanceof Date
? cell.toLocaleString()
: cell.toString().replace(/"/g, '""');

if (navigator.msSaveBlob) {
cell = cell.replace(/[^\x00-\x7F]/g, ""); //remove non-ascii characters
}
if (cell.search(/("|,|\n)/g) >= 0) {
cell = `"${cell}"`;
}
return cell;
}).join(separator);
}).join('\n');

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // In case of IE 10+
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
if (link.download !== undefined) {
// Browsers that support HTML5 download attribute
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}

 

Note: You should keep in mind that if the data you are going to export includes non-ascii characters you will have to make some extra modifications to allow these characters.

 

Now our method is complete, it only remains to call it from wherever you consider.  You can check the full method here.

 

Have a nice day and may the power of the CSV be with you!

Author bio

Jorge Garcia

Jorge Garcia

I develop websites with Sitecore and mobile apps for iOS, Android and Universal Windows Platform with Xamarin. I love developing apps and investigating exciting new ways to engage users. When I'm not in front of the computer, you'll find me practicing magic tricks or reading a great book.

View all articles by this author