Style : Classique Ngage Design Messenger Xbox Xtreme Matrix WinHelp Pas de Style
http://hotline.c.la :  Begoodtux Megasearch :Camping-carhamsearchminisearch
Annuaire Web

Application hors-ligne HTML5 le JavaScript #2


Dans l’article précédent, nous avons rendu une interface HTML disponible hors-ligne via l’utilisation du fichier manifest de cache. L’interface était totalement statique, ne supportait que la consultation en lecture seule et la gestion du contenu dynamique reposait sur les différents versions du manifest ; les nouvelles API JavaScript vont nous aider à résoudre ces problèmes.
Avec le localStorage et l’état window.onLine, nous pourrons stocker le contenu dynamique et différencier les traitements JavaScript selon le mode (dé)connecté.

Sommaire


Cet article s’appuie sur la librairie PrototypeJS pour la majorité des exemples de code JavaScript.

Surveiller l’état de la connexion

La première étape est d’afficher un bloc indiquant à l’utilisateur l’état de la connexion. Le bloc affiche online ou offline en fonction du mode actif (connecté / déconnecté).

Il faut tout d’abord créer le bloc HTML dont nous avons besoin :

1
2
3
<div id="line-status" class="online">
Online
</div>

Un bloc div contient le libellé associé au statut. Nous avons positionné un id sur l’élément pour le manipuler facilement en JavaScript et la classe CSS nous servira à changer la couleur de fond : rouge pour connecté et vert pour déconnecté. Nous pouvons maintenant modifier le texte et la classe du bloc en fonction de l’objet navigator.onLine.

1
2
3
4
5
setInterval(function () {
var status = document.getElementById('line-status');
    status.className = navigator.onLine ? 'online' : 'offline';
    status.innerHTML = navigator.onLine ? 'Online' : 'Offline';
}, 250);

Le texte et la classe du bloc #line-status seront mis à jour toutes les 250ms en fonction de la valeur de l’objet navigator.onLine. Vous pouvez tester ce code en coupant la connexion wifi/3G de votre ordinateur/téléphone (avec firefox, cliquer sur Travailler Hors Connexion). Une exécution toutes les 250ms a tendance à être consommatrice en ressources ; utilisons plutôt les événements associés à cet état fournis par HTML5.

1
2
3
4
5
6
7
8
9
10
window.addEventListener('online', function(){
  var status = document.getElementById('line-status');
  status.className = 'online';
  status.innerHTML = 'Online';
}, false);
window.addEventListener('offline', function(){
  var status = document.getElementById('line-status');
  status.className = 'offline';
  status.innerHTML = 'Offline';
}, false);

Notez que l’événement est levé sur l’objet window et non sur l’objet navigator. Avec ce code, la mise à jour du bloc est réalisée à chaque fois que le navigateur change d’état de connexion. Dans cette version, nous reposons entièrement sur le fonctionnement du navigateur pour différencier le mode connecté du mode déconnecté. Si cela fonctionne sur Webphone, sur ordinateur portable la coupure de la connexion réseau ne change pas l’état du navigateur. Seul le passage en mode hors connexion permet de lancer l’événement offline. Le navigateur restera online si les requêtes partent en timeout ou finissent en ‘host not found’ par exemple.
Pour affiner la gestion du mode hors ligne en JavaScript, nous allons maintenir l’état de la connexion dans un objet qui réagira aux événements du navigateur et fournira la possibilité de changer directement l’état de la connexion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Utils.LinkClass = Class.create({
    // Constructeur
    initialize: function (elem, config){
        // Par défaut nous utilisons la valeur du navigateur
        this.online = navigator.onLine;
        // Récupérer la div par son id
        this.elem = $(elem);
        // Ecouter les évènements online/offline sur l'objet
        this.evtOnLine = this.updateTagOnline.bindAsEventListener(this);
        this.evtOffLine = this.updateTagOffline.bindAsEventListener(this);
        Event.observe(window, 'online', this.evtOnLine);
        Event.observe(window, 'offline', this.evtOffLine);
        // Rendre les classes CSS et les libellés paramétrables
        this.config = Object.extend({
offlineText:'Offline',
onlineText:'Online',
 onlineClass:'online',
offlineClass:'offline' }, config || { });
        this.updateTag(this.online);
    },
    // change l'état et met à jour la div si l'état fourni est différent de celui de l'objet
    setLineState: function (online){
        if(online != this.online){
            this.updateTag(online);
        }
    },
    // Met à jour l'état et la div sur évènement online
    updateTagOnline: function (evt){
        this.updateTag(true);
    },
    // Met à jour l'état et la div sur évènement offline
    updateTagOffline: function (evt){
        this.updateTag(false);
    },
    // Met à jour l'état et la div avec le booléen fourni
    updateTag: function (online) {
        this.online = online;
        if (online) {
            this.elem.addClassName(this.config.onlineClass );
            this.elem.update(this.config.onlineText);
            this.elem.removeClasseName(this.config.offlineClass);
        } else {
            this.elem.addClassName(this.config.offlineClass);
            this.elem.update(this.config.offlineText);
            this.elem.removeClasseName(this.config.onlineClass);
        }
    }
});
// Instancier Utils.Link lorsque la page est chargée
Event.observe (window, 'load', function (){
    Utils.link = new Utils.LinkClass('line-status');
});

Dans le cas où l’état de la connexion est mis à jour directement sans émettre un événement du navigateur, vous pouvez lancer un service de ping http qui ira vérifier à intervalles réguliers la disponibilité du serveur et mettra à jour l’état de la connexion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Utils.PingServiceClass  = Class.create({
    initialize: function (config){
        this.timer = null;
                  // Configuration par défaut de la fréquence de ping en ms
        this.config = Object.extend({
            pingInterval: 20000,
            startDelay: 1000,
            url: null// Url ciblée par le ping Ajax
            onUplink: function(){}}, // fonction invoquée quand le ping réussi
                      config || {});
        this.url = this.config.url;
    },
        // méthode de ping
    ping: function(){
        if (this.timer != null){
            new Ajax.Request(this.url, {
                  method: 'get',
                  onSuccess: function(transport) {
                    if (transport.status < 300 && transport.status >= 200){
                        this.timer = null;
                                                  // appel du callback si le ping AJAX a réussi
                        this.config.onUplink();
                    } else if (this.timer != null){
                                                  // Planification du prochain ping en cas d'erreur
                        this.timer = setTimeout(this.ping.bind(this), this.config.pingInterval);
                    }
                  },
                                     // Planification du prochain ping en cas d'erreur
                  onFailure: function(){
                    if(this.timer != null) {
                        this.timer = setTimeout(this.ping.bind(this), this.config.pingInterval);
                    }
                  }
                });
        }
    },
          // Lancement du service de ping
    start: function(){
        if (this.timer == null){
            this.timer = setTimeout(this.ping.bind(this), this.config.startDelay);
        }
    },
      // Arret forcé du ping
    stop: function(){
        if (this.timer != null){
            clearTimeout(this.timer);
            this.timer = null;
        }
    }
});

La classe PingServiceClass utilise la fonction setTimeout pour assurer une exécution régulière et asynchrone. À chaque timeout, elle lance une requête Ajax sur l’URL fournie en configuration. Le ping se lance par un appel à start() et peut être arrêté par un appel à stop(). Il faut aussi modifier la classe Utils.LinkClass pour qu’elle utilise un PingService lorsqu’un script forcera le passage hors-ligne(Utils.link.setLineState(false);).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//...
        initialize: function(config){
          //...
            if (this.config.ping){ // Instanciation du PingService si la configuration l'autorise
          this.ping = new Utils.PingServiceClass({
            url: this.config.url,
            onUplink: this.updateTagOnline.bind(this) // Passer Online quand le ping réussi
            });
        }
           },
//...
    setLineState: function (online){
        if(online != this.online){
            this.updateTag(online);
            if (!online && this.config.ping){ // Lancement du Ping HTTP sur passage forcé en mode hors-ligne
                this.ping.start();
            }
        }
    },
    updateTagOnline: function (evt){
        this.updateTag(true);
          // Arret du Ping HTTP si le navigateur passe en ligne
                if(this.config.ping){
          this.ping.stop();
                }
    },
//...
Event.observe (window, 'load', function (){ // Ajoute l'url de ping en configuration
    Utils.link = new Utils.LinkClass('line-status',{url:'/ping.html'});
});

Nous avons maintenant un moyen de notifier l’utilisateur et de savoir à tout moment si l’application est en ligne ou non. Le service ping sera utilisé seulement sur les navigateurs ne supportant pas ou mal les événements ‘online’ et ‘offline’. Notez que le ping est exécuté sur l’URL ‘/ping.html’ qui fournit une simple page html statique hors du manifest.

Gérer le contenu dynamique

La première étape, en partant d’une application existante, consiste à ajaxifier son contenu dynamique. Pour reprendre le cas du blog, la page d’accueil liste les en-têtes des articles les plus récents. La première solution consiste à récupérer le bloc HTML listant les articles en Ajax et à l’injecter dans la page. Attention pour les moteurs de recherches et pour des questions d’accessibilité, la page doit toujours être servie avec l’intégralité de son contenu.
Dans le cas d’une page liée au fichier manifest, le problème est de savoir quand mettre à jour la liste. La page étant affichée à partir du cache, son contenu ne changera qu’avec le fichier manifest. Le plus simple est de récupérer le bloc HTML systématiquement au chargement de la page, puis de planifier des mises à jour à intervalles réguliers pour garantir la fraîcheur de l’information.

1
new Ajax.PeriodicalUpdater('blocElementId', '/articles', {frequency: 30, decay: 2});

L’exemple ci-dessus crée un PeriodicalUpdater qui rechargera les articles toutes les 30s. Si le contenu ne change pas, la prochaine exécution sera planifiée avec un timeout de 30×2 secondes en doublant le decay après chaque passage.

Afin d’améliorer l’expérience utilisateur, pensez à cacher le contenu par css au chargement initial ; le JavaScript l’affichera évitant à l’utilisateur la vue de contenu obsolète à chaque affichage de la page. Pensez aussi à mettre une animation d’attente ou une barre de chargement si vous êtes courageux. Pour la touche finale, vous pourrez ajouter un effet pour faire apparaître la liste des articles.

Attention aux liens et à la pagination

Dans le cadre d’un blog, la page d’accueil liste les articles les plus récents avec pour chaque article, un lien vers l’article complet et son pitch. Sans oublier les boutons de navigation pour consulter des articles plus anciens. Tous ces liens doivent aussi être traités en JavaScript.
Le script devra prendre soin d’intercepter les événements ‘click’ sur les liens d’articles et de pagination pour permettre l’exécution en Ajax de la requête et la mise à jour de l’interface.
A l’aide d’un sélecteur CSS, nous allons retrouver les liens et observer les événements click en exécutant la fonction d’affichage de l’article ou de la liste.

1
2
3
4
5
6
7
8
9
10
// Utilise l'URL du lien d'article pour afficher ce dernier
Blog.displayArticle = function (evt){
    var url = evt.element().href;
    // ... Update Ajax de #uiBody
};
// Intercepter le click sur un article et afficher ce dernier
$$('#uiBody a.article').each(
function (item){
        item.observe('click', Blog.displayArticle);
});

Passez au JSON

Pour offrir plus de flexibilité au script, je vous recommande de passer d’un échange Ajax en HTML brut à une donnée formatée en JSON. Nous pourrons communiquer des informations au script, comme le jeton de version, sans impacter le HTML. Le formatage des articles en JSON pourra contenir la vue HTML de pré-visualisation pour la page d’accueil et le HTML complet de l’article. L’utilisation du code HTML dans l’objet JSON évite la création chronophage des éléments HTML par le script.
Dans le cas de la liste d’articles, vous allez devoir gérer la pagination de la liste à la main. Dans l’objet JSON remonté par le serveur, pensez à intégrer les informations nécessaires à la pagination (nombre d’éléments total de la liste, numéro de la page, nombre d’éléments par page). La génération des boutons de navigation peut par exemple reposer sur le plugin JQuery pagination (http://plugins.jquery.com/project/pagination).
Le format JSON nous servira aussi au stockage des données côté navigateur. Le localStorage que nous allons utiliser ne supporte que des valeurs de type chaîne de caractères. Les objets y seront donc persistés en chaînes JSON, de manière à maintenir la structure objet sans surcoût de mapping.
Attention à l’injection de code dans vos objets JSON, pensez à toujours passer par une librairie JavaScript comme PrototypeJS ou JQuery pour évaluer l’arbre JSON. Elles se chargeront pour vous de faire les contrôles de syntaxe nécessaires ainsi que la conversion automatique du contenu JSON des réponses AJAX.

Le mode hors ligne

Que se passe-t-il lorsque le navigateur est hors ligne ? En l’état actuel des choses, la page reste vide ou affiche un contenu obsolète issu du premier chargement. Dans certain cas, le bloc de notification restera même désespérément ‘online’.

Gestion d’erreur Ajax

Le script doit traiter les erreurs AJAX, et mettre à jour le bloc de notification en cas d’indisponibilité du serveur. Pensez à positionner un timeout sur vos requêtes pour limiter le temps de chargement et détecter les erreurs réseau. Attention aussi aux redirections HTTP, les requêtes Ajax les suivent si elles sont sur le même domaine. La requête pourra être réussie et contenir la page de login vers laquelle vous aurez été redirigé si vous n’avez plus de session.
En cas de problème réseau, mettez à jour le bloc de notification Utils.link.setLineState(false); et exécutez le traitement hors-ligne reposant sur les données stockées dans le navigateur. Si vous n’utilisez pas le ping HTTP, pensez aussi à mettre à jour le bloc de notification lorsqu’une requête Ajax réussit pour éviter la notification hors-ligne éternelle.

Stratégie de stockage

Bien sûr HTML5 fournit différents supports de persistance dans le navigateur allant de la base de données SQL au stockage dans des tables clé-valeur en passant par les tables clé-valeur indexées. Aujourd’hui seul le stockage de type clé-valeur (localStorage, sessionStorage) est supporté par tous les navigateurs. Les autres API sont intéressantes et pourront faire l’objet d’études futures mais pour cet article, je me suis contenté d’utiliser le localStorage directement disponible sur tous les navigateurs.

Attention, le localStorage ne convient pas à la persistance d’une grande quantité de tuples. Ne comptez pas non plus fournir des fonctionnalités de recherche hors-ligne. L’API du localStorage permet simplement de lister les clés, de récupérer la chaîne associée à une clé et d’associer une chaîne à une clé. Nous pourrons aussi supprimer une clé et sa valeur de la table ou vider l’intégralité de la table. C’est le script qui va devoir combler les manques de l’API en maintenant ses propres index.

Choisissons maintenant le support que nous allons fournir:

Vous l’aurez compris, c’est la synchronisation automatique que nous allons retenir pour assurer l’accès au blog hors-ligne et en lecture seule.

Nous voulons lister des articles pour pouvoir afficher une introduction dans la liste des articles et le contenu intégral dans la page d’article. Nous allons donc créer une vue logique servie par un DAO article qui se chargera de persister les articles, et de les lister. Le DAO pourra par exemple, maintenir la liste des identifiants d’article dans un tableau stocké avec la clé article.index. Cette liste sera triée par ordre de date de publication décroissante pour éviter d’avoir à réaliser des tris couteux en JavaScript.
Chaque article sera stocké avec une clé de type article.id en remplaçant id par l’identifiant de l’article. Pour limiter le temps de traitement l’introduction et le contenu serront stockés séparément. Cela évite le chargement des articles complets pour toute les pages affichant une liste d’articles (pagination, tri, recherche, …). La clé article.index contiendra en plus des identifiants les introductions de chaque article.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var Article = {};
Article.DAOLocalStorageClass = Class.create( {
    initialize: function(){
        var articles = window.localStorage.getItem("article.index");
        if (articles != null && articles.isJSON()){
            this.articles= articles.evalJSON();
        }else {
            this.articles = [];
        }
    },
    getArticleList: function(){
        return this.articles;
    },
    getArticle: function(id){
        var article = window.localStorage.getItem("article."+id);
        if (article != null && article.isJSON()){
            return article.evalJSON();
        }
        return null;
    },
    setArticleList: function(list){
        if (list != null){
            window.localStorage.setItem("article.index", list.toJSON());
            this.articles = list;
        }
        else {
            this.articles = [];
        }
    },
    setArticle: function (article){
        if (article != null){
            window.localStorage.setItem("article."+article.id, article.toJSON());
        }
    },
    removeArticle: function(id){
        window.localStorage.removeItem("article."+id);
    },
    clear: function(){
        this.articles.each(function (item){
            window.localStorage.removeItem('article.'+item.id);
        });
        window.localStorage.removeItem('article.index');
        this.articles =[];
    }
});
Article.DAO = new Article.DAOLocalStorageClass();

Avec ce DAO nous sommes capables de gérer une petite liste d’articles persistés dans le navigateur. Si le navigateur implémente le localStorage et autorise le site à l’utiliser, notre DAO fonctionnera sans problème. Mais le DAO ne supporte pas de dégradation en cas d’indisponibilité du localStorage. La solution retenue est d’utiliser un bouchon à la place du localStorage dans ce cas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
initialize: function(){
    this.enabled = false;
        this.storage = this.getStorage();
        // ...
},
    getStorage: function(){
        if(window.localStorage){
            this.enabled = true;
            return window.localStorage;
        }else {
            return {
                    setItem: function (){},
                    getItem: function (){ return null;},
                    removeItem: function () {}
            };
        }
    },

La méthode getStorage() fournit un bouchon et positionne la propriété this.enabled à true si le navigateur supporte bien le localStorage. Le reste du DAO doit être modifié pour utiliser this.localStorage au lieu de window.localStorage.

Passons maintenant à la récupération des données à persister. Nous allons développer un service de synchronisation des articles les plus récents pour renseigner notre base d’articles locale.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        new Ajax.Request('sync', {
              method: 'get',
              onSuccess: function(transport) {
                if (transport.responseJSON != null){
Article.DAO.clear();                Article.DAO.setArticleList(transport.responseJSON.articles);
                    transport.responseJSON.articles.each(function (pitch){
                        new Ajax.Request('/article/'+pitch.id, {
                            onSuccess: function(transport) {
                                if (transport.responseJSON != null){
                                    Article.DAO.setArticle(transport.responseJSON);
                                }
                            }
                        });
                    });
                }
              }
              }
            });

Le code ci-dessus invoque un web service JSON qui lui retourne la liste des articles les plus récents avec les pitchs à la manière du service listant les articles. La base locale est d’abord vidée intégralement, puis la liste est persistée. Pour chaque article listé, une requête Ajax est lancée pour en récupérer le contenu et le stocker. Bien que la méthode soit assez consommatrice en ressources, elle a le mérite de fonctionner. Une bonne amélioration à réaliser serait de ne récupérer que les nouveaux articles tout en évitant de vider la base à chaque synchronisation.
Une fois la liste récupérée, il faut supprimer de la base les articles qui ne sont plus listés et télécharger tous les nouveaux ainsi que ceux nécessitants une mise à jour (date ou version différente).
N’oublions pas la règle d’or de la notification utilisateur. Nous devons montrer à l’utilisateur l’état de la synchronisation. Il suffit de stocker dans un contexte un booléen à true si la synchronisation est en cours et un compteur des requêtes Ajax lancées et non terminées. Lorsque la derniere requête se terminera elle décrémentera le compteur à zéro, passera le booléen à false et pourra alors notifier l’utilisateur. Pour le contrôle d’erreur on ajoutera un champs error initialement vide dans le contexte qui sera renseigné en cas d’erreur Ajax.

1
var syncCtx = { running: false, requestCount: 0, error: null};

Il faut lancer la synchronisation au chargement de la page et à chaque fois que le navigateur passe en ligne. En principe pour un blog cela devrait suffire car les utilisateurs ne restent pas connectés très longtemps et le contenu change peu. Dans un site où le contenu évolue rapidement, pensez à exécuter la synchronisation à intervalle régulier. Attention, il est inutile de lancer ce nouveau service si !Article.DAO.enabled, puisqu’aucun article ne sera persisté dans ce cas.

Les fonctions d’affichage précédemment créées doivent-être modifiées pour utiliser le DAO lorsque le navigateur est hors-ligne. Comme vous avez pu le constater de nombreux cas d’erreur sont envisageables : veillez à afficher un contenu alternatif en cas d’indisponibilité des données.

Gestion des formulaires

Les requetes POST sont toujours envoyées sur le serveur sans tenir compte du cache manifest. Lors d’un POST redirect le navigateur pourra afficher la page depuis le cache. Si la réponse du POST contient la page elle sera affichée telle quelle par le navigateur. Seules les requêtes de type GET sont élligibles au cache. Quand le navigateur est hors-ligne, les formulaires utilisant la méthode POST vont afficher le message d’erreur HTTP habituel. Si toutefois vous utilisez la méthode GET, le manifest pourra être utilisé pour fournir une version hors-ligne de la page. Pour permettre l’utilisation d’un formulaire hors-ligne nous allons le soumettre via une requête AJAX en captant l’évènement ‘submit’ du formulaire. Le handler sérialisera les données du formulaire dans un objet JSON ou directement dans la requête AJAX à soumettre sous forme de POST HTTP au serveur.

1
2
3
4
5
6
7
8
9
10
11
12
    // Ecouter le submit du formulaire
$('form').observe('submit', function (evt){
// Soumettre le formulaire en AJAX
            $('form').request({
                method: 'post',
onComplete: function () {
    // Affiche alerte 'Post Submitted'
    alert('Post submitted !');
}});
// Stopper l'évènement submit pour que le navigateur ne le traite pas
evt.stop();
});

Ajoutons un peu de traitement pour les cas d’erreur (indisponibilité du serveur, authentification requise) et le mode hors-ligne. Dans les deux cas, le principe sera d’enregistrer la modification dans le localStorage sous la forme d’un évènement qui pourra être synchronisé lorsque la connexion le permettra.

1
2
3
4
5
6
7
8
9
10
11
addEvent: function (evt){
    this.events.push(evt);
    this.storage.setItem('events', this.events.toJSON());
},
getEvents: function (){
    return this.events;
},
clearEvents: function(){
    this.events = [];
    this.storage.removeItem('events');
}

Sur POST d’un nouveau commentaire, l’évènement est enregistré dans la base locale.

1
Article.DAO.addEvent({ type: 'comment', comment: $('form').serialize(true)});

Pour indiquer à l’utilisateur que le commentaire est bien pris en compte, pensez à vider les champs du formulaire et à afficher un message d’information par exemple. Ajoutez aussi un élément à l’interface pour indiquer à l’utilisateur qu’il y a des données à synchroniser avec le serveur. Cela pourra être un bouton ou un lien qui permettra de soumettre au serveur les modifications réalisées hors-ligne ou hors session. Dans le cas des commentaires, il est possible de prévoir une synchronisation automatique quand le navigateur passe en ligne et au chargement de la page. En revanche cette pratique est fortement déconseillée pour des évènements concernant des articles modifiés.
Vous l’aurez compris tous les évènements qui nécessitent une authentification de l’utilisateur doivent être synchronisés manuellement à la demande de ce dernier. Prévoyez une interface d’administration permettant de lister, éditer et supprimer les évènements à synchroniser.

Conclusion

Avec l’utilisation conjointe du localStorage et du MANIFEST, nous pouvons fournir une application qui fonctionnera hors ligne. Les étapes que nous avons suivies prises séparément sont assez simples. Mais les impacts sont nombreux et plusieurs refactoring seront nécessaires sur le serveur. Le passage au mode hors ligne ne se fera pas sans mal. L’API localStorage n’est pas destinée à stocker et manipuler un grand nombre d’objets. Elle ne sera d’aucun secours pour la recherche, et le tri des listes par exemple. En bref les limitations du localStorage compliquent fortement les développements. Pour créer une application hors-ligne capable de gérer un grand nombre de données et affichant des fonctionnalités égales à des applications de bureau, vous devrez passer par l’utilisation d’un autre mode de stockage comme l’API WebSqlDatabase pour travailler en SQL ou bien l’API WebIndexedDatabase qui répondent à ces besoins. Notez tout de même que WebSQLDatabase n’est supportée ni par IE, ni par Firefox. L’API IndexedDB n’est de son côté supportée que par Firefox et Chrome. Pour un support tout navigateur vous devrez passer par l’utilisation des API fournies par Google Gears. L’inconvénient étant que Gears n’implémente pas les API du standard en cours d’écriture HTML5. À terme, l’extension risque même de disparaître au profit des implémentations natives des navigateurs HTML5.