Hlavní navigace

Čtení souborů javascriptem pomocí FILE API

Martin Smola 22. 8. 2012

HTML5 konečně umožňuje standardní způsob, jak komunikovat s lokálními soubory přes File API specifikaci. Například, můžeme soubor API použít k vytvoření miniaturních náhledů, snímků, které jsou odesílány na server, nebo předány aplikaci jako odkaz, zatímco je uživatel offline. Ukážeme si, jak na to.

Tento článek je volným překladem z anglického "Reading files in JavaSCript using the file API" od Erica Bidelmana na webu HTML5ROCK.COM

Úvod

Specifikace nabízí několik rozhraní pro přístup k souborům z "lokálního" systému:

  1. File - jednotlivý soubor, poskytuje informace pro čtení, jako je jméno, velikost souboru, mimetype a odkaz na popisovače souboru.
  2. FileList - pole, složené s jednotlivých souborů File. (Pokud použijeme <input type="file" multiple> nebo přetáhneme soubory z adresáře na pracovní ploše (drag and drop)).
  3. Blob - Umožňuje řezání souboru po určitém počtu bajtů.

Pro použití v kombinaci s výše uvedenými datovými strukturami, lze použít rozhraní pro asynchronní čtení souboru: FileReader prostřednictvím Javascriptu, Díky tomu je možné sledovat průběh čtení souboru, případné chyby, nebo určit, kdy je načítání kompletní. V mnoha ohledech se podobá specifikaci XMLHttpRequest API.

Výběr souborů

První věc, kterou musíme udělat, je zkontrolovat, zda náš prohlížeč plně podporuje FILE API:

// Podmínka pro kontrolu podpory FILE API.
if (window.File && window.FileReader && window.FileList && window.Blob){
   // Výborně, náš FILE API je plně podporován.
}
else{
   alert('FILE API není v tomto prohlížeči plně podporován.');
}

Pokud bude naše aplikace používat pouze některé z uvedených struktur. Upravíme podmínku jen na použité struktury.

Použití formulářového prvku pro výběr souborů.

Nejjednodušší způsob jak vybrat soubor, je použít standardní <input type="file"> prvek. JavaScript vrátí seznam vybraných souborů - objektů, jako seznam souborů uložený ve FileList. Zde je příklad, kde je použit atribut multiple pro výběr více souborů najednou:

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList objekt
    // proměnná files je FileList-em souborů.
    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
                  '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>  

Příklad: Zde můžete vyzkoušet input pro vybrání souborů.

Použití "drag and drop" pro výběr souboru

Další technika pro výběr souborů je nativní "drag and drop", přetažení souborů přímo z plochy do prohlížeče. Stačí upravit předešlou ukázku kódu na podporu "drag and drop":

<div id="drop_zone">Drop files here</div>
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.dataTransfer.files; // FileList objekt.

    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a',
                  '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Přikáže, že půjde o kopii.
  }

  // Nastavení drag and drop oblasti.
  var dropZone = document.getElementById('drop_zone');
  dropZone.addEventListener('dragover', handleDragOver, false);
  dropZone.addEventListener('drop', handleFileSelect, false);
</script>

Příklad: Zde můžete vyzkoušet vybírání pomocí drag and drop metody:

Sem přetáhněte soubory ze svého desktopu (plochy)

 

Poznámka: Některé prohlížeče zacházejí s <input type="file"> prvky stejně, jako s drag and drop metodou. Tím je v některých případech umožněno soubory přetahovat i do dialogového okna. Pokuste se přetáhnout soubory do vstupního pole v předchozím příkladu.

Čtení souborů

Teď přijde to nejzábavnější.

Poté, co jsme získali odkaz na soubor, pomocí FileReader objektu si můžeme načíst jeho obsah do paměti. Když načítání skončí, onload událost vyprší a jako výsledek, nám jsou zpřístupněna data souboru.

FileReader obsahuje čtyři asynchronní možnosti čtení souboru:

  • FileReader.readAsBinaryString (Blob | File) - Data souboru/části souboru, jako binární řetězec. Každý bajt bude reprezentován celým číslem v rozsahu [0 .. 255].
  • FileReader.readAsText (Blob | File, opt_encoding) - Data souboru/části souboru, jako textový řetězec. Ve výchozím nastavení je řetězec dekódován jako "UTF-8". Lze zadat i jiné kódování (opt_encoding).
  • FileReader.readAsDataURL (Blob | File) - Data souboru/části souboru, jako data URL.
  • FileReader.readAsArrayBuffer (Blob | File) - Data souboru/části souboru, jako ArrayBuffer objektu.

Jakmile se jedna z těchto metod zavolá na objekt FileReader, lze sledovat jeho průběh pomocí událostí onloadstart, onprogress, onload, onabort, onerror a onloadend.

  • onloadstart - Událost při začátku nahrávání.
  • onprogress - Událost při načítání - průběh, proces, nahrávání.
  • onload - Událost při nahrávání.
  • onabort - Událost při zrušení nahrávání,
  • onerror - Událost pokud se vyskytne chyba při nahrávání.
  • onloadend - Událost při dokončení nahrávání.

Níže uvedený příklad filtruje obrázky z uživatelského výběru, volá reader.readAsDataURL () na soubor, a vytváří miniatury, nahráním URL dat do atributu "src".

<style>
  .thumb {
    height: 75px;
    border: 1px solid #000;
    margin: 10px 5px 0 0;
  }
</style>

<input type="file" id="files" name="files[]" multiple />
<output id="list"></output>

<script>
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object

    // Průchozí smyčka objektem FileList vykreslující miniatury souborů.
    for (var i = 0, f; f = files[i]; i++) {

      // Pouze obrázkové soubory.
      if (!f.type.match('image.*')) {
        continue;
      }

      var reader = new FileReader();

      // Uzávěr k zachycení informací o souboru.
      reader.onload = (function(theFile) {
        return function(e) {
          // Vykreslení miniatur.
          var span = document.createElement('span');
          span.innerHTML = ['<img class="thumb" src="', e.target.result,
                            '" title="', escape(theFile.name), '"/>'].join('');
          document.getElementById('list').insertBefore(span, null);
        };
      })(f);

      // Číst data souboru jako URL data.
      reader.readAsDataURL(f);
    }
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
Příklad: Zde lze vyzkoušet nahrání a čtení souborů.

Krájení souborů

V některých případech není načtení celého souboru do paměti nejlepším řešením. Představme si například, že potřebujeme asynchronní nahrávání souborů. Jeden možný způsob, jak urychlit odesílání, by bylo číst a odesílat soubor v samostatných kusech bytů. Serverová část by se pak starala za rekonstrukci obsahu souboru ve správném pořadí.

Naštěstí pro nás, FILE API podporuje metodu "řezu" - slice souboru přesně pro tyto účely. Metoda slice obsahuje tři argumenty. Prvním, zadáváme od jakého bytu začínáme a druhým, jakým končíme. startingByte, endingByte. Třetím lze zadávat content type obsahu.

if (file.webkitSlice) {
  var blob = file.webkitSlice(startingByte, endindByte);
} else if (file.mozSlice) {
  var blob = file.mozSlice(startingByte, endindByte);
}
reader.readAsBinaryString(blob);

Následující příklad ukazuje čtení kousků souboru. Co stojí za zmínku je to, že script používá onloadend a kontroluje evt.target.readyState pomocí onload události.

<style>
  #byte_content {
    margin: 5px 0;
    max-height: 100px;
    overflow-y: auto;
    overflow-x: hidden;
  }
  #byte_range { margin-top: 5px; }
</style>

<input type="file" id="files" name="file" /> Read bytes: 
<span class="readBytesButtons">
  <button data-startbyte="0" data-endbyte="4">1-5</button>
  <button data-startbyte="5" data-endbyte="14">6-15</button>
  <button data-startbyte="6" data-endbyte="7">7-8</button>
  <button>entire file</button>
</span>
<div id="byte_range"></div>
<div id="byte_content"></div>

<script>
  function readBlob(opt_startByte, opt_stopByte) {

    var files = document.getElementById('files').files;
    if (!files.length) {
      alert('Please select a file!');
      return;
    }

    var file = files[0];
    var start = parseInt(opt_startByte) || 0;
    var stop = parseInt(opt_stopByte) || file.size - 1;

    var reader = new FileReader();

    // Pokud budeme používat onloadend, je potřeba zkontrolovat readyState.
    reader.onloadend = function(evt) {
      if (evt.target.readyState == FileReader.DONE) { // DONE == 2
        document.getElementById('byte_content').textContent = evt.target.result;
        document.getElementById('byte_range').textContent = 
            ['Read bytes: ', start + 1, ' - ', stop + 1,
             ' of ', file.size, ' byte file'].join('');
      }
    };

    if (file.webkitSlice) {
      var blob = file.webkitSlice(start, stop + 1);
    } else if (file.mozSlice) {
      var blob = file.mozSlice(start, stop + 1);
    }
    reader.readAsBinaryString(blob);
  }
  
  document.querySelector('.readBytesButtons').addEventListener('click', function(evt) {
    if (evt.target.tagName.toLowerCase() == 'button') {
      var startByte = evt.target.getAttribute('data-startbyte');
      var endByte = evt.target.getAttribute('data-endbyte');
      readBlob(startByte, endByte);
    }
  }, false);
</script>

Příklad: Zde je možné si vyzkoušet krájení souborů.

Číst byty:

Sledování průběhu čtení

Jednou z výhod při použití asynchronního zpracování událostí, je možnost sledovat průběh čtení souborů. To může být užitečné pro velké soubory, pokud potřebujeme odchytat chyby nebo když chceme vědět kdy bylo nahrávání dokončeno.

Události mohou být použity k monitorování při zpracovávání souboru. Používají se onloadstart a onprogress.

Níže uvedený příklad ukazuje zobrazení progress baru pro sledování stavu čtení. Chcete-li zobrazit ukazatel průběhu v akci, zkuste velký soubor nebo soubor ze vzdáleného disku.

<style>
  #progress_bar {
    margin: 10px 0;
    padding: 3px;
    border: 1px solid #000;
    font-size: 14px;
    clear: both;
    opacity: 0;
    -moz-transition: opacity 1s linear;
    -o-transition: opacity 1s linear;
    -webkit-transition: opacity 1s linear;
  }
  #progress_bar.loading {
    opacity: 1.0;
  }
  #progress_bar .percent {
    background-color: #99ccff;
    height: auto;
    width: 0;
  }
</style>

<input type="file" id="files" name="file" />
<button onclick="abortRead();">Cancel read</button>
<div id="progress_bar"><div class="percent">0%</div></div>

<script>
  var reader;
  var progress = document.querySelector('.percent');

  function abortRead() {
    reader.abort();
  }

  function errorHandler(evt) {
    switch(evt.target.error.code) {
      case evt.target.error.NOT_FOUND_ERR:
        alert('File Not Found!');
        break;
      case evt.target.error.NOT_READABLE_ERR:
        alert('File is not readable');
        break;
      case evt.target.error.ABORT_ERR:
        break;
      default:
        alert('An error occurred reading this file.');
    };
  }

  function updateProgress(evt) {
    // evt je ProgressEvent.
    if (evt.lengthComputable) {
      var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
      // Přibývání progress baru.
      if (percentLoaded < 100) {
        progress.style.width = percentLoaded + '%';
        progress.textContent = percentLoaded + '%';
      }
    }
  }

  function handleFileSelect(evt) {
    // Reset programu pro nový výběr.
    progress.style.width = '0%';
    progress.textContent = '0%';

    reader = new FileReader();
    reader.onerror = errorHandler;
    reader.onprogress = updateProgress;
    reader.onabort = function(e) {
      alert('File read cancelled');
    };
    reader.onloadstart = function(e) {
      document.getElementById('progress_bar').className = 'loading';
    };
    reader.onload = function(e) {
      // Ujistíme se, že pokud se dokončí proces progress bar ukáže 100%.
      progress.style.width = '100%';
      progress.textContent = '100%';
      setTimeout("document.getElementById('progress_bar').className='';", 2000);
    }

    // Číst soubor jako binární řetězec.
    reader.readAsBinaryString(evt.target.files[0]);
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

Příklad: Zde lze vyzkoušet monitoring při čtení souboru:

0%

Toto je pro dnešní tutoriál o JS vše. Doufám, že jste se přiučili něčemu novému a článek vám pomohl k realizaci vašich projektů.

Našli jste v článku chybu?

22. 8. 2012 9:11

Zajímavý článek, i když tady nejsou žádné reakce v diskusi, neznamená to, že je článek nezajímavý - rozhodně je!

Jen tak dál!

22. 8. 2012 23:20

Pokud budete trvat na přesnosti, neumožňuje to HTML5, a neumožňuje to ani JavaScript

1/ HTML5 - s ničím se smiřovat nemusíte, je přesně definováno, co to je (http://www.whatwg.org/specs/web-apps/current-work/multipage/, http://www.w3.org/TR/html5/)
2/ Technicky vzato, prohlížeče s ničím jako JavaScript nepracují, pracuji s ECMAScriptem a různými API implementovanými v ES (s JavaScriptem pracoval tuším Netscape, nevím, jak je to teď u FF s označením)

FileAPI není ani součástí HTML5 specifikace,…


120na80.cz: Rakovina oka. Jak ji poznáte?

Rakovina oka. Jak ji poznáte?

Vitalia.cz: Jak vybrat ořechy do cukroví a kde mají levné

Jak vybrat ořechy do cukroví a kde mají levné

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

120na80.cz: Horní cesty dýchací. Zkuste fytofarmaka

Horní cesty dýchací. Zkuste fytofarmaka

Podnikatel.cz: Babiš: E-shopy z EET možná vyjmeme

Babiš: E-shopy z EET možná vyjmeme

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

DigiZone.cz: ČRa DVB-T2 ověřeno: Hisense a Sencor

ČRa DVB-T2 ověřeno: Hisense a Sencor

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube