Categories
Web

Créer un client Twitter offline pour l’iPhone avec HTML5

Cet article va expliquer comment créer une application Twitter simple, qui fonctionne qu’on soit connecté ou non. Pour celà, nous allons utiliser des technologies Web, en particulier des nouveautés de HTML5 qui permettent de stocker des données côté client et de mettre des fichiers en cache. Le navigateur Safari de l’iPhone se prête bien à l’exercice, puisqu’il supporte ces technologies.

Les plus pressés peuvent tester immédiatement la démo.

Le squelette de l’application

Tout d’abord, il faut créer le squelette HTML de notre application. Ce fichier HTML devrait faire l’affaire:

 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; UTF-8" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Language" content="en" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=1;"/> <style type="text/css">     body {         font-family: sans-serif;         margin: 0;         padding: 0;     }     #toolbar {         background: #EFEFEF;         padding: 5px;         border-bottom: 1px solid #a3a3a3;     }     #toolbar button {         border: 1px solid #a1a1a1;         -webkit-border-radius: 3px;         background: #F3F3F3;         padding: 0.5em;     }     ol {         margin: 0; padding: 0;     }     ol li{         list-style: none;         border-bottom: 1px solid #CCC;         margin: 0;         padding: 0.5em;         overflow: auto;     }     span.text {         display: block;         margin-left: 29px;     }     li img {         float: left;     }     .date {         font-size: 0.8em;         color: #AAA;     } </style> <title>iPhone Twitter</title> </head> <body> <div id="toolbar">     <button id="load">&#8635; Refresh</button> </div> <ol id="timeline"></ol> <script type="text/javascript" src="twitter.js"></script> <script type="text/javascript"> <!-- window.onload = function() {     Twitter.init();     document.getElementById('load').onclick = function() {         Twitter.load();     } } --> </script> </body> </html> 

Ce fichier se décompose de la manière suivante:

  • L’entête HEAD avec le doctype HTML5, une balise META spécifique à l’iPhone, un peu de CSS (et même un peu de CSS3);
  • Le corps BODY qui contient une toolbar et une liste ordonnée vide;
  • L’inclusion d’un fichier JS et un peu de code pour démarrer l’application et ajouter un le comportement à notre toolbar.

Passons maintenant au fichier twitter.js

Accès à la base de données

La première chose, est d’ouvrir la base de données. Si elle n’existe pas, elle sera créée automatiquement.

 var db = null; try {     if (window.openDatabase) {         db = openDatabase("Twitter", "1.0", "Twitter Feed", 200000);     } } catch(err) {} 

La fonction openDatabase prend 4 paramètres:

  • le nom de la base
  • la version de la base
  • le nom long (ou description)
  • la taille attendue de la base

La base sera accessible à travers la variable globale db.

L’objet Twitter

On va ensuite créer un objet Twitter qui va contenir le code pour le chargement et l’affichage des tweets.

 var Twitter = {     count: 20 } 

Pour le moment il ne contient qu’un attribut de configuration: le nombre de tweets à télécharger à chaque rafraîchissement. Mais nous allons tout de suite lui ajouter des méthodes dans le chapitre suivant.

Création de la table et affichage des tweets

Au chargement de l’application, nous allons tenter de créer la table (si elle n’existe pas) et lire les tweets qu’elle contient pour les afficher. Comme les données sont stockée en local, les tweets s’afficheront, même si la connexion Internet est interrompue.

En revanche, si la base de données n’est pas disponible, nous allons récupérer les tweets directement en ligne.

On va donc ajouter la méthode init qui suit:

Création de la table:

 //Create table init : function() {     console.log('init');     if (db) {         //Have database? Read data         db.transaction(function(tx) {             tx.executeSql(                 "CREATE TABLE IF NOT EXISTS status (id REAL UNIQUE, username TEXT, created_at TEXT, text TEXT, avatar TEXT)",                 [],                 function(result) {                     Twitter.readStatus();                 },                 function(tx, error) {                     Twitter.readStatus();                 }             );         });     } else {         //No database? Just load data and display         Twitter.load();     } }, 

Si la base de données est bien là et la table créée, nous pouvons faire une requête pour récupérer les tweets:

 //Read statuses readStatus : function() {     console.log('read');     db.transaction(function(tx) {         tx.executeSql("SELECT id, username, created_at, text, avatar FROM status ORDER BY id DESC", [], function(tx, result) {             var timeline = document.getElementById('timeline');             timeline.innerHTML = '';             for (var i = 0; i < result.rows.length; ++i) {                 var row = result.rows.item(i);                 Twitter.display(row);             }         }, function(tx, error) {             // Couldn't retrieve tweets             return;         });     }); }, 

Rien de très compliqué: la requête de type SELECT récupère les données et on passe chaque ligne résultat à une fonction qui va se charger de les afficher.

L’affichage d’un tweet est effectué par une fonction qui va simplement généré un LI pour l’ajouter à notre liste ordonnée:

 //Display statuses display : function(row) {     console.log('display');     var timeline = document.getElementById('timeline');     var li = document.createElement('LI');     var d = new Date();     d.setTime(Date.parse(row['created_at']));     li.innerHTML =         '<img src="'+row['avatar']+'" alt="" height="24" widht="24"/>'+         '<span class="text">'+         '<strong>'+row['username'] + '</strong> ' +         row['text'] +         ' <span class="date">' + prettyDate(d) + '</span>'+         '</span>';     timeline.appendChild(li); }, 

Notez que cette fonction utilise une autre fonction prettyDate qui formatte la date pour la rendre lisible. Je ne documenterai pas cette fonction mais vous la trouverez dans la démo.

Remplissage de la base de données

Pour le moment, notre table ne contient aucun tweet, donc rien ne s’affiche. Nous allons charger les données twitter, les stocker et les afficher.

Pour faire simple, nous allons utiliser l’API JSONP de Twitter pour charger les 20 derniers tweets. La méthode load fait plusieurs choses:

  • nettoyer les <script> précédents qui auraient pu être ajouté par un précédent chargement
  • donnée un feedback visuel pour indiquer que le chargement a démarré
  • charger les tweets en insérer le fichier JSONP en tant que <script> dans le <head>
  • indiquer que le chargement est terminé lorsque les données sont là

Comme la timeline est protégée par un login et un password, il faudra les entrer lorsque le navigateur le demande.

 load : function() {     console.log('load');     //Remove JSONP scripts     var head = document.getElementsByTagName('head')[0];     var scripts = null;     while ((scripts = head.getElementsByTagName('script')).length > 0 ) {         head.removeChild(scripts[0]);     }     document.getElementById('load').innerHTML = 'Refreshing...';     var now = new Date();     var url = 'http://twitter.com/statuses/friends_timeline.json?callback=Twitter.twitterCallback&count={count}&random={random}';     var script = document.createElement('script');     script.setAttribute(         'src',         url.replace('{count}',Twitter.count)            .replace('{random}',now.getTime())     );     script.onload = function() {         document.getElementById('load').innerHTML = '&#8635; Refresh';     }     console.log(script);     head.appendChild(script); }, 

Une fois le chargement terminé, un callback est automatiquement appelé. Si la base de données existe, il va simplement extraire les données qui nous intéressent et les stocker dans la base de données. Sinon, il va afficher chaque tweet.

 twitterCallback : function(obj) {     console.log('callback');     if (db) {         var inserts = [];         for (var i=0, l=obj.length; i<l; i++) {             var status = [                 obj[i].id,                 obj[i].user.screen_name,                 obj[i].text,                 obj[i].created_at,                 obj[i].user.profile_image_url             ]             inserts.push(status);         }         Twitter.insert(inserts, Twitter.readStatus);     } else {         //No database? just display         for (var i=0, l=obj.length; i<l; i++) {             Twitter.display({                 'id' : obj[i].id,                 'username' : obj[i].user.screen_name,                 'text' : obj[i].text,                 'created_at' : obj[i].created_at,                 'avatar' : obj[i].user.profile_image_url             });         }     } }, 

Il faut savoir que l‘INSERT est asynchrone et risque de lancer l’affichage avant que nos données soient entièrement stockées. Nous utilisons donc notre propre fonction Twitter.insert qui va faire les insertions et n’afficher qu’à la fin.

Voilà notre méthode d’insertion qui simule une insertion synchrone. Elle prend deux paramètres: le tableau de données et un callback à executer en find d’insertion.

 //Synchronous insert insert : function(arStatus, callback) {     var status = arStatus.pop();     var sql = "INSERT INTO status (id, username, text, created_at, avatar) VALUES (?,?,?,?,?)";     db.transaction(         function (tx) {             tx.executeSql(                 sql,                 status,                 function(tx, result){                     if (arStatus.length > 0) {                         Twitter.insert(arStatus, callback);                     } else {                         callback();                     }                 },                 function(tx, error){                     if (arStatus.length > 0) {                         Twitter.insert(arStatus, callback);                     } else {                         callback();                     }                 }             );         }     ); } 

Voilà, nous avons tout le code nécessaire pour récupérer des tweets, les stockers localement et les afficher sans repasser par le serveur.

Cache d’applications offline

Jusque là, nous avons créé une application web qui conserve des données en local. Une connexion reste néanmoins nécessaire pour afficher la page HTML et charger le javascript. Heureusement, HTML5 offre un mécanisme qui permet de garder ces deux fichiers en cache et d’y accéder même sans connexion Internet. Cette fonctionnalité est offerte par le cache-manifest, un fichier qui indique au navigateur la liste des fichiers à garder en cache. Ces fichiers ne seront plus jamais rafraichis, à moins que le cache-manifest ne change.

Ce fichier doit être envoyé avec le bon type MIME Content-type: text/cache-manifest (avec PHP par exemple) pour fonctionner. Notez qu’on inclue un commentaire avec la date pour versionner ce fichier. Il suffira de changer la date pour forcer un rechargement complet de l’application.

 CACHE MANIFEST #20090201-1731 index.html twitter.js 

Un fois ce fichier créé, il suffit de l’inclure dans le fichier HTML en modifiant la balise HTML:

 <html manifest="cache-manifest.php"> 

Conclusion

Si vous avez bien suivi toutes les étapes (et si j’ai bien expliqué), vous devriez avoir une application qui fonctionne totallement offline. Lors de la première connexion, la page HTML et le JS se chargent et sont mis en cache. Ils pourront être accédés n’importe quand par la suite, même sans connexion. L’application permet ensuite d’afficher des tweets stockés localement, sans téléchargement quelque donnée.

Pour télécharger les fichiers complet et tester l’application, tout est disponible à cette adresse: http://svay.com/experiences/iphone-twitter/

Cet exemple est plutôt basique et vous aurez certainement des idées d’applications plus élaborées. D’ailleurs, il semblerait que Google lance dans peu de temps une version offline de GMail pour iPhone en utilisant les même technologies.

Enfin, voilà les liens qui ont servi à préparer ce tuto:

20 replies on “Créer un client Twitter offline pour l’iPhone avec HTML5”

Bon quelques remarques plus pertinentes maintenant que je suis revenu de ma (bonne !) surprise :

* pourquoi ne pas utiliser les balises header et section (quitte à faire du html5) ?
* le prochain billet ajoutera la gestion d’OAuth ? 🙂

Sinon super tuto, je m’étais jamais trop intéressé au stockage côté client mais ça a l’air super simple, cool.

w00t, mauriz iz back!

Une petite erreur de rien du tout dans la section : “Remove JSONP scripts” voir http://gist.github.com/67230
et fais de l’HTML5 ou du XHTML 1.0, mais évite de mixer les deux.

Sinon, il ne me manque plus qu’un iPhone pour tester ça 🙂

Yay, ça fait plaisir, ça. Pis ça commence avec du lourd, c’est cool.

Pourvu que ça dure le plus longtemps possible 🙂

Comme les autres : non seulement c’est une excellente publication mais en plus ça signe ton retour 🙂 Comme quoi, j’ai bien fait de ne pas supprimer ton blog de mes RSS.

Qui sait, peut être que Cybercodeur reviendra … lui aussi !

“Blogger un jour, Blogger toujours … ”
🙂

David, Yoan: Pour le mélange de HTML5 et de XHTML, c’est surtout parce que j’ai pas assé plongé dans le HTML5 pour le moment pour pouvoir en faire réellement.

Ensuite sur l’erreur du JSONP, je dois pas être encore assez réveillé (je sors du lit là), mais je vois pas vraiment.

Yoan: en effet, j’étais pas bien réveillé. C’est corrigé!

Je garde ça sous le coude, je sors d’un pot pour fêter deux CDI, j’ai pas forcément tous les neurones en place pour lire du javascript 🙂

Au plaisir, et surement au prochain Paris Web !

(PS: I love Dotclear)

Merci beaucoup ! Excellent tutoriel !

Je decouvre ton site et ne suis pas surpris de lire de tels commnentaires ! :o)

Au plaisir de te relire !

Buenas !

I think additional site owners ought to consider this website being an product * very neat and fantastic styling, let alone this content. You’re a specialist of this type!

It is in reality a great and helpful piece of information.
I am happy that you simply shared this helpful information with us.
Please stay us informed like this. Thanks for sharing.

Comments are closed.