Les moteurs Javascript s’améliorent, de nouvelles API sont introduites, les spécifications HTML et CSS évoluent et permettent au développeur d’envisager des applications purement web là où autrefois nous aurions eu besoin d’applications lourdes ou natives dans le cas d’application pour smartphone/tablette. Depuis quelques années, ces différentes technologies sont regroupées sous le terme HTML5. Il ne faut pourtant pas se méprendre, ce terme est purement marketing et rassemble un ensemble de technologies qui vont bien au delà du langage HTML.html5
On pourrait donc citer HTML dans sa version 5, avec de nouvelles balises (video, audio, header, …), CSS3 (bordures arrondies, fonds multiples, dégradés, …) et de nouvelles API Javascript ( IndexedDB, getUserMedia, WebGL, …).

Pour cet article, nous allons nous intéresser aux différentes possibilités de stocker des données coté navigateur.
Il existe de nombreux cas d’utilisation pour le stockage des données coté client plutôt que sur un serveur :

  • Éviter de surcharger un serveur avec trop de requêtes HTTP
  • Économiser de l’espace sur la base de données
  • Économiser le forfait data de vos utilisateurs « mobiles »
  • Pouvoir continuer à utiliser une application lorsqu’il n’y a pas d’accès à internet (trains, avions, zones blanches, …).

Il existe pour cela plusieurs API Javascript avec chacune leurs avantages et leurs inconvénients.

WebSQL (le passé)

Le but de cette spécification était de produire un ensemble d’API Javascript permettant d’interagir avec des bases de données coté client (dans les faits, une base SQLite) en utilisant une syntaxe proche du SQL.
On peut voir (http://www.caniuse.com/sql-storage) que cette technologie n’a été adoptée ni par Mozilla, ni par Microsoft, ce qui lui laisse peu de chance d’être utilisable à grande échelle.
De plus les travaux sur cette spécification (http://www.w3.org/TR/webdatabase/) ont été stoppés par le W3C, et son support pourrait être supprimé dans les futures versions des navigateurs qui l’implémentent.

WebStorage (localStorage/sessionStorage)

localStorage et sessionStorage sont 2 API qui permettent de manipuler et de stocker aisément des données sous la forme « clé ? valeur ».
Ces API présentent des fonctions similaires, la seule différence étant que les données stockées dans sessionStorage sont détruites dès la fermeture du navigateur. Elles résistent cependant à un rafraîchissement de page ou à une fermeture d’onglet (si le navigateur n’est pas fermé ensuite).

Les données ainsi stockées ne sont disponibles que sur le nom de domaine sur lequel elles ont été créés. Par exemple, des données stockées dans http://www.atolcd.com ne seront pas disponibles sur https://www.atolcd.com, mais bien sur http://www.atolcd.com/about.

Utilisation

Les fonctions permettant de manipuler les données sont très simples :

//Ajouter une entrée
localStorage.setItem("nom_champ", "ma valeur");
sessionStorage.setItem("nom_champ", "ma valeur");
 
//Récupérer sa valeur :
localStorage.getItem("nom_champ");
sessionStorage.getItem("nom_champ");
 
//La supprimer :
localStorage.removeItem("nom_champ");
sessionStorage.removeItem("nom_champ");

Points de vigilance

Stockage sous forme de chaîne de caractère

Il n’y a pas de typage des données stockées en local, elles sont automatiquement stockées sous la forme de chaînes de caractères.
Ainsi l’ajout d’un item de type entier:

localStorage.setItem("id",1);

Sera restitué sous forme de l’entier transformé en chaîne de caractères.

localStorage.getItem("id"); //Renvoi "1" et non 1

On peut outrepasser cette restriction en sérialisant en JSON les données en entrée puis en les dé-sérialisant lors de la récupération.

localStorage.setItem("id", JSON.stringify(1));
JSON.parse(localStorage.getItem("id")); //Nous retourne bien 1 (entier)

Ceci introduit alors de nouvelles possibilités, si l’on peut stocker une chaîne encodée, on peut très bien stocker des objets complexes. Par exemple :

var data = {
    id: 1,
    prenom : "John",
    nom : "Doe"
};
localStorage.setItem("user",JSON.stringify(data));
JSON.parse(localStorage.getItem("user"));//Retourne un objet similaire à data

Restriction sur le volume des données

il y a une limite de taille (~5Mo par nom de domaine) sur ce qui peut être stocké. Cela ne fait pas partie des spécifications, et n’était à la base qu’une recommandation que la plupart des constructeurs de navigateurs ont implémenté. Il faut aussi savoir que les navigateurs basés sur le moteur WebKit stockent les données en UTF-16, ce qui prend 2 fois plus de place qu’en UTF-8 et réduit donc la taille autorisée à 2.5 Mo.

Ces limitations peuvent être d’autant plus gênantes qu’il n’existe pour l’instant aucun moyen permettant de vérifier l’espace de stockage restant sur le localStorage. Le navigateur ne permettra simplement plus l’ajout de nouvelles valeurs, en lançant une exception.

On peut mettre en place un mécanisme vérifiant le nombre maximum de caractères que l’on peut stocker, et gérer nous même la place restante dans l’API.

Cela peut être fastidieux et il existe d’ailleurs une proposition d’API (Quota Management API) permettant de gérer plus efficacement les données à l’avenir

Mécanismes synchrones

La récupération des données est synchrone, et plus le volume de données stockées est important, plus la récupération d’une entrée prend du temps. Cela est d’autant plus à prendre en compte lors de développement d’application pour smartphone, qui ne dispose pas de la puissance des machines de bureau. (Voir localStorage read performance[EN])

Conclusion

LocalStorage & SessionStorage peuvent se révéler très utiles, à condition d’en connaître les limitations et de les utiliser avec parcimonie. Comme on peut le voir sur le lien suivant (Support WebStorage) le support est quasiment universel et permet son utilisation dans de nombreux projets.

IndexedDB (l’avenir)

Cette spécification ( IndexedDB) a été proposée afin de pallier la trop grande simplicité de WebStorage (localStorage et sessionStorage). Il s’agit là aussi d’un système de gestion et de manipulation de données sous forme de clés-valeurs , en créant des « store » d’objets (au sens Javascript), et pas seulement de chaînes de caractères, contrairement à WebStorage. IndexedDB est aussi asynchrone, ce qui évite de bloquer l’interface lors de recherche sur des volumes de données importants.

Support

La spécification est encore à l’état de brouillon. Chrome et Firefox l’ont implémenté, ainsi que Internet Explorer 10 (non disponible sous Windows XP) (Support IndexedDB). Sur mobile, IndexedDB n’est présent que sur Chrome et Firefox pour Android (prévu pour Blackberry 10), ce qui exclut tous les appareils sous iOS. Chrome n’est disponible que pour les terminaux sous Android 4 minimum, ce qui exclut là aussi un grand nombre de terminaux ( Fragmentation Android)

Application concrète : Mécanisme de persistance des données

L’application doit permettre de quitter un formulaire sans l’enregistrer sans que les données saisies soient perdues.

Mise en place du mécanisme

L’idée est assez simple. On parcours l’ensemble des éléments du formulaire, et pour chacun d’entre eux, on leur attache une fonction lorsqu’ils changent.

//On récupère l'ensemble des éléments de formulaire
var inputs = $('input,select,textarea');
 
//Et lorsqu'ils changent
inputs.on('change', function(evt) {
    //On lance la fonction de sauvegarde
    saveValueLocal(evt.target);
});

Utilisation de localStorage

Pour la fonction de sauvegarde, on doit appliquer un traitement particulier pour les checkbox qui ne disposent pas de l’attribut value.

function saveValueLocal(formField) {
    var inputType = formField.type;
    var value = formField.value;
 
    if (inputType == "checkbox") {
        value = formField.checked;
    }
 
    //On enregistre la nouvelle valeur
    localStorage.setItem(formField.name, value);
}

Chargement des données

//Sur chacun des input, au chargement
inputs.each(function(idx, input) {
    //On regarde si on dispose de sa valeur en local
    var localData = localStorage.getItem(input.name);
    //Et si c'est le cas
    if (localData !== null) {
        //On récupère le type de l'input
        var inputType = input.type;
        var outputStr;
        if (inputType == "checkbox") {
            //Dans le cas d'une checkbox, on doit mettre à jour l'attribut coché
            input.checked = JSON.parse(localData);
        } else if (inputType == "radio") {
            if (!input.loaded == true) {
                //Si c'est une radio, on va parcourir l'ensemble des radio liées
                $('input[name=' + input.name + ']').each(function(idx, radio) {
                    if (radio.value === localData) {
                        radio.checked = true;
                    }
                    radio.loaded = true;
                });
            }
        } else {
            input.value = localData;
        }
    }
});

Validation d’un formulaire : on peut vider les données dans le localStorage

inputs.each(function(idx, input) {
    localStorage.removeItem(field.name);
});

Résultat

Dans l’application ci-dessous, vous pouvez saisir des données du formulaire, puis appuyer sur le bouton reload (ou alors rafraîchir cette page, ou même, la fermer puis la rouvrir), vos données devraient encore être présentes.