save-csv
Version:
Download an array of objects as a CSV file in the browser
110 lines (97 loc) • 3.54 kB
JavaScript
/*! save-csv v4.1.0 | (c) silverwind | BSD license */
(function(root, m) {
if (typeof define === "function" && define.amd) {
define([], m);
} else if (typeof module === "object" && module.exports) {
module.exports = m();
} else {
root.saveCsv = m();
}
})(typeof self !== "undefined" ? self : this, function() {
"use strict";
return function(arr, opts) {
if (!Array.isArray(arr) || !arr.length) {
throw new Error("Expected an array of values, got " + arr);
}
opts = opts || {};
opts.filename = opts.filename || "export.csv";
opts.sep = opts.sep || "auto";
opts.eol = opts.eol || "\r\n";
opts.bom = typeof opts.bom === "boolean" ? opts.bom : true;
opts.quote = opts.quote || '"';
opts.mime = opts.mime || "text/csv;charset=utf-8";
// This is not ideal, but given that Array.prototype.toLocaleString is
// rather unreliable, it's a good compromise.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1388777
// https://bugs.chromium.org/p/chromium/issues/detail?id=753841
if (opts.sep === "auto") {
if ("toLocaleString" in Number.prototype) {
opts.sep = (1.2).toLocaleString().substring(1, 2) === "," ? ";" : ",";
} else {
opts.sep = ",";
}
}
var quoteRe = new RegExp(opts.quote, "g");
var sepRe = new RegExp(opts.sep, "g");
opts.formatter = opts.formatter || function(value) {
var quoted = false;
if (typeof value !== "string") {
value = JSON.stringify(value) || "";
}
// escape quotes by doubling the quotes and wrapping in quotes
if (quoteRe.test(value)) {
value = opts.quote + value.replace(quoteRe, opts.quote + opts.quote) + opts.quote;
quoted = true;
}
// escape separator by wrapping in quotes
if (sepRe.test(value) && !quoted) {
value = opts.quote + value + opts.quote;
}
return value;
};
// build headers from first element in array
var paths = [];
(function scan(prefix, obj, keys) {
keys.forEach(function(key) {
var path = prefix ? prefix + "." + key : key;
if (typeof obj[key] === "object" && obj[key] !== null) {
scan(path, obj[key], Object.keys(obj[key]));
} else {
paths.push(opts.formatter(path));
}
});
})(null, arr[0], Object.keys(arr[0]));
var header = paths.join(opts.sep) + opts.eol;
// build body
var body = arr.map(function(obj) {
var row = [];
(function scan(obj, keys) {
keys.forEach(function(key) {
if (typeof obj[key] === "object" && obj[key] !== null) {
scan(obj[key], Object.keys(obj[key]));
} else {
row.push(opts.formatter(obj[key]));
}
});
})(obj, Object.keys(obj));
return row.join(opts.sep);
}).join(opts.eol);
// build a link and trigger a download
var text = header + body;
var blob = new Blob([opts.bom ? "\ufeff" + text : text]);
if (window.navigator.msSaveOrOpenBlob) { // compat: ie10
window.navigator.msSaveOrOpenBlob(blob, opts.filename);
} else {
var a = document.createElement("a");
a.setAttribute("href", URL.createObjectURL(blob, {type: opts.mime}));
a.setAttribute("download", opts.filename);
a.setAttribute("target", "_blank");
a.style.display = "none";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
}, 0);
}
};
});