[: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;
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;
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.
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.
Для того чтобы избежать открытия нового окна можно добавить вторым параметром ‘_self’ — window.open(sUrl, ‘_self’);
Спасибо, добавил этот пункт в статью.
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?
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
Thanks for the update which informs the iPad user about downloads, it’s very helpful.
Nuvvu devudu samee…..
Just opens another tab in latest Chrome…
Just tried to run example from repository. It works as expected. Lets determine you issue, drop me a message in skype lp_funky.
It works now. Used error on my part. Nice code.
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;
Thanks, fixed
Thank you so much for this!
Another way is to create hidden form with action = «url-to-file» and then submit it.
Hi, it’s not working in webview cordova of android app.
А возможно задать директорию для скачивающегося файла?
Нет, к сожалению в браузерах такая возможность не предусмотрена.
Downloading file is working fine.i need to download the file with its folder-javascript
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…’;