HTML
Børre Stenseth

FileReader

Hva

Vi vet at vi kan la leseren identifisere filer på sin egen harddisk, enten ved å browse, se modulen Forms Files eller ved å dra filer inn på websiden, se modulen DragDrop . I denne module frisker vi opp dette, og vi skal se hvordan vi kan lese innholdet i filer som brukeren har gitt websiden tilgang til på en av disse måtene.

Grunnlaget for dette er W3C File API [1] . Mozzila Developer Network [2] gir en rimelig grei innføring.

File

I følge W3C File API [1] gir et File-object oss tilgang til følgende egenskaper:

  • size
  • name
  • lastModifiedDate
  • type (mimetype)

Merk at name er bare selve filnavnet. Vi har ikke tilgang til filstien.

Vi lar brukeren identifisere filer ved hjelp av browse og drag-drop.

Browse

Vi kan gjøre slik:

finn 1 textfil
<fieldset style="width:300px;padding:20px">
<legend>finn 1 textfil</legend>
<input type="file"  autocomplete="off" id="txtfile" accept="text/*"/>
<output id="outtxtfile"> </output>
</fieldset>

Merk accept attributten. Denne sier hvilke filer som vises når vi browser. Vi kan angi dette på flere måter:

  • file extension: accept=".text" eller flere file extensions: accept=".text,.csv"
  • mediatype [3] : accept="text/*", eller accept="text/html" eller accept="image/*,video/*"

Følgende eventhandler kjører når brukeren har valgt fil:

function handleFileSelect1(evt) {
    var f = evt.target.files[0];
    result=[];
    result.push(encodeURI(f.name),'(',f.type || 'n/a', ') :  ',f.size,' bytes')
    document.getElementById('outtxtfile').innerHTML = result.join('');
}

Metoden er plantet slik når siden lastes:

document.getElementById('txtfile')
  .addEventListener('change', handleFileSelect1, false);

Vi kan tillate valg av flere filer ved å sette attributte multiple i input elementet.

Dersom vi forsøker å skrive: s=JSON.stringify(f)), der f er et filobjekt, så får vi 0. Dette er altså ikke noen måte å finne ut mer om fila, f.eks. filstien.

Drag

drag filer hit
<fieldset id="dropfiles1" style="width:300px;padding:20px">
<legend>drag filer hit</legend>
<output id="list"></output>
</fieldset>

Følgende eventhandlere kjører når brukeren har droppet filen(e):

function handleFileSelect2(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files; // FileList 
    var result = [];
    for (var i = 0, f; f = files[i]; i++) {
      result.push('<li><strong>', escape(f.name), '</strong> (', 
        f.type || 'n/a', ') - ',
        f.size, ' bytes',
        ' , modified: ',
        f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 
            'n/a','</li>');
    }
    document.getElementById('list').innerHTML = 
        '<ul>' + result.join('') + '</ul>';
  }
  function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy';
  }

Metoden er plantet slik når siden lastes:

var dropZone = document.getElementById('dropfiles');
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect2, false);

FileReader

Når websiden nå har fått tilgang til informasjon om filer på vår harddisk kan vi lese disse filene med FileReader. En fil kan oppfattes enten som en Blob eller en File. En Blob betrakter fila som en serie med bytes( 0,255) og gir mulighet for selektere byte-sekvenser. FileReader gir oss tilgang til følgende metoder for File eller Blob:

  • abort()
  • readAsArrayBuffer(Blob|File)
  • readAsBinaryString(Blob|File)
  • readAsDataURL(Blob|File)
  • readAsText(Blob|File,encoding)

Merk at vi selvsagt ikke får lov til å skrive. Vi kan lese filene på litt forskjellig måte og vi kan abortere en leseoperasjon. FileReader har attributter (error, readyState, result) og eventhandlere (onabort, onerror, onload, onloadstart, onloadend, onprogress) som tilsammen gir oss kontroll.

Dette gir oss en åpen situasjon og det er vanskelig å vise vanntette eksempler på hva det er mulig å foreta seg siden det avhenger av hva du, som leser, finner på når det gjelder å lokalisere filer på din harddisk. Nedenfor finner du noen eksempler på noen øvelser som du kan forsøke deg på. Eksemplene bruker browse, men det kunne like godt vært gjort ved drag.

Eksempel 1

Vi forsøker først å laste opp en ren textfil (.txt), og vise den i en pre-tag. Vi bruker browse.

finn 1 textfil

<fieldset style="width:300px;padding:20px">
<legend>finn 1 textfil</legend>
<input type="file"  autocomplete="off" id="txtfile3" accept=".txt"/>
<output id="outtxtfile3"> </output>
</fieldset>
<pre style="border-style:solid;border-width:thin" id="filecontent3">
</pre>

Følgende eventhandler kjører når brukeren har valgt fil:

function handleFileSelect3(evt) {
    var f = evt.target.files[0];
    found=[];
    found.push(encodeURI(f.name),'(',f.type || 'n/a', ') : ',
               f.size,' bytes')
    document.getElementById('outtxtfile3').innerHTML = found.join('');
    txtType = /text.*/;
    if(!f.type.match(txtType)){
        document.getElementById('filecontent3').innerHTML=
            "bad MIME type";
        return;
    }
    reader=new FileReader();
    reader.onload=function(e) {
            document.getElementById('filecontent3').innerHTML=
                reader.result;
        }
    reader.readAsText(f);
}

Metoden er plantet slik når siden lastes:

document.getElementById('txtfile3')
    .addEventListener('change', handleFileSelect3, false);

Vi har da kontroll over innholdet i fila brukeren har vist oss og vi kan ta innholdet og manipulere det etter ønske eller sende det, eventuelt sammen med noe annet, i en AJAX-request. Det vi ikke kan gjøre er å skrive noe tilbake til brukerens harddisk. Et interessant eksperiment vil kanskje være om vi kunne lagre innholdet med f.eks. localStorage slik at det dukker opp når brukeren melder seg igjen. Kanskje ikke så lurt med mye informasjon, siden localStorage tross alt har begrenset kapasitet.

Eksempel 2

I dette eksempelet lar vi brukeren, deg, velge flere bildefiler og viser fram thumbnails.

finn bildefil(er)
<fieldset style="width:350px;padding:20px">
<legend>finn bildefil(er)</legend>
<input type="file"  autocomplete="off" id="imgfile" multiple="true" accept="image/*"/>
<output id="outimgfiles"> </output>
</fieldset>
<div style="border-style:solid;border-width:thin" id="filecontent4"> </div>

Følgende eventhandler kjører når brukeren har valgt filer:

function handleFileSelect4(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.target.files; // FileList 
    var found = [];
    for (var i = 0, f; f = files[i]; i++) {
      found.push('<li><strong>', escape(f.name), '</strong> (', 
                   f.type || 'n/a', ') - ',
                   f.size, ' bytes</li>');
    }
    document.getElementById('outimgfiles').innerHTML = 
        '<ul>' + found.join('') + '</ul>';
    
     var txtType = /image.*/;
    var showElt=document.getElementById("filecontent4");
    showElt.innerHTML="";
    for (var i = 0, f; f = files[i]; i++) {
        if(!f.type.match(txtType))
            continue;
        var img = document.createElement("img");
        img.classList.add("thumb");
        img.file = f;
        showElt.appendChild(img);
        reader=new FileReader();
        reader.onload=(function(theImg) { 
            return function(e) { theImg.src = e.target.result; }; 
        })(img);
        reader.readAsDataURL(f);
        }
}

Metoden er plantet slik når siden lastes:

document.getElementById('imgfile')
	.addEventListener('change', handleFileSelect4, false);

Når bilder som lastes på denne måte legges ut på websiden legges de ikke i det formatet vi vanligvis bruker:

<img  class="thumb" src="bilde.png" alt="bilde"/>

De legges ut slik:

<img class="thumb" src="data:image/gif;base64,R0lGODlh…BACxA0...."></img>

(der dataene er forkortet)

Referanser
  1. File API W3C dev.w3.org/2006/webapi/FileAPI/ 14-03-2014
  1. Using files from web applications Mozilla developer.mozilla.org/en-US/docs/Using_files_from_web_applications 14-03-2014
  1. Media Types iana www.iana.org/assignments/media-types/media-types.xhtml 14-03-2016