Как сгенерировать файл для скачивания налету и указать имя при скачивании в javascript

Мне нужно было сделать кнопку экспорта данных со страницы в CSV. При этом нужно было задать осмысленное название файла. Вот такой код, работающий в chome и firefox, решает эту задачу:

const csvHeader = `Type,Id`
// Получаем строку вида "значение,значение,\nзначение,значение"
const csvData = [['type1', 1], ['type2', 2]].map(a => a.join(',')).join('\n')
const csvContent = csvHeader + '\n' + csvData
const downloadFileLink = document.createElement('a')
downloadFileLink.href = 'data:text/csv;charset=utf-8,' + encodeURI(csvContent)
downloadFileLink.target = '_blank'
downloadFileLink.download = 'fancy-filename.csv'
document.body.appendChild(downloadFileLink) // без добавления в DOM не работает в FF
downloadFileLink.click()
document.body.removeChild(downloadFileLink) // подчищаем за собой узлы

Важно чтобы этот код выполнялся синхронно с кликом пользователя. Тогда пройдет трюк с искусственным кликом по виртуальной ссылке.


Статья в рамках Today I Learned формата

1 Симпатия

На днях столкнулся с подобной задачей, но нужно было еще чтоб и в ИЕ11 работало. Вот такой код подходит для этой задачи:

function downloadCSVfile(filename, data) {
    const blob = new Blob([data], {type: 'text/csv'});
    if(window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
    }
    else{
        const elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;        
        document.body.appendChild(elem);
        elem.click();        
        document.body.removeChild(elem);
    }
}
3 Симпатий

На нескольких проектах тоже требовалось подобное. По возможности, пытался делать сам кликабельный элемент ссылкой с прописанным атрибутом download. Тогда в коде останется только подменить href. И нет необходимости программно создавать/прописывать все атрибуты/добавлять в ДОМ, потом удалять из ДОМ. Немного, но сокращает код :)

Но в одном проекте были несколько ограничений

  1. доступ к ресурсам был только на сервере.
  2. составление содержимого было трудоёмкой задачей.

Сперва была попытка просто также синхронно прописывать линку на сервер с параметрами. Но в зависимости от этих параметров зависел и размер. И иногда вычисление переваливало за timeout 😅 Чисто теоретически можно было увеличить его, но решили, что такое поведение неподобающе для хорошего пользовательского опыта.