Pokud máte stránku, která slouží ke stažení souboru, který se může delší dobu připravovat (např. se musí nejprve zabalit do ZIPu), můžete do stránky přidat jednoduchou kontrolu, zda se ještě stále soubor připravuje nebo se již začal stahovat.
Cookie ze serveru
Princip je v celku jednoduchý – společně s daty souboru pošlete v odpovědi specifické Cookie, na které bude klient reagovat a zobrazí zprávu.
setcookie('download-started', '1');
Tento příkaz zavolejte ve funkci, která připravuje soubor ke stažení těsně před tím, než soubor odešlete. Např.:
$zip = compress_file($filename);
$file = file_get_contents($zip);
set_file_download_headers($zip);
setcookie('download-started-' . $filename, '1');
die($file);
Funkce compress_file() a set_file_download_headers() jsou smyšlené a slouží pouze ke zjednodušení kódu. Podstatná je funkce setcookie() (v případě PHP), která do odpovědi přidá cookie pro klienta. Cookie je vytvořena bez hodnoty expires, takže nebude v prohlížeči uchována a zanikne hned po jeho ukončení.
V případě, že se ze serveru dá stahovat více souborů najednou, mělo by cookie obsahovat jméno souboru, aby skript rozpoznal, který soubor se začal stahovat. Pokud potřebujete takto ošetřit pouze jeden soubor, můžete cookie pojmenovat jen download-started.
Příprava v JavaScriptu
Klient, tedy stránka v prohlížeči, by měla dělat několik věcí.
- nabídnout tlačítko nebo odkaz pro stažení souboru
- po kliknutí na tlačítko/odkaz ho skrýt a místo něj zobrazit nějakou hlášku o tom, že příprava souboru může nějakou dobu trvat.
- současně po kliknutí zresetovat příslušnou cookie (na jejímž jméně se musí server a klient shodnout!!!)
- spustit timer, který bude periodicky kontrolovat hodnotu příslušné cookie
- po změně cookie skrýt zprávu o čekání a zobrazit zprávu o stahování.
1. odkaz pro stažení
Klasický odkaz pro stažení souboru musíte trochu upravit, aby byl schopen rozpoznat stažení:
<a href="/download/?filename=
jmeno_souboru" onclick="return prettyDownload.call(this);
" data-filename="jmeno_souboru" >Stáhnout</a>
Klasický odkaz s atributem href, který bude stahovat soubor „jmeno_souboru“ pomocí skriptu (např.:) „/download/index.php„, jsme doplnili o handler kliku, který zavolá funkci, které předáme referenci na odkaz, abychom z něj mohli přečíst datový atribut, který jsme k odkazu přidali přes data-filename. Handler by měl vracet false, aby zabránil normálnímu stažení souboru – zde je řešeno vrácením návratové hodnoty funkce, která to za nás obstará.
Kód v PHP by mohl vypadat takto:
<a
href="/download/?filename=<?php
echo $filename;
?>"
data-filename="<?php
echo $filename;
?>"
onclick="return prettyDownload.call(this);"
>
Pokud nemáte rádi volání JS metod přes HTML atributy (což je chvályhodné) a chcete registrovat odkazy a tlačítka automaticky třeba podle třídy, můžete to udělat v nějaké inicializační metodě (např. window.onload).
//vanilla JS
var
links = document.getElementsByClassName(
'prettyDownload'),
i, cnt;
for (i = 0, cnt = links.length; i < cnt; i++) {
links[i].onclick = window.prettyDownload;
}
//jQuery
$('.prettyDownload')
.on('click', window.prettyDownload);
2. pretty download
Teď máme odkazy, které volají nějakou metodu, a je potřeba ji nadefinovat. To doporučuji udělat v samostatném JS souboru, abyste mohli tuto funkčnost sdílet na dalších webech.
//vanilla JS
window.prettyDownload = function() {
var url = this.href || '';
if (!url) { return true; }
if (this.alreadyClicked) { return false; }
this.innerHTML = 'Preparing for download...';
this.className += ' downloading';
document.location = url;
this.alreadyClicked = true;
return false;
}
//jQuery ///... $(this) .html('Preparing for download...') .addClass('downloading'); ///...
Funkce nejprve přečte z odkazu aktuální URL, abychom měli jistotu, že si ji později nesmažeme. Pro případ, že je atribut href prázdný, budeme pretty download ignorovat a necháme prohlížeč, aby s odkazem naložil, jak uzná za vhodné (druhý řádek). A pokud uživatel klikl na odkaz podruhé, budeme to též ignorovat (třetí řádek).
V druhém bloku pak upravíme odkaz (či tlačítko) tak, že do něj napíšeme nějakou zprávu. Pak můžeme ještě přidat CSS třídu, která upraví jeho styl nebo provést daší akce dle potřeby. Samozřejmě tohle je velice jednoduchý příklad a kreativitě se meze nekladou.
Nakonec necháme prohlížeč stáhnout příslušný soubor (změnou URL dokumentu), poznamenáme si, že tenhle odkaz už jednou soubor stáhnul (alreadyClicked) a vrátíme false, abychom zabránili prohlížeči provést výchozí akci (tedy další stažení souboru).
Poznámka: v tomto jednoduchém příkladu by klidně bylo možno vynechat manuální změnu location a vrácení false a nechat vše na prohlížeči, ale vždy je lepší provést podobné akce programově a zabránit tak pozdějším kolizím v kódu. (např. pokud bychom uvnitř funkce měnili URL odkazu na „/download/cancel“, odkaz by pak nefungoval správně.)
3. čekání na stahování
Až doteď provedené akce byli celkem obyčejné, protože změnit odkaz po kliknutí umí každý (okopírováním z nějakého tutoriálu).
My ale potřebujeme nějak reagovat na změnu cookie, kterou jsme nastavili na začátku. Proto do funkce prettyDownload() před změnu location přidáme další volání:
///... prettyDownload()
window.waitForDownload.call(this);
document.location = url; //z předchozího příkladu
///...
A funkci samozřejmě nadefinujeme:
window.waitForDownload = function() {
document.cookie = 'download-started-'
+ this.dataset.filename + '=0;'
}
Funkce přečte jméno souboru z data atributu příslušného odkazu a nastaví cookie (ve stejném formátu jako ho bude vytvářet server!) na nulu, abychom měli jistotu, že poznáme, až se změní na 1 (např. v případě opakovaného stažení v rámci jedné sessiony).
4. čekání na změnu
Do funkce waitForDownload() ještě musíme přidat čekání:
///... waitForDownload()
window.setInterval(
window.showDownload.bind(this),
1000
);
///...
Dobu čekání volíme podle rychlosti serveru v rozmezí 200 – 2000 milisekund.
Pozn.: funkce bind() slouží k předání scopu příslušné funkci. Ve starších prohlížečích není dostupná, ale není problém si dopsat její polyfill (náhražku). Pokud nechcete používat bind(), můžete ručně vytvořit uzávěru přes this (přiřazené např. to proměnné „me“).
var me = this; //nebo 'self',
//pokud vám to přijde programátorštější ;)
window.setInterval(function() {
window.showDownload.call(me);
}, 1000);
5. hlídání cookie
A nakonec ještě metoda showDownload(), která bude mít za úkol poznat, že se začal stahovat soubor:
window.showDownload = function() {
var
regExp = new RegExp('download-started-'
+ this.dataset.filename
+ '=(^[^;])+'),
cookie =
document.cookie.match(regExp)[1];
if (1 === parseInt(cookie, 10)) {
this.innerHTML =
'File is downloading now...';
}
}
Jelikož hodnota document.cookie obsahuje seznam všech cookie v textové podobě (např. „c1=1;c2=2“), musíme si nejprve vytvořit regulání výraz, který zjistí hodnotu cookie, která nás zajímá (její jméno opět přečteme z datového atributu odkazu).
Pak už můžeme přečíst cookie z řetězce a získat hodnotu 0 nebo 1 (pomocí parseInt()). A pokud zjistíme, že hodnota cookie je již 1, můžeme postoupit v tomu, abychom uživateli oznámili, že jeho soubor je připraven a začal se stahovat.
Co přesně uděláte již nechám na vás, ale mějte na paměti, že řada prohlížečů zobrazí dialog pro stažení (Firefox), některé zobrazí upozornění nad nebo pod stránkou (IE) a některé začnou rovnou stahovat (Chrome). Některé dokonce provedou něco nepředvídatelného (např. Safari na iOS zobrazí novou stránku „Otevřít v…“ pokud jste stáhli soubor s neznámou příponou).