Přejít k navigační liště

Zdroják » Webdesign » Práce se soubory v prohlížeči, díl 1

Práce se soubory v prohlížeči, díl 1

Články Webdesign

Už několikrát jsme v článcích na Zdrojáku použili FileAPI, relativně novou součást webových technologií, která umožňuje číst, zpracovávat a ukládat soubory. V této minisérii se podíváme na nejrůznější nástroje a rozhraní, která využijete při zpracovávání souborů v prohlížečích, trochu podrobněji.

0. Drobné odbočení na úvod k atributu download

Jistě jste ve své praxi řešili případ, kdy bylo na stránce potřeba odkázat na soubor s tím, že se po kliknutí uživateli neotevře, ale nabídne mu uložení jako download. U souborů s „dostatečně exotickým “ MIME typem (míněno exotickým pro prohlížeč) to tak funguje vždy. Odkaz <a href="blabla.zip"> vyvolá rovnou stahovací dialog a nepokouší se nic otvírat.

Ale co obrázky, skripty nebo HTML stránky? Jak donutit prohlížeč, aby je neotvíral, ale nabízel ke stažení? Jedna z možností je nastavit na serveru patřičné hlavičky ( Content-Disposition atd.). Druhá možnost je o něco jednodušší: K odkazu se napíše poznámka „Klikněte pravým tlačítkem a vyberte Uložit odkaz jako…“

Tento problém řeší navrhovaný atribut download. Jeho hodnotou je jméno, pod nímž by měl být soubor uložen (samosebou si jej může uživatel v dialogu změnit).

<a href="logo.png" download="Logo Zdroják.cz">Stáhněte si naše logo</a>

Výsledkem je, že prohlížeč po kliknutí rovnou otevře ukládací dialog a nabídne uložení souboru. Šikovná věc, ale na jásání je brzy – atribut download je v tuto chvíli  implementován pouze v Chrome dev verzi (14.0.835.15+).

Zajímavé možnosti nabídne tento atribut ve spojení s blobURI či filesystem URI – a o nich bude řeč právě v souvislosti s FileAPIs.

1. Souborová API

Specifikace File API, File Writer API a File System API, resp. jejich návrhy, tvoří základ veškeré práce se soubory v prohlížečích. Pro všechny platí, že jsou v prohlížečích v různém stádiu implementace a zatím se nelze stoprocentně spolehnout, že v konkrétním prohlížeči budou fungovat, resp. že budou fungovat všechny funkce. Proto minimálně v následujících měsících využijete nejrůznější polyfills, tedy knihovny, které detekují existenci technologií, a pokud nejsou implementovány, nabídnou vlastní implementaci.

1. 1. File API

Toto API definuje základní konstrukce pro práci se soubory – Blob a File, metody pro čtení dat, synchronní čtení dat, vytváření URI pro soubory, chyby, výjimky a události, které se k souborům vážou.

1. 2. File Writer API

Druhé API přináší možnost vytvářet soubory v prohlížeči a zapisovat je lege artis na disk. Definuje BlobBuilder, základní nástroj pro vytváření souborů, a nástroje FileSaver a FileWriter, které ukládají obsah blobu na disk. Specifikace je stále ve fázi návrhu, takže je možné, že FileWriter se stane součástí Filesystem API.

1. 3. File System API

Toto API slouží pro práci se „sandboxed filesystem“, tedy s prostorem na disku, vyhrazeným pro „bezpečnou práci se soubory z prostředí prohlížeče“. Skripty tedy nebudou moci číst a zapisovat, kam se jim zamane, a přepsat tak např. systémové soubory; jejich prostor je omezen. Více o tomto API naleznete v článku První krůčky s FileSystem API.

2. Blob

Blob, neboli Binary Large OBject, je pojem, známý např. ze SQL či programovacích jazyků, kde vyjadřuje nestrukturovaný soubor binárních dat. Nestrukturovaný je zde myšleno ve vztahu k aplikaci, která s ní pracuje; například binární reprezentace obrázku ve formátu PNG zcela jistě vnitřní strukturu má, ovšem pokud je takový obrázek poslán na server a tam uložen do databáze, aniž by s ním aplikace jakkoli pracovala, měnila ho nebo z něj četla informace, může na něj nahlížet jako na shluk binárních dat, které pro ni nemají bezprostřední význam.

Podle File API je Blob objekt, který obsahuje právě ta binární data a navenek dává k dispozici readonly atributy size a type a nabízí metodu slice(start, length, type). Atribut size udává velikost souboru v bajtech a type obsahuje MIME typ dat. Metoda slice slouží k vytvoření nového blobu ze stávajícího pomocí zkopírování části, definované počátečním bajtem (start) a délkou (length). Třetí atribut je nepovinný a slouží k případné změně typu blobu.

3. File

Potomkem „třídy“ Blob je File – k výše popsaným atributům přidává další R/O atributy name a lastModifiedDate. Name obsahuje jméno souboru (jen jméno bez cesty) a lastModifiedDate informaci o poslední změně souboru.

4. FileReader

Pokud chceme pracovat s daty v blobu či souboru přímo v JavaScriptu, musíme je nejprve přečíst – tedy převést do tvaru, který lze v JS zpracovávat. K tomu slouží právě metody objektu FileReader. FileReader nabízí základní metody readAsBinaryString, readAsDataURL, readAsTextreadAsArrayBuffer.

var reader = new FileReader();
reader.onload = function(event) { ... };
reader.readAsBinaryString(blob);

V obsluze události onload můžeme zpracovat načtený obsah ve tvaru binárního řetězce (readAsBinaryS­tring), datového URL (readAsDataURL), textu (readAsText) či ArrayBuffer. Obsah bude dostupný přes vlastnost  result:

reader.onload = function(event) { ...
   var data = event.target.result;
...}

Všechny zmíněné metody mají jako parametr blob. U metody readAsText() lze zadat i druhý nepovinný parametr encoding a určit případné překódování znaků.

FileReader nabízí i atributy readyState a error. První obsahuje stav readeru (0 = nic se nedělá, 1 = probíhá čtení, 2 = čtení dokončeno), druhý obsahuje objekt FileError, který udává chybu, ke které při čtení došlo.

Kromě zmíněné události load nabízí FileReader i události loadstart, loadend (voláno při začátku čtení, resp. při skončení), error (při výskytu chyby), abort (při skončení čtení) a progress (voláno během čtení). U události progress můžeme z atributů loaded a total zjistit, kolik dat bylo zpracováno a kolik zbývá.

reader.onprogress = updateProgress;
...
function updateProgress(event) {
  if (event.lengthComputable) {
    var loaded = (event.loaded / event.total);
    if (loaded < 1) {
       bar.style.width = (loaded * 200) + "px";
    }
  }
}

Pěkné… ale kde ten blob vezmu?

Velmi správná otázka. Hovoříme tu o zpracování souborů, ale zatím nepadlo ani slovo o tom, jak se z klasického souboru, co leží někde na disku, stane blob.

Máme několik způsobů (a teď pomineme File System API). Můžeme si blob ručně vytvořit pomocí BlobBuilderu (o kterém bude ještě řeč), nebo můžeme nechat uživatele, aby soubor vybral a v prohlížeči otevřel.

Můžeme využít buď novější postup s drag-and-drop (viz článek Práce se soubory v HTML5) nebo klasický způsob s <input type=file>. Samozřejmě lze použít i obě metody najednou (což může přispět ke zlepšení použitelnosti) – samotná obsluha načítání souborů zůstává stejná, mění se pouze sledované události. U elementu input file můžeme obsloužit událost  change:

<input type=file id=upload>
var upload = document.getElementById('upload');
upload.onchange = function (event) {
   var file = upload.files[0];
   // ve file máme kýžený "blob" pro další zpracování
}

Pole files u elementu input file obsahuje jednotlivé soubory jako položky typu File. Pokud je použit v elementu atribut multiple, může být položek víc, jinak bude obsahovat položku jedinou.

Blob lze vytvořit i interně, a to jak „na zelené louce“ (pomocí Builderu), tak z HTML elementů, které podporují metodu toBlob() (např. canvas). Obojí bude námětem dalších dílů.

Příklad: Jak načíst soubor?

Ukažme si použití těchto informací v jednoduchém příkladu: necháme uživatele vybrat soubor s obrázkem, a ten zobrazíme. Autorem příkladu je Remy Sharp.

V HTML je několik prvků pro uživatelské rozhraní:

  <p id="status">File API &amp; FileReader API not supported</p>
  <p><input type=file></p>
  <p>Select an image from your machine to read the contents of the file without using a server</p>
  <div id="holder"></div>

Odstavec #status udává stav aplikace, tedy zda je čtení souborů podporováno a zda je možné je použít. Dále je v kódu vlastní input pro výběr souboru, popis a div#holder, v němž se bude obrázek zobrazovat.

Zbytek je obsloužen skriptem. Je testována dostupnost API:

if (typeof window.FileReader === 'undefined') {
  state.className = 'fail';
} else {
  state.className = 'success';
  state.innerHTML = 'File API & FileReader available';
}

a následně je nadefinována obsluha:

var upload = document.getElementsByTagName('input')[0],
    holder = document.getElementById('holder'),
    state = document.getElementById('status');

upload.onchange = function (e) {
  e.preventDefault();

  var file = upload.files[0],
      reader = new FileReader();
  reader.onload = function (event) {
    var img = new Image();
    img.src = event.target.result;
    // note: no onload required since we've got the dataurl...I think! :)
    if (img.width > 560) { // holder width
      img.width = 560;
    }
    holder.innerHTML = '';
    holder.appendChild(img);
  };
  reader.readAsDataURL(file);

  return false;
};

V obsluze události change je nejprve zabráněno vykonání předdefinovaných akcí. Výše uvedeným způsobem je získán z vybraného souboru blob a vytvořen nový FileReader, kterému je blob předán jako parametr metody readAsDataURL(). Obsluha události onload vytvoří HTML element <img>, nastaví mu atribut src na hodnotu, která je vrácena Readerem jako result  – tedy datové URL, a uloží jej do připraveného kontejneru  holder.

V příkladu nejsou ošetřeny chybové stavy ani není obrázek nějak upravován. Jde čistě jen o demonstraci čtení souborů. (Můžete se podívat na tentýž příklad s použitím drag-and-drop)

A dál…?

V dalším pokračování si probereme vytváření souborů v prohlížeči a jejich zápis. Podíváme se, jak uložit obrázek z canvasu a jak vytvořit dočasnou kódovou URL pro objekt.

Komentáře

Subscribe
Upozornit na
guest
16 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
bauglir

Pozor na poslední ukázku, čtení obsahu souboru je vhodné pouze u malých souborů, popřípadě tam, kde obsah souboru skutečně potřebujete… DataURL videa vložené do DOMu by mohlo potrápit ledasjaký počítač :)
Pokud máte BLOB a chcete jej zobrazit, doporučují spíše createObjectURL, tedy vytvoření URL na daný blob bez načítání obsahu.

Roman Jakubec

Podobný problém jsem řešil. Snažil jsem se načíst hlavičku gigabytového souboru. Bohužel jsem se k načtení hlavičky nedopracoval. Nevíte zda existuje nějaký způsob?

bauglir

Dobrý den,
BLOB má metodu slice, pokud tedy ten BLOB máte… kde ho chcete vzít?

besh

Z clanku by se mohlo zdat, ze lze pouzit bud file input nebo drag’n’drop. Pravdou ale je, ze lze pouzit oboji a vzhledem k pouzitelnosti bych to i doporucil. Asi nejhorsi variantou by bylo vyuziti samotne drag’n’drop funcionality, protoze ne kazdy v tahani mysi spatruje nejakou vyhodu. Obsluzna funkce muze byt stejna pro oba pripady, takze neni duvod to tak neudelat.

starenka

To je hezky, atribut download… Kdyz jsem pred 5+ lety psal do W3C, jestli by treba nahodou atribut checksum nebyl dobrej napad (proste by prohlizec po stazeni overil, ze je soubor stazenej v poradku), tak jsem nestal ani za odpoved. Ze bych to zkusil znova?

Jiří Kosek

Jistou šanci bych tu skutečně viděl. Když si Hixie svéhlavě bez nějaké diskuse přidal atribut download, proč by nepřidal ještě něco checksum. ;-)

František Kučera

Od toho máme HTTP hlavičku Content-MD5

(je fakt, že nějaký modernější algoritmus by se hodil – ale pokud má jít o ochranu proti náhodnému poškození souboru, je i md5 použitelná)

František Kučera

BTW: a kdyby to mělo být přímo součástí odkazu* a ne HTTP odpovědi, tak by to šlo krásně řešit pomocí jmenných prostorů – např.:

<a href="http://…" otisk:sha1="5e0c­a0e40a16c9fbfeb4d251ec2a­477f38fab9e3">sou­bor.txt</a>

Nemusí být totiž všechno naplácané v základním standardu a povinné pro všechny – přesně tohle je totiž funkcionalita typu „něco navíc“, kterou můžou implementovat některé weby a některé prohlížeče. Standard by měl být podle mého co nejjednodušší a další funkcionalitu řešit modulárně (a oddělit jednotlivé skupiny funkcí do jmenných prostorů).

*) což může mít taky smysl – když budeme odkazovat na soubor na cizím serveru, kterému nedůvěřujeme.

František Kučera

Tak striktně vzato, nemusí se vlastně vůbec nic :-) (snad leda umřít). Ale otázka je, co je vhodnější.

A když zvážím dvě možnosti:

1) Jeden velký všeobjímající standard, který zahrnuje spoustu funkcí, ale málokdo ho dodržuje. Všichni budou říkat: „Podporujeme standard XYZ!“ a malým písmem budou dodávat: „ale ne úplně na 100%“ a ještě menším (pokud vůbec): „tyhle funkce podporujeme a tyhle ne, u ostatních nevíme, a možná jsme na něco zapomněli, tak to musíte vždycky vyzkoušet“.

2) Relativně malý základní standard, který obsahuje jen tu nejdůležitější funkcionalitu a který není problém 100% implementovat. A k němu dodatečné moduly zaměřené vždy na nějakou funkční oblast. Navíc autorem těch modulů může být i někdo jiný než autor základního standardu…

Mně je bližší ten modulární způsob – kromě jiného to lépe umožňuje práci stylem: píšu web/aplikaci vůči standardu a ne vůči konkrétním prohlížečům. Naházet všechno na jednu hromadu a říct: „podporujte si z toho, co chcete“ mi moc rozumné nepřijde a zavání to starými (ne)pořádky, které na webu kdysi panovaly.

Jiří Kosek

Každý rozumně napsaný standard má sekci nazvanou „Conformance“, kde je definováno, co je povinné, co nepovinné. Někdy existuje i několik tříd shody s normou, třeba pro různé druhy aplikací.

Jestli je standard malý nebo velký je jedno. Důležité je, zda zajistí interoperabilitu. Mít dobré a testovatelné „Conformance“ kritéria je většinou jen podmínka nutná, nikoliv postačující.

Jiří Kosek

Namespace v HTML5? Tak to nepůjde, z toho by chlapci z WHATWG dostali pupínky. ;-)

„Distributed extensibility“ je totiž pro WHATWG priorita -1000000 a červený hadr. HTML5 není sémantický značkovací jazyk, který by šlo rozšiřovat. Je to platforma pro tvorbu aplikací naroubovaná na HTML. S tím je potřeba se smířit. Slova „hypertext“ a „markup“ v názvu HTML5 jsou dneska již zavádějící. Vždyť se také dlouho HTML5 pod křídly WHATWG jmenovalo „Web Applications“.

misaz

Ahoj,
pěkný článek, ale nějak jsem nepochopil jak mam obsah souboru text.txt dostat do proměnné mujtext.
Co proto musím udělat?

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.