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">↻ 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 = '↻ 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: