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

Zdroják » Různé » Práce se soubory v HTML5

Práce se soubory v HTML5

Články Různé

Pomocí souborového API, které bylo přidáno k DOM v HTML5, může webová aplikace požádat uživatele, aby vybral lokální soubory, a poté přečíst jejich obsah. Uživatel může soubory vybrat buď klasicky pomocí HTML elementu input, nebo pomocí techniky drag-and-drop, tedy přetažením do okna webového prohlížeče.

Článek je překladem textu Using files from web applications, který je k dispozici na webu Mozilla Developer Center pod licencí CC-BY-SA. Tento text je k dispozici pod stejnou licencí, můžete jej tedy šířit a upravovat, pokud zachováte informace o autorovi a pokud své dílo zveřejníte pod podobnou licencí.

Poznámka překladatele

Text pojednává o technice, která byla představena v HTML5 a kterou podporují nejnovější webové prohlížeče. S její pomocí lze snadno provádět některé operace se soubory, které bylo až dosud nutno řešit pomocí pluginů (Flash, Java), přímo z HTML a JavaScriptu. Pravděpodobně nejznámější implementací této techniky je přidávání příloh do mailů pomocí drag-and-drop, které před několika dny představil GMail.

Výběr souborů pomocí HTML

Vybrat jeden soubor a použít jej pomocí File API je snadné:

<input type="file" id="input" onchange="handleFiles(this.files)" />

Jakmile uživatel vybere soubor, je zavolána funkce handleFiles(), jako parametr je jí předán objekt FileList , a v něm je objekt File , který představuje soubor vybraný uživatelem.

Pokud chcete povolit uživateli vybrat víc souborů najednou, použijte atribut  multiple:

<input type="file" id="input" multiple="true" onchange="handleFiles(this.files)" />

V takovém případě bude seznam souborů, předaný funkci handleFiles(), obsahovat samostatný objektFile pro každý soubor, který uživatel vybral.

Dynamické přidání obslužné rutiny

Pokud jste vytvořili vstupní pole pomocí JavaScriptové knihovny, např. jQuery, budete muset k přidání obslužné funkce použít element.addEventListener(), například takto:

var inputElement = document.getElementById("inputField");
inputElement.addEventListener("change", handleFiles, false);

function handleFiles() {
  var fileList = this.files;

  /* zde můžete pracovat se seznamem souborů */
}

Všimněte si, že v takovém případě není předán seznam souborů funkci handleFiles() jako parametr, ale je v this, protože obslužné rutiny událostí, které jsou přidané tímto způsobem, nedostávají data coby parametry při volání funkce.

Výběr souborů pomocí drag-and-drop

Uživatelům své webové aplikace můžete rovněž nabídnout možnost přetažení souborů z prostoru operačního systému (z plochy, Průzkumníka atd.) na určené místo.

První krok je vytvoření oblasti, do níž budou soubory přetahovány („drop zone“). Přesné určení místa, kam mají být soubory přetaženy, záleží na designu vaší aplikace. Vlastní vytvoření elementu, který přijímá událost „puštění“, je snadné:

var dropbox;

dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);

V tomto příkladu jsme udělali z elementu s ID „dropbox“ oblast, která přijímá puštěné objekty. Stačí nastavit obsluhu událostí dragenter, dragover a   drop.

V našem případě nepotřebujeme nijak zvlášť ošetřovat události  dragenter a dragover, tudíž budou obě obslužné funkce velmi jednoduché. Pouze zastavíme další šíření události a zabráníme tomu, aby byly vykonány standardní akce:

function dragenter(e) {
  e.stopPropagation();
  e.preventDefault();
}

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
}

Celé kouzlo se skrývá ve funkci drop():

function drop(e) {
  e.stopPropagation();
  e.preventDefault();

  var dt = e.dataTransfer;
  var files = dt.files;

  handleFiles(files);
}

V této funkci nejprve vyzvedneme pole dataTransfer z objektu události, z něho pak vybereme seznam souborů, a ten předáváme funkci handleFiles() ke zpracování. Od tohoto okamžiku je zpracování naprosto stejné jako ve výše uvedeném příkladu s elementem  input.

Získání informací o souborech

ObjektFileList obsahuje seznam všech souborů, které uživatel vybral, každý jako samostatný objekt File . Můžete snadno zjistit jejich celkový počet stejně jako u ostatních seznamů, pomocí čtení atributu  length:

var numFiles = files.length;

Jednotlivé objekty typuFile získáme známým způsobem – přístupem k seznamu jako k poli:

for (var i = 0; i < files.length; i++) {
  var file = files[i];
  ..
}

Tento cyklus projde všechny soubory v seznamu.

ObjektFile poskytuje několik atributů, které obsahují užitečné informace o souboru – nás budou zajímat především tyto tři:

name
Jméno souboru (jen ke čtení). Je to pouze čisté jméno souboru a neobsahuje žádné informace  o jeho umístění na disku (je bez cesty).
size
Velikost souboru v bajtech jako 64bitové celé číslo bez znaménka (pouze ke čtení).
type
MIME typ souboru jako řetězec (pouze ke čtení), nebo prázdný řetězec, pokud nemohl být MIME typ zjištěn.

Příklad: Zobrazení náhledů vybraných obrázků

Řekněme, že vyvíjíte další úžasný web pro sdílení fotografií a chcete použít HTML5 pro zobrazení náhledů obrázků dřív, než je uživatel uploaduje na server. Stačí vám připravit element input nebo „drop zone“, viz výše, a k obsluze zavolat funkci, která bude podobná této:

function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var imageType = /image.*/;

    if (!file.type.match(imageType)) {
      continue;
    }

    var img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img);

    var reader = new FileReader();
    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
    reader.readAsDataURL(file);
  }
}

V cyklu procházíme seznam souborů, které uživatel vybral, a kontrolujeme, zda jejich typ (atribut  type) odpovídá obrázku (pomocí regulárního výrazu, který hledá řetězec „image.*“). Pro každý takový soubor vytvoříme nový element img element. Pomocí CSS mu můžeme nastavit nějaké hezké okraje, stínování a určit velikost, ale pro naši ukázku to není nezbytné.

Každému obrázku jsme přiřadili CSS třídu „obj“, aby byly snadno k nalezení. Rovněž jsme každému nastavili atribut  file a do něho uložili objektFile pro daný obrázek; to nám později dovolí vybrat soubory k uploadu. Nakonec jsme pomocí Node.appendChild() přidali nový element do dokumentu.

Poté jsme vytvořili instanci FileReader , který se postará o asynchronní načtení obrázku a připojení k elementu img. Po vytvoření nového objektu FileReader nastavujeme obsluhu pro jeho událost onload a voláme metodu readAsDataURL(), která zahájí čtení dat na pozadí. Když jsou veškerá data načtena, převede je na URL ve tvaru data:, a to předá obslužné funkci pro událost onload. Naše implementace této obsluhy nastaví atribut src u elementu img na předané URL. Výsledkem je, že se obrázek objeví na stránce.

Příklad: Upload vybraného souboru

Další věcí, kterou můžete chtít udělat, je dát uživateli možnost nahrát vybraný soubor nebo soubory na server. V HTML5 to lze udělat velmi snadno „na pozadí“.

Pozor! Následující text používá metody, konkrétně getAsBinary(), které fungují v prohlížečích s jádrem Gecko a nejsou součástí specifikace File API od W3C, takže jsou „deprecated“. Rozhodně proto nelze doporučit popisované řešení jako implementační standard, spíš jako inspiraci k vytvoření vlastní obdoby – pozn.překl.

Příprava dat pro upload

Budeme pokračovat v předchozím příkladu, který zobrazoval náhledy obrázků. Jednotlivé obrázky zobrazoval jako elementy img s CSS třídou „obj“ a do atributu file ukládal odpovídající objekt File. To nám umožní snadno získat všechny obrázky, které uživatel vybral k uploadu, pomocí Document.querySelectorAll()  – například takto:

function sendFiles() {
  var imgs = document.querySelectorAll(".obj");

  for (var i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}

Na řádku 2 nám metoda querySelectorAll vrátí pole, které si uložíme do proměnné imgs, a v něm budou všechny elementy z aktuálního dokumentu, které mají CSS třídu „obj“. Což budou v našem případě právě ty dříve vybrané obrázky. Jakmile máme tento seznam připravený, můžme jej projít a pro každou položku vytvořit novou instanci FileUpload, která se postará o odeslání souboru.

Obsluha uploadu pro jeden soubor

Funkce FileUpload dostává dva argumenty: element img a soubor, z něhož bude číst data.

function FileUpload(img, file) {
  this.ctrl = createThrobber(img);
  var xhr = new XMLHttpRequest();
  this.xhr = xhr;

  var self = this;
  this.xhr.upload.addEventListener("progress", function(e) {
        if (e.lengthComputable) {
          var percentage = Math.round((e.loaded * 100) / e.total);
          self.ctrl.update(percentage);
        }
      }, false);

  xhr.upload.addEventListener("load", function(e){
          self.ctrl.update(100);
          var canvas = self.ctrl.ctx.canvas;
          canvas.parentNode.removeChild(canvas);
      }, false);

  xhr.open("POST", "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php");
  xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
  xhr.sendAsBinary(file.getAsBinary());
}

Výše uvedená funkce FileUpload() nejprve vytváří throbber (ukazatel), který zobrazuje průběh nahrávání, a pak pomocíXMLHttpRequest nahrává data na server.

Před samotným odesláním dat na server probíhají ještě některé přípravné kroky:

  1. Je nastavena obsluha události „progress“ u XMLHttpRequest  tak, aby aktualizovala ukazatel postupu. Ukazatel bude tedy zobrazovat průběžně aktuální data.
  2. Obsluha události „load“ pro XMLHttpRequest se stará o to, aby zobrazila v ukazateli hodnotu „100%“, a pak ukazatel odstraní.
  3. Požadavek na upload je vytvořen zavoláním metody open() objektu XMLHttpRequest, která vygeneruje HTTP POST požadavek.
  4. Je nastaven MIME typ pro upload voláním metody overrideMimeType(). V našem případě je použit generický typ, stejný pro všechny soubory. Podle situace můžete chtít MIME typ nastavit, ale také nemusíte.
  5. Nakonec je zavolána metoda sendAsBinary(), která pošle binární data ze souboru. (Tato část funguje pouze v prohlížečích s jádrem Gecko a měla by být změněna, protože používá pro načtení dat ze souboru synchronní rutinu getAsBinary(), která je označena jako „deprecated“ a v jiných prohlížečích či v budoucích verzích nemusí být podporována.)

K tématu

Komentáře

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

Škoda že není v HTML5 výběr víc souborů najednou.

K dokonalosti tomu chybí ještě další (dost podstatná) věc: aby mohla aplikace i soubory ukládat. Abych si mohl třeba načíst dokument do online editoru napsaném v JS, upravit ho a uložit, aniž by se něco odesílalo na server. Tohle prostě pokud vím v JS nejde nijak udělat a je to škoda.

Jde na něco takového použít Java, tam je ale zase fakt super jak pak vypadá „zabezpečení“ – povolit přístup na disk – ano/ne – ne „povolit přístup k souboru xyz“, případně „vybrat soubor k uložení“. U důvěryhodné aplikace to není problém, ale na používání jako se běžně používá JS na stránkách (tj. pustí se to samo, ani nedůvěryhodná aplikace mi nemůže ublížit) to není.

pas2007

Bezpečným způsobem je to implementované ve Flashi – vytvoříte nějaký byte-array a vyvoláte funkci, která zobrazí uživateli dialog k uložení. No a díky dobře fungujícímu rozhraní Flash-JS si můžete udělat univerzální nevizuální flashovou komponentu k tomuto účelu… do doby, než to bude umět nativně HTML.

František Kučera

Tohle šlo s Java Applety už dávno ;-)

imploder

Je to tahle věc? http://java.sun.com/docs/books/tutorial/uiswing/components/filechooser.html

Tam je totiž právě problém, že nedávám aplikaci přístup k mnou vybranému souboru – vyskočí okno jestli chci povolit přístup a ani tam není napsané, jaký soubor aplikace chce – tzn. když dám Allow, může si vzít jakýkoliv.

František Kučera

Jenže tohle právě aplikaci dovolí pracovat jen se souborem, který uživatel vybral v dialogu, vyzkoušej si to.

peep

a proč to neuložit na server a pak nestáhnout?

Darker

Hmm. Třeba protože je to neefektivní?

čárlí

V čistém JavaScriptu to nejde, v JScriptu přes ActiveX ale ano.

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.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.