Maybe there is a better way, but here's one way to do it: submit a form with the desired filename and the data as two hidden form elements. Have the server simply return the data with the appropriate headers set for a file download. No need for tmp files; works on all browsers.
HTML:
<form id="download-form" method="post">
<input type="button" value="download CSV">
</form>
<!-- the button is right above the HTML table -->
<table>... </table>
JavaScript/D3:
var jsonData;
var filenameDateFormat = d3.time.format("%Y%m%d-%H%M%S");
// ... after loading the data, and setting jsonData to the data returned from d3.csv()
jsonData = data;
// display the form/button, which is initially hidden
d3.select("#download-form").style("display", "block");
d3.select("#download-form input[type=button]").on('click', function() {
var downloadForm = d3.select("#download-form");
// remove any existing hidden fields, because maybe the data changed
downloadForm.selectAll("input[type=hidden]").remove();
downloadForm
.each(function() {
d3.select(this).append("input")
.attr({ type: "hidden",
name: "filename",
value: CHART_NAME + "-"
+ filenameDateFormat(new Date()) + ".csv"});
d3.select(this).append("input")
.attr({ type: "hidden",
name: "data",
value: convertToCsv(jsonData) });
});
document.getElementById("download-form").submit();
});
function convertToCsv(data) {
var csvArray = ['field_name1_here,field_name2_here,...'];
data.forEach(function(d) {
csvArray.push(d.field_name1_here + ',' + d.field_name2_here + ...);
});
return csvArray.join("\n");
}
Server (Python, using Bottle):
@app.route('/download', method='POST')
def download():
if request.environ.get('HTTP_USER_AGENT').find('Chrome'):
# don't add the Content-Type, as this causes Chrome to output the following
# to the console:
# Resource interpreted as Document but transferred with MIME type text/csv
pass
else:
response.set_header('Content-Type', 'text/csv')
response.set_header('Content-Disposition',
'attachment; filename="' + request.forms.filename + '"')
return request.forms.data
Not pretty, but it works.