Maurice Svay

Web

Le web, en général

Fil des billets - Fil des commentaires

jeudi, juin 24 2010

Soirée Performance Web le 21 juillet 2010 chez Octo

Éric Daspet organise une soirée sur le sujet des Performances Web le 21 juillet 2010. Sponsorisée par le cabinet de conseil Octo, la soirée aura lieu dans ses locaux à partir de 19h. Stoyan Stefanov, qui fait partie de l'équipe Exceptional Performance de Yahoo! fera une présentation de plusieurs techniques avancées pour améliorer la performance des vos sites Web. La présentation sera suivie d'une séance de questions/réponses plus libre. L'inscription est gratuite, faites passer le mot!

Si vous êtes intéressé par cet événement, tous les détails sont disponibles sur cette page : Webperf User Group France, 21 juillet 2010 Paris

mardi, juin 8 2010

PageSaver extension for Safari5

pagesaver.png

Version française à la fin de l'article.

Safari 5 has just been released with an API for writing extensions. Writing extensions is fairly easy, as long as you know javascript. I gave it a shot and wrote my first extension: PageSaver. This extension saves a screenshot of the visible part of the page to an image in your download folder. Right now, it's still rough (images have filenames that make no sense) but it was nice to be able to write it within an hour.

Get the extension here: PageSaver.safariextz 1.1

En français: Safari 5 vient juste de sortir, avec une API pour développer des extensions. Ces extensions sont plutôt faciles à développer, dans la mesure où il suffit de savoir programmer en javascript. J'ai essayé et j'ai pu créer ma première extension: PageSaver. Cette extension fait une capture de la partie visible de la page et la sauvegarde en tant qu'image dans votre dossier de téléchargement. Pour le moment, c'est encore assez brut (les images ont des noms qui ne ressemblent à rien) mais c'est assez sympa de pouvoir écrire une extension en une heure à peine.

jeudi, février 19 2009

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:

vendredi, octobre 6 2006

Ornements pour vos sites web, partie 2

Cette série est la suite du précédent pack d'ornements publiés il y'a quelques mois. Je suis content de voir qu'ils ont pu servir dans quelques projets déjà.

Ornements

Télécharger les ornements (fichiers SVG, zippés, 14Ko)

Comme d'habitude, c'est libre et gratuit. Si vous ne savez pas comment ouvrir les fichiers SVG, essayez Inkscape, ça déchire sa race, comme on dit.

mercredi, août 16 2006

Recherche graphiste pour projet libre

Apinc Oui je sais, ce blog commence à se transformer en site de petites-annonces, mais j'ai promis de faire passer le message.

Donc, le projet libre en question est GHS, le système de gestion d'hébergement utilisé par l'Apinc. Une nouvelle version de l'outil est en préparation pour cet hébergeur associatif. Si côté code, il semble y avoir suffisament de monde, côté graphisme et interface, le projet manque de bras.

Si l'un de vous souhaite aider la très utile association Apinc (qui héberge openweb.eu.org notamment), n'hésitez pas à contacter Yann pour postuler !

vendredi, juillet 14 2006

Fête nationale, des drapeaux pour vos sites web

Je ne sais pas si c'est l'effet 14 juillet, mais voilà quelques icônes qui peuvent s'avérer utiles: les drapeaux des 25 pays de l'Union Européenne.

Drapeaux de l'union européenne

Télécharger les icônes (fichier zip, 20Ko)

Comme à l'habitude, ces icônes sont placées sous licence Creative Commons BY (la moins restrictive) donc librement et gratuitement utilisables sur vos sites web si vous respectez cette licence.

J'espère aussi avoir reproduit fidèlement (pas facile à cette résolution) chacun des drapeaux. Si vous trouvez qu'un pays n'a pas bien été représenté, je m'en excuse par avance et vous demande de me le faire savoir.

Concernant la suite, je vais sûrement compléter ce jeu d'icônes avec les autres pays du monde mais il faudra patienter, cela prend du temps. Il faut aussi que je réaménage la rubrique "Créations" du site pour rendre le téléchargement des icônes plus pratique. D'ici là, utilisez ces icônes où bon vous semble et bonne fête nationale !

mercredi, juillet 12 2006

Il fallait que ça arrive

Il y'a près d'un an, je vous parlais de capturer les mouvements et les clics de la souris à l'aide d'XmlHttpRequest. Je proposais même une démonstration intitulée "Ajax is evil". La prédiction est désormais réalité. Je suis tombé par l'intermédiaire de digg.com sur le site ClickTale qui vous propose de traquer l'activité de la souris de vos visiteurs. ClickTale est présenté comme un outil permettant d'améliorer l'ergonomie d'un site web et de gagner en efficacité. Reste à voir si les résultats seront réellement exploitables car ces données récupérées en masse et à l'aveugle risquent de produire beaucoup d'enregistrements inutilisables. Si quelqu'un a pu tester la beta, ça m'intéresse de savoir si c'est vraiment utilisable.

vendredi, juin 16 2006

Ornements pour vos sites web

Je ne sais pas si ça vous est déjà arrivé, mais parfois il m'arrive de voir de jolis ornements sur des sites web et je me dis que ce serait pas mal sur certaines de mes créations. Mais là, si on a un peu de conscience professionnelle, on se pose immédiatement la question de la licence car il est hors de question de repomper sans scrupules. Et là le flou est de rigueur, ces ornements sortent souvent de cliparts livrés avec les logiciels graphiques ou trouvés au hasard lors d'une recherche sur Google Image. La solution ? Faire soi-même. Et bien c'est ce que j'ai fait, j'en ai créé moi-même. Et comme d'habitude, les voici disponibles sous licence Creative Commons (BY), donc libres et gratuits:

Ornements

Télécharger les ornements (zippés, 14Ko)

Pour le moment, il n'y en a que 5, en SVG, mais j'ai d'autres croquis en stock. Ce sont mes premiers essais, donc je demande votre indulgence. J'essaierai de faire mieux pour les prochains. En attendant, j'espère que vous saurez en faire une utilisation créative !

- page 1 de 7