HTML5 File API, React et créativité

⏲️ ~13 min de lecture

Publié le par Pierre
JavaScript

L'API File de HTML5 est là depuis un petit moment maintenant et est désormais plutôt bien supportée (en lecture tout du moins). Elle a le mérite de pouvoir nous aider en tant que développeurs à proposer des solutions plus simples en tirant partie du navigateur client. Sur des parties administratives il est possible en quelques lignes de code de faciliter le traitement de fichiers, voire leur conversion.

Le but de cet article est de montrer à quel point il est simple d'utiliser cette API afin que vous l'envisagiez pour des tâches simples ou phases de prototypage.

TL;DR #

Le chargement et la création de fichiers dans un navigateur tiennent en moins de 20 lignes. Avec un peu de créativité, je suis sûr que nous pouvons trouver beaucoup d'utilisations concrètes au quotidien comme le montre l'exemple final s'apparentant à de l'Event Sourcing.

Les bibliothèques permettant l'utilisation de modules NodeJS côté client, ou les frameworks frontend ne font qu'agrandir le champ des possibilités d'un point de vue technique.

Un éditeur markdown simpliste #

Pour voir quelques exemples basiques d'utilisation de l'API dans le cadre d'une application concrète nous allons créer un éditeur Markdown simpliste contenant :

Comme nous sommes au top des technos, nous utiliserons la librairie React couplée à Dispatchr afin d'avoir un flux de données unidirectionnel. On pense à tout : en cette période de fêtes, vous pourrez fièrement glisser à tonton Michel entre la dinde et le fromage que vous travaillez sur des technos de Facebook et Yahoo ;)

Le code illustrant cet article est sur Github, ainsi qu'une démonstration du rendu final. Si seule la partie manipulation de la FileAPI vous intéresse, focalisez vous sur la section "Ajout de chargement / sauvegarde de fichiers".

ATTENTION : c'est ici ma première utilisation de React, le but n'est donc pas d'illustrer les bonnes pratiques de cette librairie ... veuillez excuser mes erreurs de débutant.

Application initiale #

Voici donc l'architecture de notre application initiale : un éditeur et la prévisualisation du rendu HTML, avec un flux de données unidirectionnel. Ci-dessous le schéma illustrant ce dernier :

Illustration de l'architecture initiale

L'éditeur #

Le composant d'édition est un composant React contenant un champ de texte.

Le code restant de ce composant est ce qui permet d'activer le flux présenté dans le schéma précédent ... préparez vous pour le départ :

Nous aurions pu ignorer cette dernière étape si tôt. Mais imaginez que le contenu soit modifié d'une autre manière, par exemple en chargeant des données depuis un fichier (exemple totalement fortuit ;)), notre éditeur aurait alors été désynchronisé !

La prévisualisation #

Le composant de prévisualisation du rendu est très proche de l'éditeur. La structure ayant déjà été mise en place précédemment, il suffit d'écouter les mises à jour du ContentStore et de mettre à jour le HTML d'après la source Markdown. Pour cela nous utilisons la librairie markdown-js.

Ça y est, nous avons maintenant une base suffisante pour pouvoir jouer avec nos fichiers. Le plus laborieux est fait !

Ajout de chargement / sauvegarde de fichiers #

Un éditeur c'est bien, mais pouvoir sauvegarder son contenu pour le partager ou le terminer plus tard c'est mieux ! Nous allons donc ajouter un nouveau composant à notre interface.

Celui-ci aura deux rôles distincts dans notre application :

Pour nous aider dans l'implémentation technique, nous allons créer deux sous-composants distincts qui ne contiendront aucune logique "métier".

Charger du contenu #

Le composant permettant de charger le contenu d'un fichier a un contrat très simple : dès que l'utilisateur sélectionne un fichier, il transmet le nom du fichier et son contenu à une méthode de callback.

Nous voici dans le vif du sujet ! Si vous ne devez retenir qu'une chose, c'est la taille du code nécessaire pour récupérer le contenu du fichier.

Il nous faut un champ de type file. Celui-ci expose une API vous permettant de récupérer une liste des fichiers sélectionnées par l'utilisateur, par sa propriété Element.files, de type FileList. Cela permet d'accéder par un index de tableau ou par la méthode item(index) à un objet File.

Une fois que l'objet File récupéré il faut accéder à son contenu. Pour cela, nous passons par un FileReader qui permettra de lire le contenu de différentes manières selon le type de données (texte ou binaire). De plus vous pouvez écouter les différentes phases du chargement de fichier afin, par exemple, d'afficher une barre de progression à l'utilisateur ou de bloquer certaines interactions durant le chargement d'un gros fichier.

Dans notre cas nous utilisons simplement FileReader.readAsText(file) en écoutant l'évènement de fin de chargement pour pouvoir transmettre le contenu du fichier à la fonction de traitement "métier" (passée à notre composant FileUploader par la propriété onUpload).

Sachez enfin que cette interface est compatible avec tous les navigateurs récents (IE10+), donc pas de limite de ce côté là. Je vous invite à prendre 1 minute afin de regarder la documentation de FileReader car cela peut vous donner des idées d'utilisation des fichiers reçus. Quelques exemple :

Sauvegarder la source #

Passons maintenant au second composant dont nous aurons besoin techniquement pour permettre à l'utilisateur de télécharger un fichier contenant la source Markdown.

Le contrat de ce composant est lui aussi simple : dès que l'utilisateur clique sur un lien, il demande les informations "métier" du fichier (contenu et nom) par un callback getFileData puis crée et fait télécharger le fichier au client.

Là encore, la création d'un fichier en terme de code est ultra simple car il suffit de créer un objet de type Blob. Le constructeur accepte un tableau de contenu pouvant être de pas mal de types différents (ArrayBuffer, ArrayBufferView, Blob ou DOMString), il est donc en pratique très simple de "sérialiser" un ensemble de données sous forme de Blob (y compris des images et autres données binaires).

Une fois le Blob créé vous avez la possibilité de lui assigner une URL temporaire pour le navigateur client. Cette URL vous permet ensuite d'y rediriger l'utilisateur, ou comme mentionné précédemment de l'assigner comme src à une image voir une iframe. La création d'une URL se fait par l'appel à URL.createObjectURL(blob) disponible dans le navigateur. Voici un exemple d'URL générée : blob:http%3A//real34.github.io/dc1f911f-f6ae-46f9-89bb-090a57fddac7

Ce qui est chouette c'est qu'en terme de compatibilité navigateur nous sommes jusqu'ici toujours compatible avec plus de 90% des navigateurs (IE10+), et pour le reste il y a Blob.js !

Nous arrivons maintenant aux limites de compatibilités actuelles. En effet, la spécification FileWriterAPI du W3C ayant été abandonnée il n'y a plus beaucoup de moyens standards de demander la sauvegarde d'un fichier au navigateur. Voici quelques pistes possibles pour parvenir à nos fins :

C'est cette dernière solution que nous avons implémenté dans notre composant, afin de pouvoir contrôler le nom du fichier téléchargé : cette technique n'est compatible qu'avec Chrome et Firefox, soit près de 70% du parc Français aujourd'hui ... à garder en tête avant de la réutiliser.

Intégration dans l'application #

Nous venons de créer deux composants techniques nous permettant désormais de travailler sur l'interaction avec des fichiers simplement. Il est donc temps d'ajouter la fonctionnalité souhaitée à notre éditeur : sauvegarder la source actuelle dans un fichier et charger le contenu d'un fichier dans notre application.

Le code nécessaire est minimaliste (deux méthodes de callbacks "métier" : handleContentUpload et downloadedFileData) car il ne suffit plus qu'à connecter chaque partie de l'application les unes aux autres :

Pour ce faire, nous avons tiré parti de l'architecture en place et il nous a suffit :

... cela sera désormais la même chose à chaque fois, il ne reste plus qu'à être créatif !

Déboguage par fichiers #

Nous avons tout en place pour nous amuser un petit peu et explorer quelques pistes d'utilisation de la FileAPI pour ajouter des fonctionnalités simplement à une application existante. Pour se faire nous allons nous focaliser sur des utilisations à but de déboguage.

Ajoutons alors une barre de déboguage super pratique à notre application ! J'entrerai moins dans les détails, mais le code ci-après devrait vous permettre de visualiser la simplicité de mise en oeuvre.

Représenter l'état d'une application #

Lorsqu'un problème survient, une des premières choses qu'il faut comprendre est "quel est l'état de l'application qui pose problème" (pour la corriger vers l'état qui aurait été attendu). Je vous propose donc d'ajouter un premier composant à notre barre de déboguage, qui nous permettra de :

Grâce aux solutions choisies pour illustrer cet article c'est très simple ! Jugez par vous même :

En effet, nous utilisons les fonctionnalités de Dispatchr permettant le développement d'applications JS isomorphiques afin d'avoir un état sérialisé de l'interface. Pour cela nous avons dû implémenter les méthodes dehydrate / rehydrate au sein du ContentStore.

Tout le reste est "automatique" grâce aux propriétés de React et à l'architecture unidirectionnelle en place.

Représenter l'évolution d'une application #

Lorsqu'on y réfléchit un peu, l'état d'une application n'est pas ce qui contient les informations nécessaires à la compréhension d'un problème. Ce qui permet de bien comprendre est plutôt l'enchaînement d'évènements qui a donné lieu au problème !

Au lieu de simplement pouvoir transmettre l'état d'une application à un instant t, nous allons permettre aux utilisateurs de nous transmettre l'intégralité de ce qu'ils ont fait sur la page. Et le pire c'est que c'est facile à faire !

En effet, si nous regardons l'architecture en place de plus près, nous nous rendons compte qu'il nous suffit de transmettre deux choses afin de tout pouvoir reconstruire et analyser :

Cliquez sur l'image pour voir une animation du rendu :

React exemple evenements

C'est article est déjà bien assez long, aussi je vous invite à analyser le code de démonstration disponible pour juger de la facilité de mise en oeuvre. Nous avons ajouté :

Note : nous venons de toucher du doigt une autre approche de persistance des données qui se nomme l'Event Sourcing. Je vous invite à vous renseigner sur ce sujet passionnant ;)

Les limites techniques #

Tout ceci ne reste bien sûr valable que pour des besoins simples, et la solution de manipulation de fichiers a des limites qu'il faut garder en tête :

Il n'y a pas de limites à votre imagination #

Quelques idées de choses possibles avec l'API, en ne restant qu'avec des fichiers textes (de nombreux exemples existent déjà avec des images par exemple) :

... avec des outils comme Browserify rendant disponibles côté client de nombreux modules NodeJS, cela ne fait qu'agrandir le champ des possibles.

Enfin, gardez en tête que d'autres solutions telles que IndexedDb, remotestorage.io ou firebase vous permettront chacune à leur manière de lâcher l'utilisation de fichiers le moment venu, pour des choses plus robustes et fonctionnalités plus intéressantes !

Cet article vous a plu ? Sachez que nous recrutons !

← Accueil