Il y a 12 ans -
Temps de lecture 21 minutes
Application hors-ligne HTML5 le JavaScript
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 :
Online
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
.
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.
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.
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.
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);
).
//... 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.
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.
// 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:
- Proxy cache – Le script stocke le résultat des requêtes AJAX dans le localStorage et utilise les objets JSON stockés si le serveur est indisponible. Ce type de stockage est adapté aux contenus liés (tag cloud, articles les plus lus, …) évoluant peu, mais il trouvera vite ses limites dans la gestion du contenu principal pour la pagination notamment. La taille du localStorage va grandir à chaque nouvel article et les articles n’ayant pas été visités ne seront pas disponibles hors-ligne.
- Synchronisation automatique – Stockage d’un nombre restreint d’articles en fonction de la date de publication par exemple. Le navigateur doit synchroniser les n articles les plus récents lorsqu’il est en ligne. En mode hors-ligne, le navigateur ne listera que les articles disponibles dans le localStorage. Le script sera un peu plus compliqué à réaliser mais la taille de la base locale sera fixe et la pagination inutile. La synchronisation automatique est sans doute le meilleur choix dans le cas du blog en lecture seule.
- Synchronisation manuelle – Stockage manuel des articles à positionner dans le cache. L’utilisateur choisi lui même d’ajouter les articles dans le localStorage à l’aide d’un bouton radio par exemple. Pour chaque article listé, le script indique si il est disponible hors ligne. Lorsque le serveur est indisponible, le navigateur n’affichera que les articles persistés dans le localStorage. Là encore, le script sera plus compliqué car cela ajoute plusieurs traitements d’évenements. Il faudra limiter le nombre d’articles persistants pour éviter les scripts trop longs et trop consommateurs en mémoire. La synchronisation manuelle est la plus efficace et la mieux sécurisée pour autoriser l’édition des articles hors-ligne.
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.
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.
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.
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.
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.
// 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.
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.
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.
Commentaire
5 réponses pour " Application hors-ligne HTML5 le JavaScript "
Published by Benoît Dissert , Il y a 12 ans
Super article ! Merci beaucoup
Published by Marc Deschamps , Il y a 12 ans
Article très complet et bien écrit.
Cet article m’a aidé à mettre en place une gestion de personnalisation d’un site web en cours de réalisation :-)
Merci
Published by Paul Romano , Il y a 12 ans
Bravo pour cet article des plus formateurs. Cela nous confirme que le chemin vers LE véritable mode déconnecté ne sera pas sans effort.
Published by b.omar , Il y a 11 ans
bjr,
excellent article bravo! j’aurai souhaité en plus avoir par rapport à chaque rubrique un fichier fonctionnel.
si vous avez la possibilité de le faire ca serai génial !
merci.
Published by Xavier NOPRE , Il y a 11 ans
Bonjour Séven,
Grand merci pour cet article assez complet et bien didactique.
La conclusion me confirme ce que je pensais : la solution n’est pas si « simplement » HTML5 …
Une question : dans ton exemple, je ne comprends pas trop commence fonctionne le ping ajax, et notamment comment il traite l’échec du ping ? Si le ping réussit, il y a appel à « onUplink », OK. Par contre, si le ping échoue (ou état réponse >= 300, pour simplifier), à part relancer le setTimeout, je ne vois rien …
Merci
Xavier