L'API File

Auparavant, la gestion des fichiers était extrêmement limitée avec le JavaScript, les actions possibles étaient peu intéressantes, à la fois pour le développeur et l'utilisateur. En revanche, le HTML5 fournit maintenant une API nommée « File ». Celle-ci est nettement plus intéressante que ce à quoi nous étions limités avant son implémentation. Il est maintenant possible de manipuler un ou plusieurs fichiers afin de les uploader ou d'obtenir des informations, comme leur poids par exemple.

L'objectif de ce chapitre est de vous fournir un tour d'horizon de l'API File.

Première utilisation

L'API que nous allons découvrir n'est pas utilisable seule. Autrement dit, elle nécessite d'être appelée par diverses technologies permettant son accès et lui fournissant les fichiers qu'elle peut manipuler. Cette API a été conçue de cette manière afin d'éviter que ce ne soit vous, développeurs, qui choisissiez quel fichier lire sur l'ordinateur du client. Si cette sécurité n’existait pas, les conséquences pourraient être désastreuses.

Afin de pouvoir utiliser notre API, il va nous falloir définir comment les fichiers vont pouvoir être choisis par l'utilisateur. La solution la plus simple pour commencer est l'utilisation d'une balise<input type="file" />, qui va nous permettre d'accéder aux propriétés des fichiers sélectionnés par l'utilisateur. Ces propriétés constituent une partie de l'API File.

Prenons donc une balise toute simple :

<input id="file" type="file" />

Et ajoutons-lui un événement :

document.querySelector('#file').addEventListener('change', function() {
// Du code…
});

Pour accéder au fichier il va nous falloir passer par la propriétéfilesde notre balise<input>. Celle-ci va nous permettre d'accéder à une collection d'objets utilisables de la même manière qu'un tableau, chaque objet représentant un fichier.

Pourquoi une collection d'objets, alors que notreinputne nous permet de sélectionner qu'un seul fichier ?

Eh bien, parce que le HTML5 a ajouté la possibilité de choisir plusieurs fichiers pour un seul et mêmeinput! Il vous suffit d'y ajouter l'attributmultiplepour que cela soit autorisé au client :

<input id="file" type="file" multiple />

La propriétéfilesest la même pour tous, que la sélection de fichiers soit multiple ou non. Si vous voulez lire le fichier d'un<input>ne gérant qu'un seul fichier, alors vous utiliserezfiles[0]et non pasfile.

Maintenant que ce point est éclairci, essayons d'obtenir le nom du fichier sélectionné grâce à la propriéténamecontenue dans chaque objet de typeFile:

document.querySelector('#file').addEventListener('change', function() {
alert(this.files[0].name);
});

Essayer le code

Alors, certes, l'utilité est vraiment moindre, mais vous allez vite découvrir de nombreuses possibilités d'utilisation au cours des paragraphes suivants. Ici, nous allons tâcher de vous faire découvrir l'API File, nous aurons donc beaucoup de théorie, mais la mise en pratique viendra après.

Les objets Blob et File

Actuellement, notre utilisation de l'API File s'est limitée à l'obtention du nom du fichier. Cependant, il existe bien plus d'informations que cela grâce aux objetsBlobetFile!

L'objetBlob

Un objet de typeBlobest une structure représentant des données binaires disponibles uniquement en lecture seule. La plupart du temps, vous rencontrerez ces objets uniquement lorsque vous manipulerez des fichiers, car ces objets représentent les données binaires du fichier ciblé.

Concrètement, que pouvons-nous faire avec unBlob? Eh bien, pas grand-chose au final… Enfin, pas en l'utilisant seul tout du moins. Car, bien qu'il soit possible de créer unBlobpar nous-mêmes (avec l'objet BlobBuilder
), nous ne le ferons quasiment jamais puisque nous utiliserons ceux créés lors de la manipulation de fichiers, ce que nous verrons par la suite.

Les objetsBlobpossèdent deux propriétés nomméessizeettypequi permettent respectivement de récupérer la taille en octets des données manipulées par leBlobainsi que leur type MIME .
Il existe également une méthode nomméeslice(), mais c'est un sujet bien trop avancé et peu utile. Si vous souhaitez en savoir plus sur cette fonction, nous vous invitons à consulter la documentation du MDN .

L'objetFile

Les objetsFilepossèdent un nom bien représentatif puisqu'ils permettent de manipuler les fichiers. Leur particularité est qu'ils héritent des propriétés et méthodes des objetsBlob, voilà pourquoi nous ne créerons quasiment jamais ces derniers par nous-mêmes.

Donc, en plus des propriétés et méthodes des objetsBlob, les objetsFilepossèdent deux propriétés supplémentaires qui sontnamepour obtenir le nom du fichier etlastModifiedDatepour obtenir la date de la dernière modification du fichier (sous forme d'objetDatebien évidemment).

Et c'est tout ?

Heureusement que non ! Bien que les objetsFilene soient pas intéressants en terme d'informations, ils le deviennent soudainement bien plus lorsque l'on commence à aborder leur lecture, grâce aux objets de typeFileReader!

Lire les fichiers

Comme précisé précédemment, nous allons aborder la lecture des fichiers grâce à l'objet FileReader
. Son instanciation s'effectue sans aucun argument :

var reader = new FileReader();

Cet objet permet la lecture asynchrone de fichiers, et ce grâce à trois méthodes différentes :

Nom

Description

readAsArrayBuffer()

Stocke les données dans un objet de typeArrayBuffer. Ces objets ont été conçus pour permettre l'écriture et la lecture de données binaires directement dans leur forme native, ils sont surtout utilisés dans des domaines exigeants tels que le WebGL . Il y a peu de chances pour que vous utilisiez un jour cette méthode.

readAsDataURL()

Les données sont converties dans un format nommé DataURL
. Ce format consiste à convertir toutes les données binaires d'un fichier en base64 pour ensuite stocker le résultat dans une chaîne de caractères. Cette dernière est complétée par la spécification du type MIME du fichier concerné. LesDataURLpermettent donc de stocker un fichier sous forme d'une URL lisible par les navigateurs récents, leur utilisation est de plus en plus fréquente sur le Web.

readAsText()

Les données ne subissent aucune modification, elles sont tout simplement lues puis stockées sous forme d'une chaîne de caractères.

Ces trois méthodes prennent chacune en paramètre un argument de typeBlobouFile. La méthodereadAsText()possède un argument supplémentaire (et facultatif) permettant de spécifier l'encodage du fichier, qui s'utilise comme ceci :

reader.readAsText(file, 'UTF-8');
reader.readAsText(file, 'ISO-8859-1');

Avant d'utiliser l'une de ces méthodes, rappelez-vous que nous avons bien précisé que la lecture d'un fichier est asynchrone ! Il faut donc partir du principe que vous allez avoir plusieurs événements à votre disposition. Ces événements diffèrent peu de ceux que l'on rencontre avec la seconde version de l'objetXMLHttpRequest:

Nom

Description

loadstart

La lecture vient de commencer.

progress

Tout comme avec les objets XHR, l'événementprogressse déclenche à intervalles réguliers durant la progression de la lecture. Il fournit, lui aussi, un objet en paramètre possédant deux propriétés,loadedettotal, indiquant respectivement le nombre d'octets lus et le nombre d'octets à lire en tout.

load

La lecture vient de se terminer avec succès.

loadend

La lecture vient de se terminer (avec ou sans succès).

abort

Se déclenche quand la lecture est interrompue (avec la méthodeabort()par exemple).

error

Se déclenche quand une erreur a été rencontrée. La propriétéerrorcontiendra alors un objet de type FileError
 pouvant vous fournir plus d'informations.

Une fois les données lues, il ne vous reste plus qu'à les récupérer dans la propriétéresult. Ainsi, afin de lire un fichier texte, vous n'avez qu'à faire comme ceci :

<input id="file" type="file" />
<script>
var fileInput = document.querySelector('#file');
fileInput.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('load', function() {
alert('Contenu du fichier "' + fileInput.files[0].name + '" :\n\n' + reader.result);
});
reader.readAsText(fileInput.files[0]);
});
</script>

Essayer le code

Pour finir sur la lecture des fichiers, sachez que l'objetFileReaderpossède aussi une propriétéreadyStatepermettant de connaître l'état de la lecture. Il existe trois états différents représentés par des constantes spécifiques aux objetsFileReader:

Constante

Valeur

Description

EMPTY

0

Aucune donnée n'a encore été chargée.

LOADING

1

Les données sont en cours de chargement.

DONE

2

Toutes les données ont été chargées.

Tout comme avec un objet XHR, vous pouvez vérifier l'état de la lecture, soit avec la constante :

if (reader.readyState === reader.LOADING) {
// La lecture est en cours...
}

soit directement avec la valeur de la constante :

if (reader.readyState === 1) {
// La lecture est en cours...
}

Mise en pratique

L'étude de l'API File est maintenant terminée. Il est probable que vous vous demandiez encore ce que nous lui trouvons d'exceptionnel... Eh bien, il est vrai que, si nous l'utilisons uniquement avec des balises<input>, alors nous sommes assez limités dans son utilisation. Ce chapitre couvre la base de l'API File, son utilisation seule, mais il faut savoir que le principal intérêt de cette API réside en fait dans son utilisation avec d'autres ressources. Ainsi, un petit peu plus loin dans ce chapitre, nous allons étudier comment l'utiliser conjointement avec l'objetXMLHttpRequestafin d'effectuer des uploads ; nous verrons aussi, dans un chapitre ultérieur, comment s'en servir efficacement avec le Drag & Drop. Bref, ne vous en faites pas, nous n'en avons pas encore terminé avec cette fameuse API.

Nous allons ici faire une mise en pratique plutôt sympathique de cette API. Le scénario est le suivant : vous souhaitez créer un site d'hébergement d'images interactif. Le principe est simple, l'utilisateur sélectionne les images qu'il souhaite uploader, elles sont alors affichées en prévisualisation sur la page et l'utilisateur n'a plus qu'à cliquer sur le bouton d'upload une fois qu'il aura vérifié qu'il a bien sélectionné les bonnes images.

Notre objectif, ici, est de créer la partie concernant la sélection et la prévisualisation des images, l'upload ne nous intéresse pas. Afin d'obtenir le résultat escompté, nous allons devoir utiliser l'API File, qui va nous permettre de lire le contenu des fichiers avant même d'effectuer un quelconque upload.

Commençons par construire la page HTML qui va accueillir notre script :

<input id="file" type="file" multiple />
<div id="prev"></div>

Il n'y a pas besoin de plus, nous avons notre balise<input>pour sélectionner les fichiers (avec l'attributmultipleafin de permettre la sélection de plusieurs fichiers) ainsi qu'une balise<div>pour y afficher les images à uploader.

Il nous faut maintenant passer au JavaScript. Commençons par mettre en place la structure principale de notre script :

(function() {
var allowedTypes = ['png', 'jpg', 'jpeg', 'gif'],
fileInput = document.querySelector('#file'),
prev = document.querySelector('#prev');
fileInput.addEventListener('change', function() {
// Analyse des fichiers et création des prévisualisations
});
})();

Ce code déclare les variables et les événements nécessaires. Vous constaterez qu'il existe une variableallowedTypes, celle-ci contient un tableau listant les extensions d'images dont nous autorisons l'upload. L'analyse des fichiers peut maintenant commencer. Sachant que nous avons autorisé la sélection multiple de fichiers, nous allons devoir utiliser une boucle afin de parcourir les fichiers sélectionnés. Il nous faudra aussi vérifier quels sont les fichiers à autoriser :

fileInput.addEventListener('change', function() {
var files = this.files,
filesLen = files.length,
imgType;
for (var i = 0; i < filesLen; i++) {
imgType = files[i].name.split('.');
imgType = imgType[imgType.length - 1].toLowerCase(); // On utilise toLowerCase() pour éviter les extensions en majuscules
if (allowedTypes.indexOf(imgType) != -1) {
// Le fichier est bien une image supportée, il ne reste plus qu'à l'afficher
}
}
});

Les fichiers sont parcourus puis analysés. Sur les lignes 9 et 10 nous faisons l'extraction de l'extension du fichier en faisant un découpage de la chaîne de caractères grâce à unsplit('.')et nous récupérons le dernier élément du tableau après l'avoir passé en caractères minuscules. Une fois l'extension obtenue, nous vérifions sa présence dans le tableau des extensions autorisées (ligne 12).

Il nous faut maintenant afficher l'image, comment allons-nous nous y prendre ? L'affichage d'une image, en HTML, se fait grâce à la balise<img>, or celle-ci n'accepte qu'une URL en guise de valeur pour son attributsrc. Nous pourrions lui fournir l'adresse du fichier à afficher, mais nous ne connaissons que son nom, pas son chemin. La réponse se trouve dans lesDataURL! Rappelez-vous, nous avions bien précisé que lesDataURLpermettaient de stocker des données dans une URL, c’est exactement ce qu'il nous faut ! Tout d'abord, avant de commencer cet affichage, plaçons un appel vers une fonctioncreateThumbnail()à la 14e ligne de notre précédent code :

if (allowedTypes.indexOf(imgType) != -1) {
createThumbnail(files[i]);
}

Nous pouvons maintenant passer à la création de notre fonctioncreateThumbnail():

function createThumbnail(file) {
var reader = new FileReader();
reader.addEventListener('load', function() {
// Affichage de l'image
});
reader.readAsDataURL(file);
}

Comme vous pouvez le constater, il n'y a rien de compliqué là-dedans, nous instancions un objetFileReader, lui attribuons un événementload, puis lançons la lecture du fichier pour uneDataURL. Une fois la lecture terminée, l'événementloadse déclenche si tout s'est terminé correctement, il ne nous reste donc plus qu'à afficher l'image :

reader.addEventListener('load', function() {
var imgElement = document.createElement('img');
imgElement.style.maxWidth = '150px';
imgElement.style.maxHeight = '150px';
imgElement.src = this.result;
prev.appendChild(imgElement);
});

Et voilà, notre script est terminé ! Vous pouvez l'essayer en ligne et voir les codes complets :

<input id="file" type="file" multiple />
<div id="prev"></div>
(function() {
function createThumbnail(file) {
var reader = new FileReader();
reader.addEventListener('load', function() {
var imgElement = document.createElement('img');
imgElement.style.maxWidth = '150px';
imgElement.style.maxHeight = '150px';
imgElement.src = this.result;
prev.appendChild(imgElement);
});
reader.readAsDataURL(file);
}
var allowedTypes = ['png', 'jpg', 'jpeg', 'gif'],
fileInput = document.querySelector('#file'),
prev = document.querySelector('#prev');
fileInput.addEventListener('change', function() {
var files = this.files,
filesLen = files.length,
imgType;
for (var i = 0; i < filesLen; i++) {
imgType = files[i].name.split('.');
imgType = imgType[imgType.length - 1];
if (allowedTypes.indexOf(imgType) != -1) {
createThumbnail(files[i]);
}
}
});
})();

Upload de fichiers avec l'objet XMLHttpRequest

Il était auparavant impossible d'uploader des données binaires avec l'objetXMLHttpRequest, car celui-ci ne supportait pas l'utilisation de l'objetFormData(qui, de toute manière, n'existait pas à cette époque). Cependant, depuis l'arrivée de ce nouvel objet ainsi que de la deuxième version duXMLHttpRequest, cette « prouesse » est maintenant réalisable facilement.

Ainsi, il est maintenant très simple de créer des données binaires (grâce à unBlob) pour les envoyer sur un serveur. En revanche, il est bien probable que créer vos propres données binaires ne vous intéresse pas, l'upload de fichiers est nettement plus utile, non ? Alors, ne tardons pas et étudions cela !

Afin d'effectuer un upload de fichiers, il vous faut tout d'abord récupérer un objet de typeFile, il nous faut donc un<input>:

<input id="file" type="file" />

À cela, ajoutons un code JavaScript qui récupère le fichier spécifié et s'occupe de créer une requêteXMLHttpRequest:

var fileInput = document.querySelector('#file');
fileInput.addEventListener('change', function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://exemple.com'); // Rappelons qu'il est obligatoire d'utiliser la méthode POST quand on souhaite utiliser un FormData
xhr.addEventListener('load', function() {
alert('Upload terminé !');
});
// Upload du fichier…
});

Maintenant, que fait-on ? C'est très simple, il nous suffit de passer notre objetFileà un objetFormDataet d'uploader ce dernier :

var form = new FormData();
form.append('file', fileInput.files[0]);
xhr.send(form);

Essayer le code complet

Et ? C'est tout ?

Plus ou moins. L'upload de fichiers par le biais d'un objet XHR ne va pas révolutionner votre façon de coder. Il permet juste de simplifier les choses puisque l'on n'a plus à s'embêter à passer par le biais d'une<iframe>.
En revanche, il nous reste encore un petit quelque chose en plus à étudier et cela va sûrement vous intéresser : afficher la progression de l'upload ! L'objet XHR est déjà nettement plus intéressant, non ?

Nous n'avions pas étudié cela plus tôt, car vous n'auriez pas été capables de vous en servir de manière utile, mais sachez que l'objet XHR possède une propriétéuploaddonnant accès à plusieurs événements dont l'événementprogress. Ce dernier fonctionne exactement de la même manière que le précédent événementprogressque nous avions étudié dans le chapitre consacré à l'objet XHR :

xhr.upload.addEventListener('progress', function(e) {
e.loaded; // Nombre d'octets uploadés
e.total; // Total d'octets à uploader
});

Ainsi, il est facile de faire une barre de progression avec cet événement et la balise HTML5<progress>:

<input id="file" type="file" />
<progress id="progress"></progress>
var fileInput = document.querySelector('#file'),
progress = document.querySelector('#progress');
fileInput.addEventListener('change', function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'upload.html');
xhr.upload.addEventListener('progress', function(e) {
progress.value = e.loaded;
progress.max = e.total;
});
xhr.addEventListener('load', function() {
alert('Upload terminé !');
});
var form = new FormData();
form.append('file', fileInput.files[0]);
xhr.send(form);
});

Essayer le code

En résumé
  • L'API File permet de manipuler les fichiers au travers d'un objetFilequi hérite lui-même de l'objetBlob, conçu pour manipuler les données binaires.

  • Il est maintenant possible de lire le contenu d'un fichier sans avoir à passer par un quelconque serveur.

  • Les fichiers peuvent être utilisés au travers de plusieurs autres technologies telles que l'AJAX ou la balise<canvas>.