Publié par

Il y a 9 ans -

Temps de lecture 8 minutes

NoThunes, AJAX-ification sauce Grails

Projet NoThunes

Retour sur notre plateforme de musique en ligne préférée. Comme promis, dans ce chapitre, nous allons ajouter une pincée de 2.0 à notre application. Nous allons donc nous pencher sur la mise en oeuvre d’AJAX à la mode de Grails.

  • Listes déroulantes dynamiques avec JSON
  • Mise à jour de fragments de page avec modèles GSP

Vous allez pouvoir découvrir l’intégration d’AJAX dans Grails, et à quel point la vie du développeur est facilitée. Les exemples d’utilisation sont simples mais déclinables à l’infini.

Sprint Backlog détaillé

En tant que … je dois pouvoir … détail …
membre Créer/modifier/supprimer des Tracks liés à mes albums, en uploadant un fichier mp3 CRUD standard avec upload de fichier.
internaute naviguer dans la hiérarchie de groupes/albums/morceaux existante Dans une même page, on offre la sélection par groupe, album et morceaux. Si un groupe est sélectionné, on filtre les albums et les morceaux. Si un album est sélectionné, on filtre les morceaux.
internaute télécharger un morceau Dans la navigation offerte aux internautes, on doit disposer d’un lien pour télécharger le fichier mp3 associé à un Track.

Gestion des Tracks

Nous sautons tout ce qui concerne la mise en place du CRUD et de la gestion de l’upload de fichier mp3. La méthode employée est foncièrement identique au travail déjà fait pour les classes Band et Album. Comme d’habitude le détail est dans le GitHub.

Attardons nous, par contre, sur un point particulier du formulaire de création de Track : l’attribution à un Album. Chaque Track appartient à un Album (par un lien belongsTo). Pour une meilleure ergonomie, nous avons choisi de proposer à l’utilisateur de sélectionner d’abord un Band dans un <select ../> et, dynamiquement, nous proposons les Albums de ce groupe dans un second <select ../>. Enfin un peu d’AJAX dans notre projet !

Pour ceux qui n’ont pas fait de veille techologique depuis plusieurs années, voici un petit rappel sur AJAX.

Nous commençons par outiller nos pages avec ce qu’il faut de Javascript :

grails-app/views/layouts/main.gsp


    
...
      
      
    
...

Ces tags ajoutent dans toutes les pages l’import de la librairie Prototype, fournie par défaut avec Grails, et du fichier web-app/js/application.js. Ce dernier est destiné à recevoir le code Javascript spécifique à notre appli. Ensuite nous créons une méthode dans notre BandController pour renvoyer, au format JSON, la liste des albums appartenant au groupe dont l’id est passé en paramètre :

grails-app/controllers/fr/xebia/nothunes/domain/BandController

...
// Pensez bien à faire cet import
import grails.converters.*
...
def ajaxGetAlbums = {
   render Band.get(params.id).albums as JSON
}

Comme on peut le voir le package grails.converters nous offre une méthode plus que simple pour la conversion d’objets en JSON. A noter que, dans la même famille il est possible d’utiliser as XML.

Maintenant il faut que nous lancions un appel AJAX vers cette méthode, lors de la sélection d’un groupe. Grails a prévu pour nous la méthode remoteFunction qui va générer le Javascript nécessaire.

grails-app/views/tracks/create.gsp

...

  
    
  
  
    
  

...

Les paramètres de remoteFunction sont assez explicites :

  • controller, action : le contrôleur et l’action ciblée
  • params : les paramètres de la requête
  • onComplete : la méthode javascript callback de l’appel AJAX

Il ne reste plus qu’à rédiger la méthode Javascript pour mettre à jour le select des Albums :

web-app/js/application.js

function updateSelectAlbumWithJSON(e, albumListId) {

   var rselect = $(albumListId)
   
   // Vidage du select
   var l = rselect.length
   while (l > 0) {
      l--
      rselect.remove(l)
   }

   // Evaluation du JSON retourné par le serveur
   var albums = eval("(" + e.responseText + ")")
   
   if (albums) {
   // Remplissage du select avec les nouvelles options
      addOption(rselect, '---Choose an album---', '')
      
      for (var i=0; i < albums.length; i++) {
         addOption (rselect, albums[i].name, albums[i].id)
      }
   }
}

function addOption(selectId, text, value) {
   var opt = new Element('option');
   opt.text = text
   opt.value = value
   
   try {
      selectId.add(opt,null) // Standard, ne fonctionne pas avec IE
   }
   catch(ex) {
      selectId.add(opt) // seulement pour IE
   }
}

En mode développement, c'est certainement superflu, mais au cas où le listing des albums deviendrait un peu long, il est possible d'ajouter un gif d'attente. Cette image est gracieusement fournie par Grails. Ça n'a l'air de rien, d'ailleurs ce n'est rien, mais ça fait plaisir quand même ! :-)
Juste à coté du tag <g:select/> des Albums on peut ajouter un petit :

grails-app/views/tracks/create.gsp

${message(code:'spinner.alt',default:'Loading...')}

Et dans le javascript, ajouter du $('waitingGif').show() et $('waitingGif').hide(). Ca fait toujours plus joli après tout.

create_track

Ça y est, pas besoin de plus ! Pour la page d'édition d'une instance existante, la méthode est pratiquement la même. il faut juste ajouter dans le trackcontroller le chargement de la liste des albums pour que les 2 select soient remplis au chargement. les gentils membres ont maintenant toutes les fonctionnalités pour alimenter notre plateforme de musique en contenu dûment étiquetté.

Il faut maintenant penser aux internautes moyens, avides de pouvoir accéder au contenu des membres. Nous allons donc attaquer la navigation publique. Pour éviter de surcharger les contrôleurs existant, nous créons un NavigationController, qui sera dédié à la page de navigation destinée aux internautes lambdas, ainsi qu'une page GSP associée à l'action list (donc dans le fichier grails-app/views/navigation/list.gsp).

La sélection d'un Band doit déclencher le filtrage de la liste des Albums et des Tracks, et la sélection d'un Album doit déclencher le filtrage de la liste des Tracks uniquement. Le filtrage des Albums est identique au cas précédent, puisque c'est un select. Le tableau des Tracks, un peu plus riche, mérite qu'on s'y attarde un peu.

Le principe est globalement le même, à ceci près qu'on va utiliser un modèle GSP pour créer le fragment HTML qui servira à remplacer dynamiquement la liste des Tracks. Zoomons un peu sur la mécanique de notre page list.gsp :


   
      
      
         // Fonction de filtrage du select des Albums et de la liste des Track, à partir d'un id de Band
         function filterByBand() {
            var bandId = $F('bandSelect');
            
            
         }

         // Fonction de filtrage de la liste des Track, à partir d'un id d'Album
         function filterByAlbum() {
            var albumId = $F('albumSelect');
            
         }

         // Fonction de mise à jour de la liste des Track avec la réponse (au format HTML) de la requête AJAX.
         function updateTrackList(e) {
            $('navTrack').innerHTML = e.responseText;
         }

         // Fonction de mise à jour du select des Albums avec la réponse (au format JSON) de la requête AJAX.
         function updateAlbums(e) {
            // Le code est identique à l'exemple donné pour la 
            // mise à jour de select du paragraphe précédent
         }
	
      
   
   
      

Navigation

Dans le tag <g:render .../> en fin de l'exemple, on voit qu'on fait appel au modèle GSP qui contient la liste formatée des Tracks. Passons à sa rédaction. Il faut penser à nommer le fragment qui servira de modèle en commençant par un caractère '_', c'est la convention. Notez que dans la colonne Download on réutilise le DlController créé à l'épisode 2.

grails-app/views/navigation/_trackList.gsp


         
${message(code: 'track.name.label', default: 'Name')} ${message(code: 'band.name.label', default: 'Band')} ${message(code: 'album.name.label', default: 'Album')} Download
${fieldValue(bean: trackInstance, field: "name")} ${fieldValue(bean: trackInstance, field: "album.band.name")} ${fieldValue(bean: trackInstance, field: "album.name")}

Enfin, dans notre méthode du contrôleur on pourra faire usage de render comme ceci :

grails-app/controllers/fr/xebia/nothunes/domain/NavigationController.groovy

...
   def ajaxFilterTrackByAlbum = {
      
      def trackList = Track.withCriteria {
         ne('audioPath', 'empty')
         
         if (params.id) {
            album {
               eq('id', new Long(params.id))
               if (params.band) {
                  band {
                     eq('id', new Long(params.band))
                  }
               }
            }
         }
      }
      
      render (view:'_trackList', model: [trackList: trackList])
   }
...

Ça y est nos internautes peuvent naviguer dans les morceaux uploadés par nos gentils membres, et les télécharger à leur guise.

Sprint Review

Nous avons pu voir dans cet épisode que la mise en place de méthodes AJAX est relativement intuitive et rapide. Les 2 cas décrits ici sont déclinables à l'infini pour répondre à beaucoup de besoins. L'API Grails est correctement documentée, et la librairie Prototype embarquée par défaut offre des outils plus que suffisant pour manipuler le DOM de la page. Pour ceux qui préférent, il existe les plugins JQuery et JQuery-UI pour installer en une commande tous les fichiers au bon endroit.

Le programme du prochain épisode :

En tant que ... je pourrai ...
internaute écouter un morceau
internaute rechercher des morceaux par mots-clés

Stay (no)tuned ...

Ressources

Publié par

Publié par Aurélien Maury

Aurélien est passionné par les frameworks web haute productivité comme Grails, JRuby on Rails ou Play! framework. Il est également intéressé par tous les aspects de performance et d'optimisation (mais aussi par la phytothérapie, la PNL, la basse électrique, la philosophie et pleins d'autres sujets). Il est également formateur au sein de Xebia Training .

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.