Google Gears : version off-line d'une application

28 septembre 2008Javascript, google-gears
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

Cet article est le second d'une série visant au développement d'une mini-application vous permettant de travailler hors-ligne, à l'aide de Google Gears.
Le premier article vous a appris comment stocker les fichiers statiques de votre site sur les postes clients de vos utilisateurs.

Nous allons à présent passer à la seconde étape : mettre en place une application dynamique, totalement indépendante de la connexion Internet, et fonctionnant même lorsque l'utilisateur n'est pas connecté !
Nous continuerons à travailler avec notre application de saisie de notes, dont le principe peut être résumé comme suit :

  • une note est composée d'un nom d'auteur, d'une date, et d'un texte
  • un formulaire permet de saisir une note
  • les trois champs sont obligatoires
  • la liste des notes saisies est affichée au-dessus du formulaire de saisie.

Sauf que, pour pouvoir être utilisée hors-connexion, notre application ne peut plus réaliser d'appels vers le serveur pour charger la liste des notes, ni pour en enregistrer un nouvelle : les notes vont devoir être enregistrées en local, sur le poste de votre utilisateur !
Pour répondre à ce besoin, Gears permet d'accéder à une base de données, en Javascript, directement au sein de votre application, sans passer par un quelconque serveur !

Les sources de l'application utilisée comme exemple sont disponibles en téléchargement ; Cf la pièce jointe à ce billet, tout en bas de celui-ci.


Pré-requis

Avant de commencer la lecture de cet article, quelques pré-requis :

  • Vous devez être intéressé par la réalisation d'une application fonctionnant hors-connexion -- après tout, c'est le sujet ^^
  • Vous savez déjà mettre en place Google Gears, du moins pour un usage de base :
  • Vous avez quelques notions de SQL -- nous allons écrire quelques requêtes simples,
  • Vous connaissez les bases de Javascript (un minimum d'orientation objet), et comprenez les bases de l'utilisation de framework Prototype, que j'utiliserai pour mes exemples.


Gears : Base de Données SQL sur le poste client

A partir de l'instant où l'utilisateur de votre application a autorisé le fonctionnement de Gears pour votre site, vous pouvez, directement depuis le code Javascript de votre application, accéder à une base de données SQLite (version 3), stockée sur la machine de votre utilisateur.

Cette base de données se manipule à l'aide de deux classes fournies par Gears :

  • La première classe est Database, pour ce qui est manipulation de la base de données en elle-même :
    • Ouverture et fermeture de connexion,
    • Exécution de requêtes,
    • Récupération du nombre de lignes affectées par la dernière requête d'écriture exécutée,
    • Et récupération de l'identifiant de la dernière ligne insérée en DB.
  • La seconde classe est Resultset, qui permet, comme son nom l'indique, d'accéder aux résultats d'une requête de chargement de données :
    • Parcours des lignes de résultats,
    • Et, pour chaque ligne, récupération des données la composant.


Ouvrir une connexion à une Base de Données

Une instance d'objet Database, permettant de manipuler une base de données et d'effectuer des requêtes dessus, s'obtient à l'aide de la portion de code suivante :

db = google.gears.factory.create('beta.database');

Une fois cette instance de Database obtenue, il faut ouvrir une connexion vers la base de données qui vous intéresse - typiquement, la Base de Données de votre application :

db.open('database-test');

Notre application s'appelant "test", j'ai choisi de nommer ma base de données "database-test"[1].

Notez que lorsque vous ouvrez une Base de Données, si elle n'existe pas, elle sera créée, vide.


Effectuer des requêtes sur votre Base de Données

Une fois que vous avez obtenu une instance d'objet Database et que vous avez indiqué sur quelle base de données vous souhaitiez travaillez, vous pouvez commencer à effectuer des requêtes sur cette base.

Pour cela, il faut utiliser la méthode execute de votre instance d'objet Database.
Par exemple, pour charger toutes les données se trouvant dans la table "notes" :

db.execute('select * from notes');

Si la requête se passe bien, execute renverra une instance d'objet Resulset, permettant d'accéder aux données.
Dans le cas contraire, une exception sera levée.


Créer les tables dans votre Base de Données

Lorsque vous ouvrez une connexion vers une Base de Données, si celle-ci n'existait pas, elle est créée... Mais totalement vide... Ce qui ne correspond pas vraiment aux besoins de votre application, qui s'attend au minimum à y trouver des tables, et, probablement, quelques données (du style données de "référence", par exemple).

Dans le cadre de notre application d'exemple, nous attendons une table, nommée "notes", composée des champs suivants :

  • id : clef primaire, entier, autoincrement
  • auteur : chaine de caractères
  • date : chaine de caractères : SQLite n'a pas de vraie notion de types... Donc, pas de type "date" ou équivalent.
  • texte : chaine de caractères

Ces champs sont tous attendus comme NOT NULL.

Au niveau SQL, nous pourrions utiliser le script de création suivant :

create table notes
(
    id integer primary key autoincrement,
    auteur text not null,
    date text not null,
    texte text not null
)

Sans cette table, notre application de fonctionnera pas... Et, bien évidemment, on ne peut pas demander à l'utilisateur de cliquer sur un bouton pour créer la structure de nos table !
Il nous faut donc détecter, au chargement de la page, si la table existe... Et, si elle n'existe pas, la créer.


Détecter l'existence d'une table

Pour détecter l'existence d'une table dans notre base, une solution est d'essayer de compter le nombre de lignes qu'elle contient.
Si la requête réussi, c'est que la table existe... Et si elle échoue, c'est que la table n'existe pas - et, probablement, que la Base de Données est vide.

Comme indiqué plus haut, exécuter une requête SQL s'effectue à l'aide de la méthode Database.execute, qui lève une exception en cas d'échec.
On peut donc, pour détecter l'existence de notre table de notes, utiliser la portion de code suivante :

try {
    db.execute('select count(*) from notes');
    // Pas d'exception => la table existe
} catch (e) {
    // Exception => la table n'existe probablement pas
}

Nous essayons de compter le nombre de lignes dans la table de notes ; si la requête lève une exception, c'est probablement que la table en question n'existe pas !
Globalement, n'importe quelle requête ferait l'affaire : compter le nombre de lignes n'est qu'une possiblité -- peut-être pas forcément la plus judicieuse si votre table est volumineuse, d'ailleurs !


Créer une table

A partir de là, créer la table devient uniquement une histoire de lancer une requête de plus :

sql = 'create table notes ' +
    '(id integer primary key autoincrement, ' +
    'auteur text not null, ' + 
    'date text not null, ' +
    'texte text not null)';
db.execute(sql);

On retrouve la requête de création de table évoquée plus haut ; et, une fois qu'elle a été jouée, la requête de comptage du nombre de lignes dans la table ne lèvera plus d'exception.


Insérer des données dans une table

Maintenant que nous avons à la fois une base de données et une table dans celle-ci, il devient possible d'y insérer des données.

Ici encore, rien de compliqué : nous utilisons la méthode Database.execute pour lancer une requête insert.
Un point à noter, cependant : Gears permet de travailler avec des requêtes paramétrées : vous passez en premier paramètre à la méthode execute une requête SQL contenant des point d'interrogation, et, en seconde paramètre, une liste de valeurs, qui seront injectées dans la requête, à la place des points d'interrogations, une fois proprement échappées.

Par exemple, pour insérer une note :

db.execute('insert into notes (auteur, date, texte) values (?, ?, ?)', 
    ['user1', 
    '2008-06-18', 
    "Ceci est la seconde note de l'application"]);

On remarque les trois ? dans la requête, qui seront remplacées par les trois valeurs que contient la liste passée en second paramètre.

On évitera d'utiliser une syntaxe de cette forme (requête incorrecte !) :

db.execute("insert into notes (auteur, date, texte) " + 
    "values ('user1', '2008-06-18', 'Ceci est la seconde note de l'application')");

Si vous lancez cette portion de code, vous aurez en retour une exception : "Error: SQLite prepare() failed. ERROR: SQL logic error or missing database DETAILS: near "application": syntax error EXPRESSION: insert into notes (auteur, date, texte) values ('user1', '2008-06-18', 'Ceci est la seconde note de l'application')".

Ceci est dû au fait qu'avec cette écriture, c'est à vous de gérer l'échappement des données injectées dans la requête ; l'apostrophe devant "application", par exemple, ici, doit être échappée : la requête correcte serait la suivante :

db.execute("insert into notes (auteur, date, texte) " + 
    "values ('user1', '2008-06-18', 'Ceci est la seconde note de l''application')");

Vous remarquerez au passage que l'échappement des apostrophes se fait en les doublant, comme en SQL Server, et non en les précédent d'un antislash, comme en MySQL...

En somme : utilisez toujours des requêtes paramétrées : laissez Gears faire le travail d'échappement à votre place !


Obtenir des données depuis la Base de Données

Requête de sélection

A partir du moment où nous avons une base de données, une ou plusieurs table(s), et des données dans celle(s)-ci, il devient possible de charger des données depuis la Base de Données.
Encore une fois, cela se fait à l'aide de la méthode Database.execute, mais en lançant cette fois-ci une requête select.

Par exemple, la requête utilisée sur notre application pour charger toutes les notes depuis la DB, en renvoyant les plus récentes en tête de liste est exécutée comme ceci :

var rs = db.execute('select * from notes order by date desc');

Dans le cas d'une requête de sélection, la méthode execute renvoi une instance d'objet Resultset permettant d'accéder aux données chargées par la requête.


Parcourir les résultats d'une requête

Une fois le Resultsat chargé, il est possible de le parcourir, à l'aide de la structure suivante :

while (rs.isValidRow()) {
    // Travail avec la ligne courante
    rs.next();
}

Tant qu'il y a des lignes dans le Resultset, nous avons une ligne sur laquelle travailler... Puis nous passons à la suivante.


Travail avec la ligne courante d'un Resultset

Lors du parcours des lignes du Resultset, plusieurs méthodes vous permettent d'accéder aux données de la ligne courante :

  • fieldCount : cette méthode renvoi le nombre de colonnes que comporte la ligne courante (et l'ensemble du Resultset : les lignes comportent toutes autant de colonnes les unes que les autres)
  • fieldName : cette méthode reçoit un paramètre entier, et renvoi le nom du champ présent à la position passée en paramètre, en comptant à partir de zéro.
  • field : cette méthode prend un paramètre, entier en comptant à partir de zéro ici aussi, et renvoi la valeur du champ se trouvant à cette position.
  • fieldByName : cette méthode, enfin, est probablement la plus utile : elle prend en paramètre un nom de champ, et renvoi sa valeur.

Par exemple, pour connaitre le nom de l'auteur et le contenu d'une note, vous utiliseriez les instructions suivantes :

var auteur = rs.fieldByName('auteur');
var texte = rs.fieldByName('texte');

Ou alors, considérant que notre requête effectuait un select *, les champs seront renvoyés dans l'ordre suivant : id, auteur, date, texte.
Les instructions suivantes auront donc le même effet :

var auteur = rs.field(1);
var texte = rs.field(3);

Mais si vous modifiez votre requête, ou l'ordre des colonnes dans la table... votre code ne fonctionnera plus correctement !
Aurez-vous vous aussi tendance à préférer l'approche à base de fieldByName autant que possible ?


Clore un Resultset

Enfin, une fois que vous avez terminé votre travail avec le Resultset, vous devez le clore, à l'aide de la méthode Resultset.close :

rs.close();


Accéder directement à la Base de Données SQLite

La Base de Données utilisée par Gears est une base SQLite (v3) ; il est possible d'y accéder directement et d'effectuer des requêtes dessus, sans passer par Gears, en utilisant le client auquel vous êtes habitué pour ce genre de manipulation.
Naturellement, ceci n'est possible que si vous avez accès à la machine de votre utilisateur... Ce qui est généralement le cas de l'ordinateur sur lequel vous développez et testez votre application (puisque, dans ce cas, vous en êtes vous-même utilisateur).

L'intérêt est de pouvoir jouer n'importe quelle requête SQL, sans avoir à modifier votre application.
Par exemple, dans le cadre de l'exemple que j'ai pris pour cet article, j'ai plusieurs fois utilisé une requête de la forme suivante pour "réinitialiser" ma Base de Données locale aux données qu'elle contenait au moment où nous avons créé les tables et les trois premiers enregistrements :

delete from notes where id > 3;

Lancer directement une requête de ce genre est tout de même plus pratique que de devoir modifier l'application pour lui ajouter une interface d'administration - ou que d'incorporer des requêtes SQL en plus dans nos fichiers JS !

La documentation de Gears indique où sont placées les Bases de Données sur le disque du poste client.
Sur ma machine (sous Linux), la base de données utilisée ici se trouve à l'emplacement suivant :

$ ll /home/squale/.mozilla/firefox/cd7t8m2c.FF3/Google\ Gears\ for\ Firefox/tests/http_80/
total 20
-rw------- 1 squale squale 16384 2008-09-27 20:09 database-test#database
drwx------ 2 squale squale  4096 2008-09-27 20:12 test_managed[84]#localserver

Dans le chemin indiqué, "tests" correspond au nom du site (souvenez-vous de la boite de dialogue reproduite ci-dessous demandant l'autorisation d'activer Gears pour votre site), et le nom de fichier "database-test" correspond au nom de la base de données en elle-même : celui passé en paramètre à Database.open.

gears-demande-autorisation.png

Sachant cela, nous pouvons nous connecter à cette base de données ; par exemple, en utilisant le client SQLite (v3, encore une fois !) en ligne de commandes :

$ sqlite3 -header -column ~/.mozilla/firefox/cd7t8m2c.FF3/Google\ Gears\ for\ Firefox/tests/http_80/database-test#database
SQLite version 3.4.2
Enter ".help" for instructions
sqlite> 

Et nous n'avons maintenant plus qu'à taper nos requêtes !

Par exemple, une fois la base de données initialisée :
Lister les tables contenues dans notre base :

sqlite> .tables
notes

Il n'y en n'a qu'une : après tout, notre application est des plus simples ^^

Et pour lister les données :

sqlite> select * from notes;
id          auteur      date        texte
----------  ----------  ----------  -------------
1           user1       2008-05-15  Hello, world!
2           user1       2008-06-18  Ceci est la s
3           user1       2008-07-22  Et voici la t


Application complète d'exemple

Pour cet article, mon exemple est le même que pour le précédent : une application des plus simples, qui permet de saisir des notes et de les restituer.
La nouveauté est que cette application doit maintenant fonctionner en mode déconnecté, sans le moindre appel au serveur une fois qu'elle a été chargée.

Pour cela, en complément du stockage local des fichiers tel que vu au cours de l'article précédent, il va falloir stocker les données dans une Base de Données sur le poste client -- et, en parallèle, modifier l'application pour qu'elle utilise cette Base de Données et n'effectue plus d'appel serveur !


Page HTML de l'application

Dans sa version précédente, la page "index.php" se chargeait d'un peu tous les traitements de notre application : chargement des données depuis la DB, construction de la liste à afficher, et gestion de l'enregistrement...
Puisque nous ne devons plus effectuer d'appel serveur, tout ceci est à recoder en Javascript... Sans laisser quoi que ce soit de réellement dynamique dans notre page : celle-ci n'est chargée depuis le serveur que la toute première fois, avant que l'utilisateur n'active le mode "Hors-connexion" !

Au niveau des points notables de notre page :

Les blocs de confirmation et de message d'erreurs sont maintenant toujours présents, mais ils sont masqués par défaut :

<div id="error" style="display: none;"></div>
<div id="ok" style="display: none;">
    Enregistrement effectué !
</div>

Nous intégrons à la page un élément qui contiendra la liste des notes :
Par défaut, il est vide : il sera rempli en Javascript, lors du chargement des notes depuis la Base de Données stockées sur le poste de l'utilisateur.

<div id="notes"></div>

Le formulaire d'ajout de note ne change pas beaucoup :
La méthode JS appelée à la soumission est importante : c'est elle qui va effectuer tout le travail d'enregistrement, et plus le serveur,
Et on ne ne ré-affiche pas, en cas d'erreur, les valeurs saisies côté serveur : ceci aussi sera fait en JS côté client.

<form id="formAjoutNote" method="post" action="" onsubmit="return gestionAjoutNote();">
    <p>
        Auteur : <input type="text" name="auteur" id="auteur" maxlength="64" />
        <br />Date : <input type="text" name="date" id="date" maxlength="10" />
        <br />Texte :
        <br /><textarea name="texte" id="texte" rows="5" cols="50"></textarea>
        <br /><input type="submit" value="Hop !" name="ok" />
    </p>
</form>

Et enfin, pour cette application d'exemple, nous incluons un élément qui recevra des informations de debug, pour vous permettre de comprendre ce qu'il se passe :

<div id="debug"></div>

Rien de très différent, donc, si ce n'est que nous avons supprimé tout ce qui se passait auparavant côté serveur...


Nouveau fichier manifest

L'application développée précédemment stockait tous ses fichiers sur le poste client... Sauf un : la page en elle-même.
Il nous faut donc modifier notre fichier manifest, pour lui ajouter la page "index.php".
Et, par commodité, nous ajouterons que l'URL racine du site est un alias vers "index.php".

Notre fichier manifest devient donc le suivant :

{
    "betaManifestVersion": 1, 
    "version": "0.1", 
    "entries": 
    [
        {"url": ".", "src": "index.php"}, 
        {"url": "index.php"}, 
        {"url": "css/style.css"}, 
        {"url": "js/lib/gears_init.js"}, 
        {"url": "js/lib/prototype.js"}, 
        {"url": "js/lib/scriptaculous/builder.js"}, 
        {"url": "js/lib/scriptaculous/effects.js"}, 
        {"url": "js/lib/scriptaculous/dragdrop.js"}, 
        {"url": "js/lib/scriptaculous/controls.js"}, 
        {"url": "js/application.js"}, 
        {"url": "js/gestion-gears.js"}, 
        {"url": "img/error.png"}, 
        {"url": "img/tick.png"}
    ]
}

Au niveau des différences avec ce que nous avions auparavant, il faut juste noter que nous avons ajouté les deux premières entrées à la liste d'URLs à stocker en local.


Initialisation de la Base de Données

Ouverture de la connexion

Au chargement de la page, nous ouvrons une connexion vers la Base de Données.
Cette connexion nous servira notamment pour afficher la liste des notes : pour rappel, elle n'est plus reçue par le client depuis le serveur dans le contenu de la page : c'est au client de la générer !

Cette ouverture de connexion se fait via la méthode initDB, définie comme suit :

var initDB = function () {
    db = google.gears.factory.create('beta.database');
    db.open('database-test');
    createTablesIfNeeded();
    affichageListeNotes();
}; // initDB

Cette méthode, appelée au chargement de page si Gears a été autorisé par l'utilisateur :

  • Obtient une instance d'objet Database et ouvre la Base utilisée par notre application : database-test,
  • Puis appelle la méthode qui détectera si la DB contient la table utilisée par notre application,
  • Et enfin, affiche la liste des notes actuellement enregistrées en DB.

Création de la table, et insertions de notes par défaut

La création de la table se fait, comme indiqué plus haut, seulement si nécessaire : nous commençons par jouer une requête de sélection pour déterminer si elle existe déjà... Et si ce n'est pas le cas, nous la créons, et insérons trois premiers enregistrements, pour éviter d'avoir un affichage complément vide au lancement de notre application de démonstration :

var createTablesIfNeeded = function () {
    var sql = '';
    try {
        sql = 'select count(*) from notes';
        $('debug').innerHTML += sql + '<br />';
        db.execute(sql);
        $('debug').innerHTML += "Pas d'exception => la table semble exister<br />";
    } catch (e) {
        // Il semble que la DB n'ait pas été initialisée
        // => Création des tables, et insertion de quelques enregistrements
        sql = 'create table notes ' +
            '(id integer primary key autoincrement, ' +
            'auteur text not null, ' + 
            'date text not null, ' +
            'texte text not null)';
        $('debug').innerHTML += sql + '<br />';
        db.execute(sql);
        [
            {auteur: 'user1', date: '2008-05-15', 
                texte: "Hello, world!"},
            {auteur: 'user1', date: '2008-06-18', 
                    texte: "Ceci est la seconde note de l'application"},
            {auteur: 'user1', date: '2008-07-22', 
                        texte: "Et voici la troisième\nnote !"}
        ].each(function (data) {
            $('debug').innerHTML += 'insert into notes : ' + 
            	$H(data).inspect().escapeHTML() + '<br />';
            db.execute('insert into notes (auteur, date, texte) values (?, ?, ?)', 
                [data.auteur, data.date, data.texte]);
        });
    }
}; // createTablesIfNeeded

En somme :

  • Détection de l'existence de la table ; qui lève une exception si elle n'existe pas.
  • Création de la table
  • Parcours d'un tableau contenant les données correspondant à trois lignes, et, pour chacune de ces lignes, insertion des données en base.

Une fois cette méthode appelée, soit :

  • La table existait, et la méthode n'a rien fait,
  • La table n'existait pas ; auquel cas, elle a été créée, et trois lignes y ont été insérées.

Et voici ce que vous obtiendriez au moment où la table est créée, et les données d'exemple insérées :

creation-db-et-tables.png

A noter : la table n'est créée qu'une et une seule fois... Vous ne verrez donc pas souvent ceci, sauf si vous effacez physiquement le fichier de base de données, ou que vous droppez la table !


Affichage de la liste des notes

Maintenant que nous avons des notes en Base de Données, il faut les afficher.

A ce sujet, la page HTML, au départ, ne contient qu'un <div> vide, qu'il va falloir remplir :

<div id="notes">
</div>

Voici le code Javascript qui charge les notes depuis la base de données, et les injecte dans le corps de la page :

var affichageListeNotes = function () {
    var noteTpl = new Template('<p class="note">Auteur : #{auteur}<br />Date : #{date}<br />Texte : #{texte}</p>');
    var sql = 'select * from notes order by date desc';
    $('debug').innerHTML += sql + '<br />';
    var rs = db.execute(sql);
    $('notes').innerHTML = '';
    while (rs.isValidRow()) {
        var note = {
            auteur: rs.fieldByName('auteur').escapeHTML(),
            date: rs.fieldByName('date').escapeHTML(),
            texte: rs.fieldByName('texte').escapeHTML()
        };
        var htmlNote = noteTpl.evaluate(note);
        $('notes').insert({bottom: htmlNote});
        $('debug').innerHTML += rs.fieldByName('id') + 
            ' : ' + rs.fieldByName('auteur') + 
            ' : ' + rs.fieldByName('date') + 
            ' : ' + rs.fieldByName('texte') + 
            '<br />';
        rs.next();
    }
    rs.close();
}; // affichageListeNotes

Nous mettons en pratique ce que nous avons vu plus haut au sujet de l'exécution de requêtes de sélection, et du parcours de Recordset.

Pour construire le code HTML permettant d'afficher chaque ligne, nous utilisons les fonctionnalités de Templating fournies par prototype.js, pour nous faciliter la tâche : c'est plus simple, et nettement plus maintenable, de la sorte qu'en travaillant avec des concaténations de chaînes de caractères !
Et ceci, même si ce n'est que peu visible sur une portion de code HTML aussi courte, prend toute son importance lorsqu'il s'agit de générer des portions de code HTML plus conséquentes !

Notez un point : vous injectez dans du code HTML des données issues d'une base de données... contenant des saisies utilisateur, qui plus est...
N'oubliez donc pas d'échapper les données chargées depuis la base de données, pour éviter toute injection HTML / faille XSS !

Voici, typiquement, ce que vous devriez obtenir à l'écran lors de l'affichage de la liste des notes :

application-hors-ligne-1.png

Et si vous jetez un coup d'oeil à l'onglet Network de Firebug :

firebug-network-gears-active.png

Aucun chargement de fichier n'est effectué depuis le serveur !
N'est-ce pas merveilleux ?


Enregistrer une nouvelle note

Enfin, il ne nous reste plus qu'à gérer l'enregistrement d'une nouvelle note.
Pour cela, nous branchons une méthode JS sur la soumission du formulaire ; c'est cette méthode qui devra gérer tout le travail, sans jamais déclencher d'appel au serveur !

Globalement, gérer l'enregistrement d'une nouvelle note signifie :

  • Effectuer les vérifications de saisies,
  • Enregistrer les données en base,
  • Effacer le contenu du formulaire : si les données ont été enregistrées, pas de raison qu'elles apparaissent encore dans le formulaire !
  • Afficher un message de confirmation,
  • Et enfin, ré-afficher la liste de notes, mise à jour.
  • Le tout en gérant les erreurs et échecs, bien évidemment ^^

Voici ce que ça peut donner au niveau du code :

var gestionAjoutNote = function () {
    if (validation()) {
        var auteur = $F('auteur');
        var date = $F('date');
        var texte = $F('texte');
        try {
            db.execute('insert into notes (auteur, date, texte) values (?, ?, ?)', 
                [auteur, date, texte]);
            $('debug').innerHTML += 'Enregistrement OK<br />';
            
            // Effacement du contenu des champs du formulaire
            $('auteur').value = '';
            $('date').value = '';
            $('texte').value = '';
            
            // Affichage confirmation d'enregistrement
            $('ok').show();
            $('error').hide();
            setTimeout(function () {$('ok').hide();}, 5000);
            
            // Chargement et affichage de la liste mise à jour
            affichageListeNotes();
        } catch (e) {
            // Affichage message d'erreur
            $('error').innerHTML = 'Echec Enregistrement :<br />' + e.message + '<br />';
            $('error').show();
            $('debug').innerHTML += 'Echec Enregistrement : ' + e.message + '<br />';
        }
    }
    return false;
}; // gestionAjoutNote

Globalement, je ne vois rien à ajouter : il s'agit, point par point, de l'implémentation du comportement décrit juste au-dessus.

Une remarque, tout de même : nous aurions pu ne pas effacer le contenu de la liste de notes pour la re-remplir juste ensuite... Mais puisque la date des notes est notre critère de tri à l'affichage, et qu'elle est saisissable dans le formulaire, tout réafficher est le plus simple : sinon, il aurait fallu où insérer la nouvelle note dans la liste des notes déjà affichées !

Voici une capture d'écran présentant un exemple de saisie dans le formulaire, avant validation :

formulaire-ajout-note.png

Et la suite, une fois le formulaire validé et la nouvelle note enregistrée en Base :

application-apres-ajout-note.png

Par contre, si une erreur de saisie s'était produite, nous aurions obtenu un affichage de la forme suivante :

erreur-saisie.png

Et si c'était une erreur au moment de l'enregistrement en base, nous aurions eu :

tentative-insert-requete-sql-erreur.png

(En l'occurrence, pour obtenir cette capture d'écran, j'ai volontairement introduit une erreur SQL dans la requête d'insertion ^^ )

A vous de "blinder" vos vérifications de saisies, 100% Javascript, pour limiter au maximum des cas d'envoi de données incorrectes vers la couche SQL !


Développement, Base de Données, et LocalServer

La première étape de notre série d'articles sur Google Gears a été de stocker les fichiers en local sur le poste utilisateur, et la seconde, de parvenir à stocker des données dynamiques au sein d'une Base SQL locale.

Cependant, il faut noter que ces deux fonctionnalités, même si elles sont liées, ne sont pas dépendantes l'une de l'autre :

  • Ces deux fonctionnalités sont fournies par Gears ; elles sont donc soumises au même accord de permission de l'utilisateur,
  • Mais il est parfaitement possible d'effectuer du stockage local à l'aide de LocalServer sans enregistrer de données à l'aide de Database : c'est ce que faisait notre premier article,
  • Et, de la même manière, il est tout à fait possible d'utiliser la classe Database sans pour autant stocker les fichiers sur le poste utilisateur !

Ce dernier point est tout particulièrement intéressant lors que vous êtes en train de développer votre application : vous modifiez fréquemment les fichiers la composant. Les stocker en local sur le poste client n'est donc pas une solution idéale, puisque qu'il vous faudrait modifier le numéro de version dans le fichier manifest à chaque modification, pour forcer la mise à jour des fichiers depuis le serveur !

Lorsque vous êtes en train de développer, vous êtes certainement connecté à Internet, et vous avez accès à votre serveur.
Vous pouvez donc :

  • Désactiver le stockage local des fichiers - tout en continuant à accéder aux fonctionnalités de Database,
  • Ou commenter dans le fichier manifest les lignes se rapportant aux fichiers sur lesquels vous travaillez - de la sorte, ces fichiers ne seront pas stockés en local, seront re-téléchargés, en version à jour, à chaque chargement de page... Et à chaque modification que vous effectuez.

La première solution est celle que j'ai mis en place dans notre application utilisée comme exemple : cliquez sur le lien "Désactiver Gears" dans le bandeau supérieur, et le stockage local ne sera plus utilisé : les fichiers seront re-téléchargés du serveur à chaque chargement de page, vous permettant de tester aisément vos modifications.
Une fois vos tests terminés, changez le numéro de version de la fichier manifest (pour forcer la MAJ des fichiers depuis le serveur), et ré-activez le stockage local, via le lien "Activer Gears".
Et vous revoila en mode non connecté !
(Je l'admet, ce lien est mal nommé : pour cet article, il devrait indiquer "stockage local", et non "Gears")

Voici une capture d'écran montrant l'application fonctionnant sans stockage local des fichiers :

application-gears-autorise-mais-storage-inactif.png

Et on voit bien, via l'onglet "Network" de Firebug, que les fichiers sont re-téléchargés depuis le serveur :

firebug-network-gears-autorise-mais-storage-inactif.png

L'accès à la Base de Données locale, quant à lui, est toujours possible.


Application hors-connexion ? Oui... mais !

Cet article nous a permis de mettre en place une application fonctionnant sans connexion Internet une fois chargée pour la première fois, à l'aide des classes LocalServer pour le stockage des fichiers, et Database pour le stockage des données ; ces classes sont toutes deux mises à votre disposition par l'extension Google Gears.

Cela dit, avec l'application que nous venons de mettre en place, même si votre utilisateur peut continuer à travailler sans être connecté à Internet, il manque encore une fonctionnalité essentielle : comment peut-il mettre en ligne le fruit de son travail une fois qu'elle revient en ligne ?
En effet, celui-ci est stocké sur sa machine, et n'est accessible que via son navigateur !

Le prochain article de cette série sur Google Gears répondra à cette problématique : comment synchroniser les versions Hors-Ligne et En-Ligne de votre application, pour enfin permettre à vos utilisateurs de réellement disposer d'une application utilisable hors connexion.


Note

[1] Peu original, je l'admet ^^