— Pixels Commander

[ In English, На русском ]

[:en]Better approach to download file in JavaScript[:ru]Лучший способ загрузки файлов в JavaScript[:]

[:en]Download file in JavaScript — what can be easier? However there are some pitfalls and there is room for improvements. This article describes how to create the best download function in JavaScript and why it`s so good.

[:ru]Скачивание файла — что может быть проще? Однако как и везде — в этом вопросе есть подводные камни и нет предела совершенству. В статье описывается лучший способ скачать файл в JavaScript.

[:][:en]

If you don`t like to dive deep into theory — you can just get library from GitHub and use downloadFile global function in your project.

Generally there are two file downloading techniques in HTML/JS: window.open and mouse click / tap on link. Both of this methods are not ideal. During investigation of the question some interesting solutions were found. Together they seems to be perfect solution for JavaScript files downloading.

Let`s start development from declaring simple function based on window.open method:

window.downloadFile = function(sUrl) {
    window.open(sUrl);
}

This function is simple and works everywhere, but have some disadvantages:

  • We`ll get useless empty window in Chrome or Safari;
  • Probably file`s content-type will command browser to show file`s content in new window and not to download it. It`s not expected behavior for downloading function.

How can we avoid this?

«_self» argument for window.open

We can avoid annoying new window opening by adding second argument to window.open:

window.downloadFile = function(sUrl) {
    window.open(sUrl, '_self');
}

 

Virtual link and virtual click

Click on link method also have not such problem as empty window in Chrome or Safari. Bad thing is that it`s user-generated event. However we can create hidden link and programmatically click on it by dispatching new mouse event.

Let`s add virtual click code for Chrome and Safari without explanation of browser detection part:

if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
    //Creating new link node.
    var link = document.createElement('a');
    link.href = sUrl;

    //Dispatching click event.
    if (document.createEvent) {
        var e = document.createEvent('MouseEvents');
        e.initEvent('click' ,true ,true);
        link.dispatchEvent(e);
        return true;
    }
}

 

HTML5 «download» attribute and content-type ignoring

What else can annoy user? For example if he tries to download HTML or PNG file and it will be opened in new browser window. I don`t like this behavior, really. And it can be avoided by using HTML5 download attribute. This attribute will tell browser that virtual link we created is aimed for download only. It will download file from link`s href to file with name specified as download attribute`s value. Sad that this great feature works in Chrome only, but 35% of happy users are serious reason to add 5 more lines of code.

Complete listing for download.js:

window.downloadFile = function(sUrl) {

    //If in Chrome or Safari - download via virtual link click
    if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
        //Creating new link node.
        var link = document.createElement('a');
        link.href = sUrl;

        if (link.download !== undefined){
            //Set HTML5 download attribute. This will prevent file from opening if supported.
            var fileName = sUrl.substring(sUrl.lastIndexOf('/') + 1, sUrl.length);
            link.download = fileName;
        }

        //Dispatching click event.
        if (document.createEvent) {
            var e = document.createEvent('MouseEvents');
            e.initEvent('click' ,true ,true);
            link.dispatchEvent(e);
            return true;
        }
    }

    // Force file download (whether supported by server).
    var query = '?download';

    window.open(sUrl + query);
}

window.downloadFile.isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
window.downloadFile.isSafari = navigator.userAgent.toLowerCase().indexOf('safari') > -1;

Demonstration

Project on GitHub[:ru]

Если вам не нужна теория о загрузке файлов в JavaScript и вы просто хотите подключить функцию downloadFile к проекту — вам на GitHub.

Как правило в HTML среде используются два пути вызова загрузки: window.open и клик по ссылке на файл. Каждый из способов по своему ущербный. В поиске универсальной техники было найдено несколько интересных решений, все вместе они составляют лучший метод для скачивания файлов из JS.

Начнем с простого метода загрузки файла через window.open:

window.downloadFile = function(sUrl) {
    window.open(sUrl);
}

Эта функция будет работать везде, но с рядом недостатков:

  • В Chrome и Safari после срабатывания останется пустое окно;
  • Если content-type файла предполагает открытие то файл не скачается, а отобразится в открывшемся окне.

Что же мы можем предпринять?

Аргумент «_self» для window.open

Убрать назойливое окно в Chrome и Safari можно добавив аргумент «_self» к window.open:

window.downloadFile = function(sUrl) {
    window.open(sUrl, '_self');
}

 

Виртуальный клик

Клик по ссылке так же не вызывает упомянутой выше проблемы с пустым окном и хоть клик — операция пользователя, нам никто не мешает создать виртуальную ссылку и виртуально по ней кликнуть сгенерировав событие мыши.

Добавим код виртуального щелчка для хрома и сафари, оставляя за кадром банальное определение браузера:

if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
    //Creating new link node.
    var link = document.createElement('a');
    link.href = sUrl;

    //Dispatching click event.
    if (document.createEvent) {
        var e = document.createEvent('MouseEvents');
        e.initEvent('click' ,true ,true);
        link.dispatchEvent(e);
        return true;
    }
}

HTML5 аттрибут download и игнорирование content-type Что еще может вызвать раздражение пользователя? Например ситуация, когда при загрузке HTML или PNG файла он открывается в браузере. Этого можно избежать добавивши к нашему виртуальному линку аттрибут download, который появился в HTML5 и служит для указания браузеру на «скачивающий» характер ссылки. Значение аттрибута — это итоговое имя скачиваемого файла, а href — исходный URL.   Итоговый код модуля download.js:

window.downloadFile = function(sUrl) {

    //If in Chrome or Safari - download via virtual link click
    if (window.downloadFile.isChrome || window.downloadFile.isSafari) {
        //Creating new link node.
        var link = document.createElement('a');
        link.href = sUrl;

        if (link.download !== undefined){
            //Set HTML5 download attribute. This will prevent file from opening if supported.
            var fileName = sUrl.substring(sUrl.lastIndexOf('/') + 1, sUrl.length);
            link.download = fileName;
        }

        //Dispatching click event.
        if (document.createEvent) {
            var e = document.createEvent('MouseEvents');
            e.initEvent('click' ,true ,true);
            link.dispatchEvent(e);
            return true;
        }
    }

    // Force file download (whether supported by server).
    var query = '?download';

    window.open(sUrl + query, '_self');
}

window.downloadFile.isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
window.downloadFile.isSafari = navigator.userAgent.toLowerCase().indexOf('safari') > -1;

 

Пример использования

Проект на GitHub[:]

20 comments
  1. Maxim Kachurovskiy says: 07.04.20136:13 пп

    The canonical way of serving file as an attachment is «Content-Disposition: attachment» header — http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 though it’s not a solution when you don’t control the server.

    Header also has one more advantage — it prevents some file types e.g. SWF from being launched/embedded which is helpful preventing a bunch of attacks.

  2. Аноним says: 07.04.20139:39 пп

    Yes, definitely, Content-Disposition is right way to go if you control server side, however it`s always good to have control from JavaScript land. Thanks for remark, I`ll edit article a bit.

  3. Александр Иванов says: 08.08.20136:08 дп

    Для того чтобы избежать открытия нового окна можно добавить вторым параметром ‘_self’ — window.open(sUrl, ‘_self’);

  4. Аноним says: 08.08.20132:19 пп

    Спасибо, добавил этот пункт в статью.

  5. Rick says: 21.12.201310:29 пп

    Thank you for this code. It works great for Chrome in Windows but with Chrome on iPad the file opens in the browser instead of downloading. Any ideas on how to fix this?

    • Аноним says: 21.12.201311:06 пп

      As I know there is no ways to download file from iPad browser. Newest snippet version have alert message in case of such attempts.

      Also seems that you need to modify headers on server side.

      Easiest way to configure headers via Apache is to set Header set Content-Disposition «attachment» for files you want to be downloaded.

      So .htaccess can look like:

      Header set Content-Disposition attachment

    • Rick says: 23.12.20131:12 пп

      Thanks for the update which informs the iPad user about downloads, it’s very helpful.

  6. anil vemula says: 17.02.20146:52 дп

    Nuvvu devudu samee…..

  7. Аноним says: 15.07.20144:41 дп

    Just opens another tab in latest Chrome…

    • Аноним says: 15.07.20147:51 дп

      Just tried to run example from repository. It works as expected. Lets determine you issue, drop me a message in skype lp_funky.

      • Аноним says: 15.07.20141:56 пп

        It works now. Used error on my part. Nice code.

  8. Gregory Goltsov says: 16.07.20142:09 пп

    Thanks for a great article! However your syntax highlighter seems to be slightly broken: in the last code snipped, the «>» sign is mangled, i.e.:


    .indexOf('chrome') & gt; -1;

    • Аноним says: 17.07.20148:27 дп

      Thanks, fixed

  9. Adam Simms says: 25.09.201411:51 пп

    Thank you so much for this!

  10. Maxim says: 24.10.20149:51 дп

    Another way is to create hidden form with action = «url-to-file» and then submit it.

  11. Hedi Herdiana says: 17.01.20159:03 дп

    Hi, it’s not working in webview cordova of android app.

  12. Andrei says: 12.09.201510:01 пп

    А возможно задать директорию для скачивающегося файла?

    • Аноним says: 12.09.201510:49 пп

      Нет, к сожалению в браузерах такая возможность не предусмотрена.

  13. sriram s says: 08.09.20169:02 дп

    Downloading file is working fine.i need to download the file with its folder-javascript

  14. G says: 08.03.201811:47 дп

    Same is not working as chrome extension. I added manifest.json. It opens the html page. Does not display
    label.innerHTML = ‘Download will start in ‘ + secondsBeforeDownloading + ‘ seconds…’;

Submit comment