Mis à jour le mardi 5 septembre 2017
  • 40 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

L'API File

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

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é files de 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 notre input ne 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ême input ! Il vous suffit d'y ajouter l'attribut multiple pour que cela soit autorisé au client :

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

La propriété files est 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 utiliserez files[0] et non pas file.

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

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 objets Blob et File !

L'objet Blob

Un objet de type Blob est 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 un Blob ? 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 un Blob par 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 objets Blob possèdent deux propriétés nommées size et type qui permettent respectivement de récupérer la taille en octets des données manipulées par le Blob ainsi que leur type MIME.
Il existe également une méthode nommée slice(), 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'objet File

Les objets File possè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 objets Blob, 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 objets Blob, les objets File possèdent deux propriétés supplémentaires qui sont name pour obtenir le nom du fichier et lastModifiedDate pour obtenir la date de la dernière modification du fichier (sous forme d'objet Date bien évidemment).

Et c'est tout ?

Heureusement que non ! Bien que les objets File ne 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 type FileReader !

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 type ArrayBuffer. 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é. Les DataURL permettent 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 type Blob ou File. La méthode readAsText() 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'objet XMLHttpRequest :

Nom

Description

loadstart

La lecture vient de commencer.

progress

Tout comme avec les objets XHR, l'événement progress se 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, loaded et total, 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éthode abort() par exemple).

error

Se déclenche quand une erreur a été rencontrée. La propriété error contiendra 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'objet FileReader possède aussi une propriété readyState permettant de connaître l'état de la lecture. Il existe trois états différents représentés par des constantes spécifiques aux objets FileReader :

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'objet XMLHttpRequest afin 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'attribut multiple afin 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 variable allowedTypes, 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 à un split('.') 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 attribut src. 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 les DataURL ! Rappelez-vous, nous avions bien précisé que les DataURL permettaient 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 fonction createThumbnail() à 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 fonction createThumbnail() :

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 objet FileReader, lui attribuons un événement load, puis lançons la lecture du fichier pour une DataURL. Une fois la lecture terminée, l'événement load se 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'objet XMLHttpRequest, car celui-ci ne supportait pas l'utilisation de l'objet FormData (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 du XMLHttpRequest, cette « prouesse » est maintenant réalisable facilement.

Ainsi, il est maintenant très simple de créer des données binaires (grâce à un Blob) 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 type File, 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ête XMLHttpRequest :

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 objet File à un objet FormData et 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é upload donnant accès à plusieurs événements dont l'événement progress. Ce dernier fonctionne exactement de la même manière que le précédent événement progress que 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 objet File qui hérite lui-même de l'objet Blob, 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>.

Exemple de certificat de réussite
Exemple de certificat de réussite