Č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.

widgety

<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?
Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Podnikatel.cz: Insolvence LevneElektro.cz? Začíná boj o peníze

Insolvence LevneElektro.cz? Začíná boj o peníze

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

DigiZone.cz: Test: brýle pro virtuální realitu Exos Urban

Test: brýle pro virtuální realitu Exos Urban

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Vitalia.cz: Jsou vegani a vyrábějí nemléko

Jsou vegani a vyrábějí nemléko

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

Podnikatel.cz: Dva měsíce na EET. Budou stačit?

Dva měsíce na EET. Budou stačit?

Vitalia.cz: Inspekce našla nelegální sklad v SAPĚ. Zase

Inspekce našla nelegální sklad v SAPĚ. Zase

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Podnikatel.cz: EET pro e-shopy? Postavené na hlavu

EET pro e-shopy? Postavené na hlavu

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští