3ème et dernier article sur Grails après une prise en main et la réalisation d’un écran complet, nous allons voir comment créer un tag pour Grails et comment mettre en place Ajax. En bonus et pour débuter je vais vous montrer aussi quelques fonctionnalités de Grails : le Bootstraping et les fonctions orientées REST pour afficher la liste des Contacts au format XML ou JSON en quelques minutes.

Bootstrapping

Avant tout, vous pouvez télécharger le code source de la partie 2 si vous ne l’avez pas déjà fait. Avez-vous remarqué qu’à chaque démarrage de l’application, il n’y a aucuns contacts ? C’est un peu gênant car il faut créer quelques Contacts pour voir la liste s’afficher. Grails en mode développement utilise une base de données en mémoire qui n’est pas persistée. A chaque démarrage il est donc normal que la liste des Contacts soit vide.

Pour créer quelques enregistrements, ouvrez le fichier /grails-app/conf/BootStrap.groovy. Ce script est exécuté lors du démarrage. Nous allons créer quelques Contacts afin de tester. Rien de plus simple avec Grails, et vous verrez qu’il n’y a pas besoin de services, tout est livré sur l’entité Contact. Voici comment ajouter les contacts : dans la méthode init, créer un nouveau Contact :

class BootStrap {
     def init = { servletContext ->
          def nm = new Contact(prenom: "Nicolas", nom: "Martignole", dateNaissance: new Date()-120, email: "nicolas@test.fr")
          nm.save()

     }
     def destroy = {
     }
}

En Groovy il est possible d’utiliser ce que l’on appelle des arguments nommés. Franchement, vous ne trouvez pas cela plus clair que la version Java ? Là les paramètres sont clairement exprimés, sans ambiguïté. Notez aussi au passage qu’il est possible de retirer 120 jours à la date courante, car une Date en Groovy est facilement manipulable. C’est de la surcharge d’opérateur comme au bon vieux temps du C++.

Si vous arrêtez et vous relancez le serveur, vous devriez voir maintenant que votre liste d’utilisateur s’est remplie :
grails_etape3_debut

Je vous laisse ajouter quelques utilisateurs, vous avez compris le principe.

Sur votre version de production, il faudra ajouter un test afin de vérifier si l’application est en mode développement ou en mode production. Sans cela, ces entrées seront créées à chaque démarrage. Si vous voulez simplifier la saisie de la date de naissance, vous pouvez modifier la class Contact afin de rendre la date de naissance optionnel avec l’option nullable=true et retirer la contrainte max que j’avais placé dans la première version. Cela permettra de créer rapidement quelques enregistrements pour tester.

La class Contact modifiée :

package org.letouilleur.demo

class Contact {
	String nom
	String prenom
	Date dateNaissance
	String email

	static constraints={
		prenom(blank:false)
		nom(blank:false)
		dateNaissance(nullable:true)
		email(email:true,nullable:true,blank:true,unique:true)
	}
}

Voici mon fichier Bootstrap final :

import org.letouilleur.demo.Contact
import javax.servlet.ServletContext

/**
 * Script de demarrage
 */
class BootStrap {

  def init = {ServletContext ctx ->

    environments {
      production {

        ctx.setAttribute("env", "prod")
      }
      development {
        createTestEntries()
        ctx.setAttribute("env", "dev")
      }
    }


  }

  def destroy = {
  }

  // Cette methode est appele uniquement en mode developpement afin de creer des Contacts

  void createTestEntries() {
    def nm = new Contact(prenom: "Nicolas", nom: "Martignole", dateNaissance: new Date() - 120, email: "nicolas@test.fr")
    nm.save()

    // Permet de verifier que le Contact est bien stocke ou non
    def c = new Contact(prenom: "Paul", nom: "Gontran", dateNaissance: new Date(), email: "paul@toto.fr")
    c.save()
    if (c.hasErrors())
      println c.errors

    new Contact(prenom: "Georges", nom: "Lucas", dateNaissance: new Date() - 1, email: "lucas@star.fr").save(failOnError: true)
    new Contact(prenom: "Gregory", nom: "Madisson", dateNaissance: new Date() - 3, email: "mads@son.com").save()
    new Contact(prenom: "Jonathan", nom: "Lalou", dateNaissance: new Date() - 2, email: "john@lecowboy.fr").save()
    new Contact(prenom: "Katherine", nom: "Grah", dateNaissance: new Date() - 3, email: "kat@noop.fr").save()
    new Contact(prenom: "Amelie", nom: "Blou", dateNaissance: new Date() - 56, email: "amelie@noop.fr").save()
    new Contact(prenom: "Anatole", nom: "Ducib", dateNaissance: new Date() - 23, email: "am@noop.fr").save()
    new Contact(prenom: "Kim", nom: "ProdMan", dateNaissance: new Date() - 4, email: "kim@noop.fr").save()
    new Contact(prenom: "David", nom: "CeChau", dateNaissance: new Date() - 91, email: "david@noop.fr").save()
    new Contact(prenom: "Pierre", nom: "Henry", dateNaissance: new Date() - 3, email: "pierre@noop.fr").save()
    new Contact(prenom: "Andre", nom: "Magphone", email: "ant@noop.fr").save()
  }
}

Générer une liste au format XML ou JSON
Certaines fonctions de Grails sont vraiment sympas. Nous allons voir comment retourner la liste des contacts au format XML simple, au format avancé et enfin au format JSON. Pour cela, éditez ContactController. Vous savez maintenant que pour déclarer une nouvelle action, il suffit de créer une méthode. Grails rend accessible cette méthode sans même qu’il ne soit nécessaire de relancer le serveur. Essayez d’ajouter ces quelques lignes à la fin de votre fichier :

  def listAsXml = {
    def newList = Contact.listOrderByNom()
    render newList as grails.converters.XML
  }

Si vous regardez maintenant la page http://localhost:8080/zencontactdemo/contact/listAsXml vous verrez que Grails génère une liste triée par nom de famille des Contacts.
grails_list_as_xml_converter

Grails propose les Converters. Il en existe 4 par défaut (voir la doc dans une autre fenêtre). Les converters « deep » gérent les associations, les relations alors que les 2 autres Converters ne rendent pas les feuilles. Ici Contact n’ayant aucunes relations, j’utilise le Converter par défaut.

Il est aussi possible de générer un fichier XML très facilement par exemple:


def converter = Contact.list() as XML
converter.render(new java.io.FileWriter("/path/to/my/file.xml"));

Générer la liste des Contacts au format JSON
Le converter JSON par défaut gére en plus très bien l’encoding au format UTF-8 des entités, ce qui nous permettra de gérer sans soucis les accents par exemple :

import grails.converters.JSON
class ContactController {
..
...

 def listAsJson = {
    def newList = Contact.listOrderByNom()
    render newList as JSON
  }

Ce qui donne

[{"class":"org.letouilleur.demo.Contact","id":7,"prenom":"Amelie","dateNaissance":"2009-11-04T18:12:31Z","email":"amelie@noop.fr","nom":"Blou"},{"class":"org.letouilleur.demo.Contact","id":6,"prenom":"Katherine","dateNaissance":"2009-12-27T18:12:31Z","email":"kat@noop.fr","nom":"Blou"},
...
etc.

Un seul enregistrement au format XML
Enfin si vous ne voulez retourner qu’un seul enregistrement, cela ne poste pas plus de problèmes. Voici une méthode showAsXml qui affichera un seul des enregistrements. Il suffit de passer l’id en argument.


  def showAsXml = {
    def contactInstance = Contact.get(params.id)
    if (!contactInstance) {
      flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'contact.label', default: 'Contact'), params.id])}"
      redirect(action: "list")
    }
    else {
       render contactInstance as XML
    }
  }

Et pour tester cette méthode (toujours sans relancer Grails) ouvrez par exemple cette URL :
http://localhost:8080/zencontactdemo/contact/showAsXml?id=7

Voilà, vous savez maintenant que Grails peut exposer facilement au format XML ou JSON des entités.

Le tag editInLine

Là nous attaquons une partie un peu plus compliquée. Dans le cahier des charges de Zenika, il faut mettre en place une fonction d’édition du nom de famille en Ajax, en utilisant la fonction Ajax.InPlaceEditor de la librairie Javascript Scriptaculous. J’ai un peu cherché mais il ne semble pas qu’il existe de tag Grails pour faire cela. Pas de plugins non plus. Enfin cela tombe bien, l’objectif est de vous montrer comment écrire ce tag.
Mais avant cela, voici une petite vidéo du résultat final, de ce que vous allez coder dans un instant (désolé si vous n’avez pas Quicktime) :

Pour réaliser cela voici ce que nous allons faire:
– créer un tag personnalisé dans la class ToolsTagLib
– modifier le fichier list.gsp pour utiliser ce tag
– ajouter une méthode dans ContactController pour sauver le changement de nom
– ajouter une page gsp pour afficher en gras le nom édité

Le tout sans relancer le serveur.

Avant tout, vérifiez bien que le fichier grails-app/views/layout/main.gsp contient bien la déclaration de la librairie Prototype et de la librairie Scriptaculous.

<html>
<head>
  <title><g:layoutTitle default="Zencontact"/></title>
  <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}"/>
  <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon"/>
  <g:layoutHead/>
  <g:javascript library="prototype"/>
  <g:javascript library="scriptaculous"/>
</head>

Création d’un tag personnalisé
Lors du premier atelier nous avons créé une class ToolsTagLib.groovy dans le répertoire grails-app/taglib. Nous allons ajouter une nouvelle méthode dans cette classe afin de créer un tag editInPlace pour Grails. J’ai décidé que ce tag prendrait quelques arguments. Dans la page list.gsp, remplacez la ligne suivante utilisée lors de l’affichage du nom du contact par le code suivant :

Dans grails-app/views/contact/list.gsp

effacez la ligne
 <td>${fieldValue(bean: contactInstance, field: "nom")}</td>

et remplacez par :

<td>
     <g:editInPlace id="${contactInstance.id}" action="inlineEdit" controller="contact" paramName="nom" initialValue="${contactInstance.nom}"/>
</td>

Notre tag prend plusieurs arguments. L’id du contact édité, l’action à appeler sur le controlleur, le nom de celui-ci, le nom du parametre initial et enfin la valeur à afficher lors du chargement de la page.

Je vous donne le code du tag, avant de l’expliquer. C’est pas très simple, j’y ai passé une bonne demie heure le temps de le tester et de trouver comment récupérer les paramètres facilement :

def editInPlace = {attrs, body ->
    def id = attrs.remove('id')
    def initialVal = attrs.remove('initialValue')

    out << "<span id='${id}' class='editInline'>"
    out << initialVal
    out << "</span>"

    out << "<script type='text/javascript'>"
    out << "new Ajax.InPlaceEditor('${id}', '"

    // id has alread been removed from attrs map
    out << createLink(action: attrs.action, id: id, controller: attrs.controller)

    out << "',{"

    if (attrs.paramName) {
      out << "callback: function(form,value) { return '${attrs.paramName}=' + encodeURIComponent(value) }  "
    }
    out << "}); "
    out << "</script>"
  }

Ligne 2 et 3 : je retire les parametres passés en argument du tag dans la page GSP et je les stocke dans des variables
Ligne 13: j’utilise la fonction createLink de Grails qui est présente par défaut pour créer un lien
Ligne 17: je précise aussi le nom du parametre

Le tout repose sur la fonction Ajax.InPlaceEditor qui permet facilement de gérer l’édition. C’est cette même librairie qui est utilisée dans la version de Zenika. Exactement la même. La différence étant que là, au lieu d’avoir un composant, je dois coder un tag.

Modification de la page list.gsp
Editez et vérifiez que la page list.gsp utilise maintenant votre tag. Nous avons maintenant la version finale de cette page. Regardez attentivement le code, c’est relativement simple.

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta name="layout" content="main"/>
  <g:set var="entityName" value="${message(code: 'contact.label', default: 'Contact')}"/>
  <title><g:message code="default.list.label" args="[entityName]"/></title>

</head>
<body>

<div class="body">
  <g:if test="${flash.message}">
    <div class="message">${flash.message}</div>
  </g:if>

  <P>Sur cette page vous pouvez éditer une fiche, ajouter rapidement un nouvel utilisateur avec un formulaire Ajax. Nous
  montrons aussi comment il est possible d'inclure une autre vue dans la vue active sans ajouter une ligne de code
  au controleur.</P>
  <P>Vous pouvez éditer directement le nom de famille d'un utilisateur en cliquant sur son nom grâce à la librairie Scriptaculous.</P>

  <div id="updateMe">
    <g:each in="${contactInstanceList}" status="i" var="contactInstance">
      <img src='<g:resource dir="images" file="people.png"/>' alt="icon"/>
      ${contactInstance.prenom}

      <!-- Exemple d'utilisation d'un tag custom -->
      <g:editInPlace id="${contactInstance.id}" action="inlineEdit" controller="contact" paramName="nom" initialValue="${contactInstance.nom}"/>

      <g:link action="show" id="${contactInstance.id}">
        <img src="${resource(dir: 'images', file: 'edit.png')}" alt="edit" border="0"/>
      </g:link>
      <br/>
    </g:each>
    <div class="paginateButtons">
      <g:paginate total="${contactInstanceTotal}"/>
    </div>

  </div>

</div>

<div class="contact">
  <g:include action="create" controller="contact"/>
</div>
</body>
</html>

Nous retrouvons bien notre tag à la ligne 27, et aussi ce que nous avions préparé dans l’article précedent. Il ne manque plus que l’édition avec le formulaire Ajax et nous aurons terminé.

Ajout d’une méthode pour sauver le nouveau nom
Si vous testez le code à cet instant, vous verrez que cela marche pas. Le nom de famille n’est pas persisté. Il manque encore 2 choses : une méthode dans ContactController et une vue GSP. Le nom de la méthode appelée par le code Ajax.EditInPlace a été passé en argument : inlineEdit. Il faut donc créer cette méthode et ensuite une vue inlineEdit.gsp pour terminer cette partie. Voici la nouvelle méthode à ajouter à la fin de ContactController :

def inlineEdit = {
    def contactInstance = Contact.get(params.id)
    if (!contactInstance) {
      flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'contact.label', default: 'Contact'), params.id])}"
      redirect(action: "list")
    }
    else {
      contactInstance.nom=params.nom
      contactInstance.save()
      return [contactInstance: contactInstance]
    }
  }

Retenez que seul la ligne 8 est intéressante : nous modifions le nom de l’entité chargée et nous la sauvons ensuite.

Il ne reste plus qu’à ajouter un fichier inlineEdit.gsp dans le répertoire grails-app/views/contact avec cette seule et unique ligne:

// Une seule ligne
<strong>${contactInstance.nom}</strong>

Une seule ligne car cette page est rendue… via Ajax. Il n’y a rien d’autres à ajouter.

Voilà c’est terminé pour l’édition. Regardez la vidéo complète à la fin de cet article si vous souhaitez faire une pause avant la dernière partie.

Dernière modification : le formulaire de saisie rapide Ajax
Tout d’abord nous allons modifier la vue contact.gsp car nous n’avons pas encore activé de datePicker. Et comme la vue create est utilisée aussi dans la vue list, autant faire d’une pierre, deux coups. Pour le datePicker nous avons l’embarras du choix car il existe plusieurs plugins Grails. J’ai pris le plugin « Calendar« . Allons-y, il y en a pour 3 minutes. Tout d’abord installons le plugin

grails install-plugin calendar
 

Voici la version finale de la page create.gsp avec la mise en page de la version Wicket de Zenika :

<%@ page import="org.letouilleur.demo.Contact" %>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta name="layout" content="main"/>
  <g:set var="entityName" value="${message(code: 'contact.label', default: 'Contact')}"/>
  <title><g:message code="default.create.label" args="[entityName]"/></title>
  <calendar:resources/>
</head>
<body>

<div class="body">
  <g:if test="${flash.message}">
    <div class="message">${flash.message}</div>
  </g:if>
  <g:hasErrors bean="${contactInstance}">
    <div class="errors">
      <g:renderErrors bean="${contactInstance}" as="list"/>
    </div>
  </g:hasErrors>
  <g:form action="save" method="post">
    <div class="dialog">
      <table>
        <tbody>

        <tr class="prop">
          <td valign="top" class="name">
            <label for="prenom"><g:message code="contact.prenom.label" default="Prenom"/></label>
          </td>
          <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'prenom', 'errors')}">
            <g:textField name="prenom" value="${contactInstance?.prenom}"/>
          </td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">
            <label for="nom"><g:message code="contact.nom.label" default="Nom"/></label>
          </td>
          <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'nom', 'errors')}">
            <g:textField name="nom" value="${contactInstance?.nom}"/>
          </td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">
            <label for="dateNaissance"><g:message code="contact.dateNaissance.label" default="Date Naissance"/></label>
          </td>
          <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'dateNaissance', 'errors')}">
            <!-- utilisation du plugin calendar, a installer avec grails install-plugin calendar -->
            <calendar:datePicker name="dateNaissance" defaultValue="${contactInstance?.dateNaissance}" dateFormat="%d/%m/%Y"/>
          </td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">
            <label for="email"><g:message code="contact.email.label" default="Email"/></label>
          </td>
          <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'email', 'errors')}">
            <g:textField name="email" value="${contactInstance?.email}"/>
          </td>
        </tr>
        </tbody>
      </table>
    </div>
    <div class="buttonbox">
      <g:submitButton name="create" class="button" value="${message(code: 'default.button.create.label', default: 'Create')}"/>
    </div>
  </g:form>
</div>
</body>
</html>

Ligne 8 : cette déclaration permet de charger les ressources javascript nécessaires au plugin calendar.
Ligne 48: utilisation du datePicker

Si vous rechargez maintenant la page de création nous avons un datePicker en bonne et due forme. De même pour la page qui liste les contacts car celle-ci importe la page create.gsp.

Passons maintenant à l’ajout du formulaire de saisie rapide à proprement parler.

Grails fournit un support d’AJAX très puissant et simple à utiliser. La librairie des tags de base permet de réaliser rapidement des fonctions puissantes en Javascript, tout en assurant l’envers du décor avec les actions dans le Controller. En fait c’est tellement simple que vous n’aurez pas le temps de terminer votre café en lisant ce qui suit :

Dans list.gsp, ajoutez un nouveau formulaire basé sur le tag formRemote:

<div id="updateMe">
    <g:each in="${contactInstanceList}" status="i" var="contactInstance">
      <img src='<g:resource dir="images" file="people.png"/>' alt="icon"/>
      ${contactInstance.prenom}

      <!-- Exemple d'utilisation d'un tag custom -->
      <g:editInPlace id="${contactInstance.id}" action="inlineEdit" controller="contact" paramName="nom" initialValue="${contactInstance.nom}"/>

      <g:link action="show" id="${contactInstance.id}">
        <img src="${resource(dir: 'images', file: 'edit.png')}" alt="edit" border="0"/>
      </g:link>
      <br/>
    </g:each>
    <div class="paginateButtons">
      <g:paginate total="${contactInstanceTotal}"/>
    </div>
</div>

<div>
    <g:formRemote name="quickSave"
                                action="saveQuickInput"
                                on404="alert('not found!')"
                                update="updateMe"
                                url="${[action:'saveQuickInput']}">
      Prénom : <g:textField name="prenom" value="${contactInstance?.prenom}"/>
      Nom :   <g:textField name="nom" value="${contactInstance?.nom}"/>

      <g:submitButton name="create" class="button" value="Créer"/>

    </g:formRemote>
</div>

Ligne 1 : vous notez que j’ai nommé le DIV principal « updateMe » qui comme vous l’avez deviné… doit se rafraîchir dès qu’un contact est ajouté.
Ligne 21 : nom de l’action à appeler sur ContactController
Ligne 23 : nom du DIV à rappeler en cas de succès

Le reste du formulaire est tout à fait normal, il n’y a rien d’exceptionnel. Il ne reste plus qu’à ajouter une nouvelle action dans ContactController. Je vous laisse faire maintenant, vous savez comment faire :

 def saveQuickInput = {
    params.max = Math.min(params.max ? params.max.toInteger() : 10, 100)

    def contactInstance = new Contact(params)

    def existingUser = Contact.findByNomAndPrenom(params.nom, params.prenom)
    if (!existingUser) {

      contactInstance.save(flush: true)
      [contactInstanceList: Contact.listOrderByNom(params), user: contactInstance, contactInstanceTotal: Contact.count()]
    } else {
      [contactInstanceList: Contact.list(params), user: contactInstance, erreur: "Cet utilisateur existe déjà"]
    }
  }

Et enfin comme toute action, il nous faut une vue. Je dis cela, ce n’est pas tout à fait vrai. Il est possible d’appeler une autre vue et vous n’êtes pas obligé de créer un fichier GSP par action dans votre Controller. Pour terminer voici le contenu de la page qui sera affichée lorsque l’utilisateur aura saisi un nouveau Contact :

<div class="errors">
  ${erreur}  <br/>
</div>

<g:if test="${flash.message}">
  <div class="errors">${flash.message}</div>
</g:if>

<g:each in="${contactInstanceList}" status="i" var="contactInstance">
  <img src='<g:resource dir="images" file="people.png"/>' alt="icon"/>

  <g:if test="${contactInstance.nom == user.nom}">
    <strong>
  </g:if>
  ${contactInstance.prenom}

  <!-- Exemple d'utilisation d'un tag custom -->
  <g:editInPlace id="${contactInstance.id}" action="inlineEdit" controller="contact" paramName="nom" initialValue="${contactInstance.nom}"/>

  <g:if test="${contactInstance.nom == user.nom}">
    </strong>
  </g:if>

  <g:link action="show" id="${contactInstance.id}">
    <img src="${resource(dir: 'images', file: 'edit.png')}" alt="edit" border="0"/>
  </g:link>
  <br/>
</g:each>
<div class="paginateButtons">
  <g:paginate total="${contactInstanceTotal}" controller="contact" action="list" next="Suivant" prev="Précedent"/>
</div>

<div class="errors">
  ${erreur}  <br/>
</div>

La pagination sur la page de résultat pose un souci : si vous créez un utilisateur appelé Bob Zorro par exemple, celui-ci sera affiché sur la 2ème page et vous ne le verrez pas tout de suite… Je pense que c’est un souci d’ergonomie plus que technique, donc nous n’en tiendrons pas compte ici.

Voilà c’est terminé. Nous avons maintenant aussi le formulaire de création rapide. Si vous avez tout suivi jusqu’ici… chapeau. Cet article est énorme à lire, et il doit tenir sur 12 pages. Je vous remercie si vous êtes encore là, car tout ceci peut vous sembler un peu indigeste.

Conclusion

Pour terminer voici une vidéo de l’application complète, avec une feuille de style adaptée, quelques améliorations sur la mise en page, mais le tout sans trucages, sans fil en nylon qui font bouger les tags sur la page et en essayant de coller à l’esprit du projet de Zenika au plus juste. Pour les observateurs attentifs il y a un bug à un moment donné… je vous laisse regarder :

Alors Grails c’est mieux que Wicket ?
Il est temps de poser un peu le clavier et de porter une autocritique sur le projet. Là où j’ai clairement gagné du temps : sur tout ce qui est test, construction des actions et de la partie Controller. Là où j’ai bien passé 1 heure, c’est sur la création du tag personnalisé et sur la gestion de la partie Ajax. Pas compliqué, mais ne connaissant pas… j’ai un peu navigué dans la documentation de Grails pour boucler le tout. Je me demande combien de temps il me faudrait pour refaire le projet en partant de zéro, en connaissant ce qu’il y a à faire… Je dirai 1h00 je pense, en imaginant que je conserve le code du tag editInPlace quelque part. Le reste est faisable rapidement.

Dans vos commentaires vous me demandez si Grails est mieux que GWT. Ils ne sont pas comparables sur le plan de la création d’application. GWT est fait pour la création d’application riche du côté du navigateur. Je préfère comparer GWT à Flex. GWT vous laisse écrire la partie serveur avec la persistence gérée par un ORM comme Hibernate, et l’injection de dépendance gérée par Spring par exemple. Grails ne vous laisse pas mettre le nez dans cette partie, ce qui je pense doit avoir ses limites. Vous êtes tributaire des mises à jour des versions de Spring et d’Hibernate puisque celles-ci font parties intégrantes de Grails… D’un autre côté, je vois cela comme un avantage : Grails vous prépare une version qui marche des différentes librairies, et tant que vous n’avez pas un bug dans celle-ci, vous êtes tranquille.

Peut-on utiliser Grails en production ?
Oui. Souvenez-vous qu’à la fin, vous faîtes « grails war » et votre archive est prête à être déployée comme vous l’auriez construit à la main. SpringSource fournit aussi du support il me semble sur Grails d’après cette page mais difficile à dire.

Est-ce que Grails est réservé à des petites applications ou des maquettes ?
Je ne pense pas. Si je suis votre raisonement, dès que votre application devient « sérieuse » il vous faut Java EE 6 ? C’est pas un peu bête comme raisonnement ? Je suis certain que vous pensez que PHP c’est « pour des petits sites » et Python « c’est pour des gars qui n’aiment pas Java« .
Excusez-moi de vous parler franchement : va falloir sortir un peu mon garçon. La majorité des projets Webs aujourd’hui tournent sur du PHP. Python a des Closures depuis 2004 je crois là où nous, dans le monde Java, nous n’aurons rien avant fin 2010… Coluche disait : « JE ME MARRE ! »

Est-ce que Grails c’est vraiment de la balle et tu es prêt à mettre ton projet dessus ?
Un autre Nicolas m’a posé cette question. Est-ce que je suis prêt à risquer un projet, une entreprise en l’occurrence, sur ce framework ? Ma réponse est oui. Je limite le risque en prenant un outil qui me donne un feedback plus rapidement qu’avec Java. Cela me permet de valider rapidement ou non des idées. Et dans le cadre d’une start-up où vous devez trouver des idées, je suis vraiment content de ce choix. Je commence à voir les limites de Grails, mais aussi à voir les parties où il ne faut pas hésiter à se servir deux fois.

Et Groovy ? Et la suite ?
Depuis mi-2009 j’ai changé. Je dirai presque que j’ai muri : je sens de moins en moins l’option « Java seul contre le reste du monde« . Je pense qu’il est intéressant pour vous de commencer à apprendre Scala ou Groovy en ce moment, bref n’importe quel langage qui tourne sur la JVM.
La force de Java ce n’est plus le langage. C’est la communauté, c’est les projets open-sources de qualité et c’est la machine virtuelle.

Play! ou Grails ?
Ben j’en sais trop rien… Je dirai que je préfère Grails à cause de Groovy. Play! m’a semblé proche de Grails, et rejoint la famille des frameworks Webs Hyper-productifs qui écrasent gentiment les frameworks orientés composants comme JSF pour n’en citer qu’un. C’est mon avis très personnel, et souvenez-vous que je portais aux louanges JSF 1.2 il y a 2 ou 3 ans. A mon avis, le ticket d’entrée avec Play! doit être moins élevé.
Souvenez-vous de la différence entre apprendre à faire du Ski Alpin et apprendre à faire du Snowboard. Grails c’est plutôt du Ski : tu vas bien galérer au début, là où Play! c’est ton pote Java avec donc le bénéfice de toutes les librairies qui existent déjà… Peut-être que tu risques de te faire moins mal ?

Il faudrait confronter les deux frameworks. Alors je vais lancer un appel solennel :
Si quelqu’un de l’équipe Play! lit cet article, ça ne vous dit pas de faire votre version du projet de Zenika ?

Là on aura une idée plus précise je pense.

Voilà j’espère que cette série d’articles vous aura donné une idée de ce que l’on peut faire avec Grails 1.2.

Je vous proposerai d’autres articles sur les sujets que j’ai découvert ces derniers mois, il y a de quoi se chauffer cet hiver.

Code du projet

Vous pouvez télécharger le code source de la version finale :
grails_final_touilleurexpress.tar.gz

9 réflexions sur « Grails étape 3 : Bootstrap, XML, JSON, Ajax et création d'un GTag »

  1. Merci Nicolas d’avoir poussé si loin tes tutos Grails !

    Je note que sur la partie Grails vs Wicket tu parles de GWT mais tu ne réponds pas à ta propre question 🙂 ton opinion nous intéresse au delà d’un temps de « coding ».

    Sinon tu abordes le sujet Play!, et la moi je m’adresse a un autre Nicolas (oui M noocodecommit : toi !), on doit pouvoir se faire le même exemple en SpringROO + Wicket non ?
    Apparement il y a un truc en cours : Wicket + SpringROO a creuser donc 🙂

    Sur ce bonne fin 2009 à tous boire ou coder il faut choisir !
    Et faites pas trop la Java ^^

  2. Cet article est certes long, mais c’est probablement le plus intéressant, puisqu’on s’éloigne un peu du tuto classique pour s’intéresser à des détails spécifiques.

    Une précision par rapport au commentaire de Nicolas : le blog Excilys n’est pas « mon » blog, mais celui de la société pour laquelle je travaille. Un peu sur le même principe que le blog Xebia.

    Bonne année à tous !

  3. J ai lu « Grails in action » durant les vacances par curiosité et pour situer GWT par rapport à Grails et j’en arrive aussi à penser que GWT c est plus pour une application riche une sorte d’appli swing + JWS qui nécessite moins de ressources alors que Grails reste pour des applications pur web. Maintenant j’ai vu que Grails contenait un plug in pour GWT, alors effectivement si Grails et GWT travaillait main dans la main on pourra d’ici quelque temps avoir quelque chose de très très fort :).
    Seul bémol, comme tu le dis c’est le côté boite noire du code généré côté serveur, qui peut être rédhibitoire et qui peut laisser encore un peu de place à des projets commes AppFuse/SpringFuse.

  4. En fait je vais faire un dojo Play au Alpes JUG le 27 et emmanuel m’a proposé de choisir l’application de zenika comme sujet. Donc je pense que je vais profiter de cette session pour l’écrire avec Play.

    Je pense que ce sera très proche de ta version Grails mais en Java du coup. A part pour la partie Javascript ou la philosophie de Play est un peu différente. On encourage à utiliser directement les frameworks Javascript qui vont bien plutôt que de les masquer sous des composants serveurs.

    Je te tiens au courant quand j’ai une version Play.

    ++

  5. Tres bon article encore une fois. Si j’ai le temps je veux bien reprendre la serie d’article et montrer pas a pas comment on fait cela en Play! aussi (si Guillaume ne me devance pas ;))

    Amicalement,

    Nicolas

  6. On dirait bien que le calendrier javascript pour créer un nouveau contact ne fonctionne pas sur la page http://localhost:8080/zencontactdemo/contact/list, alors qu’il fonctionne parfaitement sur http://localhost:8080/zencontactdemo/contact/create

    Après vérification, il me semble bien qu’un petit bug s’est glissé dans l’appli 😀

    Dans la page list, on voit l’inclusion du formulaire de création de contacts :

    => Cela inclue la vue « create.gsp », qui contient… une page HTML intégrale. On observe donc, au milieu d’un , des balises etc !

    Je suppose que la solution consisterait à créer une vue partielle, et à l’utiliser à la fois depuis « create » et depuis « list ».

  7. Mon commentaire a été moisi Grrr les balises et exemples de code ont disparu. Je disais donc (je tente avec les codes html correspondant) :

    Dans la page list, on voit l’inclusion du formulaire de création de contacts :

    <g:include action= »create » controller= »contact » />

    => Cela inclue la vue « create.gsp », qui contient… une page HTML intégrale. On observe donc, au milieu d’un <div<, des balises html, head, body, etc !

Les commentaires sont fermés.