Deep linking : jouer un son en continu de page en page (dans un site Drupal)
Dernière mise à jour : 30 mai 2011
Le problème :
- ajouter un lecteur mp3 sur un site web et permettre de changer de page sans interrompre la lecture du son
- Faire en sorte que toutes les pages soient indexées dans Google
- Faire fonctionner tout cela dans Drupal (6)
La solution : une technique inspirée de Deezer reposant sur AJAX et le deep linking ; mise en oeuvre avec l'aide précieuse d'Andrea D'Agostino.
Le résultat de cette technique peut être observée sur le site de YOM : une fois le lecteur mp3 lancé, on peut se promener de page en page sans que jamais le son ne s'arrête.
Le site est développé avec la version 6 de Drupal. Le lecteur mp3 est en Flash (et bientôt en HTML 5, c'est promis).
Le principe : ne mettre à jour qu'une partie de la page
La technique consiste à charger le contenu des différentes pages du site par une requête AJAX dans une balise div de la page, sans rafraîchir la totalité de la page.
Préparer le thème Drupal
Adapter page.tpl.php
Nous avons adapté le fichier page.tpl.php de notre thème Drupal (nous aimons bien Basic comme thème de départ) de la manière suivante, à l'endroit où l'on imprime normalement la variable $content :
<div id="content-area"> <div id="content-ajax"> <p class="nojs"> <?php print t('Il semble que votre navigateur ne prenne pas en charge le JavaScript. Si possible, activez JavaScript pour profiter de tout ce que propose ce site. Si vous ne savez pas comment faire, vous pouvez consulter <a href="http://www.google.com/adsense/support/bin/answer.py?hl=fr&answer=12654" target="_blank">cette page d\'explications</a>.');?> </p> </div> <?php endif; ?></div><!-- /#content-area -->
Nous n'affichons cette variable $content que lorsque l'utilisateur est connecté au backend ou qu'il va à la page de connexion. Autrement, nous créons simplement une div#content-ajax. Cette div contient juste un texte de remplacement au cas où le navigateur de l'utilisateur n'a pas de JavaScript (et pourrait accueillir une solution de remplacement encore plus élaborée pour être plus accessible).
page-ajax.tpl.php
Nous préparons un second fichier de template que nous appelons "page-ajax.tpl.php". Ce template va servir à afficher nos requêtes Drupal normales, et c'est le résultat des ces requêtes qui sera chargé en AJAX dans la div#content-ajax du fichier page.tpl.php.
Grâce au système des template suggestions de Drupal, toutes les URL qui contiendront comme premier argument le mot "ajax" seront affichées avec ce template.
Attention : ce mot "ajax" doit faire partie de l'URL interne de Drupal ; il ne suffit pas de l'ajouter par une réécriture d'alias (du type de celle faite par Pathauto). Pour cela, il y a deux solutions :
- créer nos URL dans un module avec une fonction hook_menu ;
- utiliser le module Views. C'est cette deuxième solution que nous avons utilisée. Cf ci-dessous.
Notre fichier page-ajax.tpl.php est très simple. Il ne contient rien d'autre que :
<div id="content-raw"> </div>
Nous n'avons pas besoin de plus de code HTML que cela, car ce type de page ne sera jamais affiché directement à l'utilisateur.
Dans Views : des displays pour générer les contenus aux bonnes adresses URL
Selon la nature du contenu à afficher et en fonction des URL à générer, on construit différents displays dans Views. Voici ce que nous avons fait pour les noeuds de type "News" du site :
- un display de type "page" qui n'affiche à chaque fois qu'un seul contenu ;
- un argument : l'identifiant du noeud ("fournir l'argument par défaut" → "Identifiant du noeud à partir de l'URL") ;
- dans les paramètres de la page, le chemin suivant : ajax/news/% (le % sera remplacé par l'identifiant du noeud passé en argument) ;
- les champs que l'on veut afficher, avec les critères de tri et les filtres qui vont bien.
Définir des alias d'URL avec Pathauto pour récupérer les identifiants des noeuds
À chaque fois qu'un noeud est demandé par l'utilisateur sur le frontend, nous avons besoin de connaître son identifiant (nid pour les intimes) afin d'aller chercher le contenu de ce noeud à l'adresse que nous avons créée avec le display de Views à l'étape précédente. Il nous faut donc disposer de cet identifiant dans l'URL.
Cette URL sera visible par nos utilisateurs, contrairement à la précédente, et elle sera indexée dans Google. Elle a donc intérêt à être "sémantique", i.e. contenir des mots-clés pertinents et compréhensibles en plus du nid.
Nous avons donc défini le schéma de réécriture suivant dans Pathauto pour le type de contenu News : news/[title-raw]/[nid].
Tout relier avec jQuery
Prérequis : jQuery Address et jQuery 1.4.4
Appeler jQuery Address dans notre thème Drupal
La technique employée exploite un plugin jQuery, jQuery Address. Pour l'utiliser nous avons créé un dossier js/ dans le dossier du thème du site, et y avons rangé le fichier téléchargé à la page indiquée ci-dessus. Dans le fichier template.php de notre thème Drupal, nous avons ajouté la ligne suivante à notre fonction theme_preprocess_page() ("theme" est à remplacer par le nom de votre thème) :
(1.3.1 est la version que nous avons utilisée, elle peut avoir changé entretemps.)
NB La fonction drupal_add_js(), qui permet d'ajouter des fichiers JavaScript à la variable $scripts du fichier page.tpl.php, doit être suivie de la fonction drupal_get_js() pour fonctionner. Et si d'autres fonctions drupal_add_js() sont utilisées, elles doivent bien sûr précéder l'appel de la fonction drupal_get_js(). Nous avons donc, vers la fin de la fonction theme_preprocess_page(), la ligne suivante :
Une fois que le registre du thème Drupal aura été actualisé, notre fichier jQuery Address se sera ajouté à la liste des scripts chargés dans la balise <head> de page.tpl.php.
Mais… il ne pourra pas marcher parce que ce script requiert la version 1.4.4 de jQuery ; or Drupal 6 utilise une version plus ancienne.
Utiliser la version 1.4.4 de jQuery
Une solution serait de remplacer simplement le fichier dans le dossier misc/ de la racine de l'installation Drupal, mais cela signifierait de toucher au noyau, chose que le drupalien averti évite. Comme nous n'avons besoin de changer ce fichier que pour notre thème, nous ajoutons la ligne suivante dans notre fonction theme_preprocess_page(), après la ligne $vars['scripts'] = drupal_get_js(); :
$vars['scripts'] = str_replace("/misc/jquery.js", "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js", $vars['scripts']);
Ce str_replace() n'est probablement pas ce qu'il y a de plus élégant, mais nous n'avons pas trouvé mieux pour l'heure :-)
Donc jusqu'ici le code de notre fonction theme_preprocess_page ressemble à ceci :
<?php function yom_preprocess_page(&$vars, $hook) { $vars['scripts'] = str_replace("/misc/jquery.js", "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js", $vars['scripts']); } ?>
Le jQuery pour exécuter les requêtes AJAX
Nous voici au point d'orgue de tout l'édifice : le fichier qui va faire la correspondance entre le noeud demandé par l'utilisateur et l'adresse URL du contenu à charger en AJAX dans page.tpl.php.
Nous créons un fichier JavaScript appelé music.js que nous rangeons dans le dossier js/ de notre thème. Nous mettons à jour notre fichier template.php, pour appeler ce nouveau script :
function yom_preprocess_page(&$vars, $hook) { $vars['scripts'] = str_replace("/misc/jquery.js", "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js", $vars['scripts']); } ?>
Nous nettoyons le registre du thème de Drupal pour que ce nouveau script soit pris en compte.
Dans le fichier music.js, a première chose que nous faisons est d'ajouter le symbole # (dièse) dans toutes les URL auxquelles nos utilisateurs accèderont, juste après le nom de domaine (dans notre cas, juste après www.yom.fr/). Tout ce qui viendra après ce dièse sera interprété comme un deep link par notre script.
$(document).ready(function() { //On récupère l'URL de la page en cours avec l'objet window.location var urlToCheck = window.location; //On transforme cette variable en chaîne urlToCheck += ''; //On cherche si l'url contient le symbole dièse var dashPosition = urlToCheck.indexOf("#"); if(dashPosition == -1) { //Si il n'y a pas de dièse dans l'url (dashPosition vaut -1), on reconstruit l'url avec un dièse au début. //On crée un tableau dans lequel on passe chaque élément séparé par un slash dans l'URL. var deepLink = '/#'; //On complète notre nouvelle URL en ajoutant à la suite du dièse les arguments de l'url en cours. NB On commence à i = 3 parce que vu qu'il y a deux slash dans http:// les valeurs qui précèdent dans le tableau sont 0 : http:, 1 : (rien), 2 : le nom de domaine. for(i = 3; i < urlArray.length; i++) { deepLink += '/' + urlArray[i]; } //On exclue la racine du site avec et sans slash final et la page de connexion à Drupal if(urlToCheck != 'http://www.yom.fr/' && urlToCheck != 'http://www.yom.fr' && urlToCheck != 'http://www.yom.fr/user') { //autrement on applique notre transformation de l'URL document.location.href = deepLink; } }
Nous utilisons à présent les fonctions rendues disponibles par l'API de jQuery Address :
//La fonction ci-dessous va lire le deep link $.address.change(function(event) { // et permet de déclencher une action en fonction de la valeur de la propriété event.value, par ex. // $('#content').load(event.value + '.xml'); var ajaxUrl;//Va contenir l'URL du contenu à charger dans page.tpl.php var hash = event.value; //la valeur du deep link dans l'url... switch(hashArray[2]){//En fonction de la valeur du 3e argument du deep link on va indiquer à quelle URL aller chercher le contenu case "news"://si l'url contient news (on est sur un noeud de type "news") ajaxUrl = '../ajax/news/'+hashArray[4];//on récupère le nid pour trouver la bonne URL break; //On ajoute un cas default et undefined pour éviter les soucis default: case undefined: ajaxUrl = '../ajax/article/5'; //C'est notre page d'accueil, le contenu d'un article en l'occurrence. break; }// fin du switch //On ajoute un petit témoin de chargement var loading = '<div class="ajax-loader"><img src="/sites/default/files/ajax-loader.gif" /></div>'; //On charge ce témoin dans notre div#content-ajax $('#content-ajax').html(loading); //Enfin on charge le contenu de notre URL $.ajax({ url: ajaxUrl, success: function(data) { $('#content-ajax').html(data); } }); });
Et le tour est joué.
Et ça marche dans Google
Donc la bonne nouvelle, constatée après quelques jours de mise en production du site, c'est que Google indexe bien les différentes URL du site, comme on peut le voir dans son index.
Il semblerait même que ce genre de liens soit plutôt bon pour le référencement. C'est en tous cas la technique qu'a employé Deezer sur sa nouvelle version actuellement beta.
Là ou ça se complique ou comment ne pas réécrire tout le Drupal en JavaScript
Passé le HOURRAH de rigueur, on se rend compte que tout un tas de petits soucis surgissent (eh oui…) du fait que l'on a pas mal changé le mode de fonctionnement normal de Drupal. En effet, à partir du moment où seule une portion de la page change, on perd toutes sortes de fonctions automatiques et usuelles pour par exemple : ajouter une classe "active" sur le lien du menu de la page en cours, changer le contenu de la balise <title>, etc.
Comme dans notre cas, le site est petit et plutôt simple, nous avons compensé ces soucis en JavaScript. Ainsi par exemple, pour les classes actives, nous avons édité notre fichier music.js de la manière suivante :
case "news"://si l'url contient news (on est sur un noeud de type "news") ajaxUrl = '../ajax/news/'+hashArray[4];//on récupère le nid pour trouver la bonne URL //On ajoute et on enlève les classes actives avec les fonctions jQuery addClass() et removeClass(). $('#primary li').removeClass("isActive"); $('#primary li.menu-1160').addClass("isActive"); break;
Transformer tous les liens internes aux pages
Une autre chose importante à faire est d'appliquer notre système à tous les liens contenus dans les pages. Nous complétons donc notre fichier music.js de la manière suivante :
$('#content-area a').click(function() { $.address.value($(this).attr('href')); return false;//pour éviter que le lien non transformé ne soit suivi. }); $('#navigation a').click(function() { $.address.value($(this).attr('href')); return false; });
Il faut également ajouter ce script dans le fichier page-ajax.tpl.php, pour remplacer là aussi le fonctionnement normal des liens. Le fichier ressemble désormais à ceci :
<div id="content-raw"> </div> <script> $('#content-area a').click(function() { $.address.value($(this).attr('href')); return false; }); </script>
La suite au prochain épisode
Là où les choses risquent de se compliquer sera en particulier pour faire fonctionner le multilinguisme sur le site, ce qui risque de réclamer encore plus de JavaScript.
Il y a aussi du travail à faire côté accessiblité pour préserver une utilisation au moins basique du contenu pour les utilisateurs qui n'ont pas JavaScript.
Enfin, à plus long terme, on pourrait essayer de rendre ce code utilisable sous forme de module "contrib" sur le site Drupal, sans doute en allant regarder de plus près ce qui a été fait pour intégrer SWFAddress, qui est est une API de deep linking pour Flash/Flex, et dont JQuery Address est une adaptation récente.
Commentaires
Merci pour cet article
Manifico
Avec plaisir
Résolu
C'est un peu tordu en effet
Bravo pour ta solution. C'est vrai que c'est un peu tordu, ça demande de changer ses habitudes sur différents points. Pour ce qui est des commentaires "ajaxés", je ne suis pas sûr de ce que tu cherches exactement. Il y a un module Ajax Comments sur drupal.org : http://drupal.org/project/ajax_comments, encore en dév pour Drupal 7, qui permet notamment la création de commentaire sans quitter la page en cours. Je ne sais pas si ça répond à ta question.
Amicalement, Sébastien
Wai ajax_comments permet de
Poster un nouveau commentaire