Tutos

Encore un peu plus loin dans prototype

Billet original sur blogbangbang.com

Après plusieurs discussions à l’école comme au travail, j’avais envie de tester un peu plus le format JSON pour donner et recevoir des données en AJAX (on devrait dire AJAJ du coup non ?).

Et vu que j’avais également envie de tester des petites choses avec prototype sur à la conférence ParisOnRails, je me suis dis pourquoi pas faire d’une pierre deux coups.

Je vous propose donc le résultat de mes différents tests avec Prototype 1.6 et Scriptaculous côté Javascript et PHP (version 5.2 minimum obligatoire) côté serveur.

J’ai retenu de la conférence de Cristophe Portenneuve que je faisais une grosse bêtise en n’utilisant pas les trésors d’Énumérable (un module de Prototype dédié aux collections), je me suis donc penché de ce côté-là et essayé de construire un petit “site”, ou les clics sur les liens sont surveillés par Prototype, envoyés au serveur afin de tenir des statistiques, puis renvoyé (statistiques comprises) en JSON à l’application afin d’offrir un retour à l’utilisateur (ceci afin de tester un autre élément intéressant du nouveau Prototype, les Templates).

Voilà là donc le résultat, en commencant par le code :

/*
* J'observe le document pour repéré l'évenement 'dom:loaded' signifiant que le dom est chargé
*/
document.observe('dom:loaded', init);
function init(){
/*
* Je récupère un tableau contenant tout les liens et j'appelle pour chaque élément
* la méthode observe avec les paramètres mouseover et link_click_handler.
*
* Cette méthode crée un écouteur sur chaque lien qui appellera la méthode link_click_handler
* à chaque évenement click sur le lien.
*/
$$('a').invoke('observe', 'click', link_click_handler);
document.observe('click',click_handler);
}
/*
* Je crée un object handler (de type fonction)
* que je vais appliquer à tous les objets que je veut surveiller
*/
var link_click_handler = function register_link_click(){
/*
* Je crée ici mon objet lien en Javasript, il contient le texte et la cible du lien surveillé
* Le formatage link[] pour les clés permet de récupérer un array link avec toutes les propriétés coté Serveur
* S'il existe un moyen plus élégant d'obtenir le même résultat, je suis preneur...
*/
var link = {
'link[text]': this.innerHTML,
'link[href]': this.href
}
//Je crée une requête AJAX
new Ajax.Request('register_links.php', {
//en POST c'est plus sur pour passer des données
method: 'post',
parameters:  $H(link),
/*
* la seule ligne un peu compliquée Je transforme mon object JS
* en HASH JS auquel j'applique la méthode toJSON qui porte bien son nom ;)
*/
onSuccess: function(transport){
//je passe la réponse JSON à la fonction qui va remplir mon tableau
populate_links_table(transport.responseJSON);
}
})
}
var click_handler = function register_link_click(e){
populate_clicks_table(e);
}
/*
* La fonction chargée de peupler mon tableau
* Elle recoit le résultat de la requête AJAX PHP
*/
function populate_links_table(link){
/*
* Je crée un Template Prototype de ce que je souhaite inséré
* en mettant entre #{} (syntaxe Ruby) les propriété dynamiques
*/
var rowTemplate = new Template(' #{text} #{href} #{timestamp}');
//J'appelle la méthode insert de prototype sur mon élément tableau
$('clicked_links_table').insert({
/*
* Je spécifie d'insérer les éléments en bas (bottom) du tableau puis
* j'évalue (interprête) mon template avec les propriétés associées
*/
bottom: rowTemplate.evaluate({
text: link.text,
href: link.href,
timestamp: link.timestamp
})
});
/*
* Un petit effet de pulsate sur la dernière ligne insérée avant de lui retirer la classe last_row
*/
$$('.last_row').invoke('pulsate');
$$('.last_row').invoke('removeClassName','last_row');
}
function populate_clicks_table(event){
var elem = event.target ;
var rowTemplate = new Template('#{identifiant} #{posX} #{posY} ');
$('clicks_table').insert({
bottom: rowTemplate.evaluate({
identifiant: elem.identify(),
posX: event.pointerX(),
posY: event.pointerY()
})
});
/*
* Un petit effet de pulsate sur la dernière ligne insérée avant de lui retirer la classe last_row
*/
$$('.last_row').invoke('pulsate').invoke('removeClassName','last_row');
}

Le Javascript fait usage d’une autre fonctionnalité de Prototype1.6, l’évènement dom:loaded afin de repérer le moment où le DOM est complètement chargé.

C’est à cet endroit que doit se situer 99,99% du code JS que vous souhaitez exécuter au chargement de la page.

Dans mon cas l’initialisation consiste à surveiller tous les liens pour repérer les clics et agir en conséquence.

C’est là où on est heureux d’utiliser un framework car grâce à $$('a') je récupère une collection de tous les liens du document.

Grâce à la méthode invoke, de Enumerable, j’appelle une méthode identique sur tous les objets de cette collection.

Le premier paramètre étant la méthode appelée, les suivants les paramètres à passer.

Je fait donc ici l’équivalent d’un $('un_lien').observe('click',link_click_handler) pour chaque lien ce qui serait bien plus long à écrire (boucle each puis observe su chaque lien…) et bien moins performant.

Pour ce qui est de la méthode observe elle indique à l’objet “appelant” de surveiller l’évènement passé en premier paramètre et exécute le deuxième paramètre.

Pendant que nous sommes dans les évènements, étudions la suite, le deuxième tableau de la page liste tous les clics, l’élément cliqué (son id s’il en a un) ainsi que les positions X et Y du clic.

On pourrait penser à faire quelque chose comme $$('*').invoke('observe', 'click', click_handler); mais ce serait oublier une chose très pratique avec la majorité des évènements, c’est qu’ils font des bulles (en anglais on dit bubbling event).

Cela veut dire que si la cible du lien ne l’intercepte pas, il est passé à l’élément supérieur et ainsi de suite.

On peut donc se contenter d’un écouteur sur le document qui détectera tous les clics : document.observe('click',click_handler)

Rien de très magique dans la partie AJAX du javascript à part le responseJSON qui vous permet de récupérer directement l’objet JSON renvoyé par le serveur, je ne m’attarderais pas plus.

Hop le javascript a fait son boulot (pour l’instant), la suite se passe côté serveur (en PHP, rare sur ce blog mais ça permet à tout le monde de suivre et en Ruby la logique est la même )

Le code :

/*
* Je crée une petite classe Link qui va geré les liens et les sauvegarder dans ma Base de Données
*/
class Link{
var $text ;
var $href ;
var $timestamp ;
function __construct($_text, $_href) {
$this->text     = $_text;
$this->href     = $_href;
$this->timestamp= time();
}
function save(){
//Je sauvegarde le lien dans ma Base de Données
}
}//END Class LINK
//Je précise le header (type de contenu) de ce que je vais renvoyé, nécessaire pour avoir acces au responseJSON de Prototype
header('Content-type: application/json');
/*
* Je récupère le contenu passé en POST
*/
$post_data = ($_POST["link"]) ;
//Je construit mon objet Link
$link = new Link($post_data["text"],$post_data["href"]);
/*
* Je renvoi l'objet JSON complet (avec timestamp) pour vous montrer les templates en Prototype
* et également car une requete Ajax ne se termine bien (onSuccess de Prototype)
* qu'a la réception d'une réponse qui peut être print(""); si vous ne souhaitez rien renvoyé de particulier
* mais qui doit absolument exister.
*/
echo json_encode($link);

Côté php donc, plus besoin de s’embêter à créer du XML, je récupère l’objet link passé en POST.

Je “l’augmente” avec une info timestamp dans le constructeur de sa classe.

Et je renvoie le tout en JSON en 2 temps trois mouvements en faisant json_encode($link) qui me renvoie par exemple

{"text":"Date","href":"http://localhost/lab/proto1_6/#api/date","timestamp":1202080178}

Retour côté Javascript, je récupère l’objet renvoyé et le passe à la méthode chargée de peuplé le tableau adéquat.

Là encore énorme gain de temps et de lignes de code (donc de probables erreurs) car l’on a plus à parser, le XML pour construire un objet Javascript, on a déjà un objet javascript.

Je construit donc un template qui correspond au code HTML que je souhaite inséré dans le tableau en spécifiant les propriétés dynamiques avec la syntaxe inspirée du ruby #{var}

J’appelle ensuite la méthode insert de prototype, lui spécifie d’insérer à la fin du tableau le résultat de l’évaluation du template précédemment crée avec les variables propres à mon Evenement.

var rowTemplate = new Template('#{identifiant} #{posX} #{posY} ');
$('clicks_table').insert({
bottom: rowTemplate.evaluate({
identifiant: elem.identify(),
posX: event.pointerX(),
posY: event.pointerY()
})
});

Voilà, c’est terminé pour cette fois, l’application dans l’état ne sert pas à grand-chose, mais elle a le mérite de m’avoir permis de vous montrer quelques fonctionnalités méconnues de Prototype et du Javascript en général.

Le 14 février 2008 par jblanche

Tags = ["", "", ""];

Laisser un commentaire