Comment se passer des templates Scala dans Play!> 2

Qu’allons nous voir ?

- *Comment crĂ©er des mĂ©thodes de contrĂŽleurs **Play!>** qui vont rĂ©pondre Ă  des requĂȘtes **REST***
- *Comment "appeler" ces méthodes via ajax avec **jQuery***
- *Comment "encapsuler" tout ça grùce à **Backbone***
- *... et comment utiliser le templating cÎté client avec **Mustache** ou **Underscore**.*


 Il y aura mĂȘme un peu de YepNope.

Code source : https://github.com/k33g/samples/tree/master/spa

Corrections & Ajouts du 06.10.2012

- *Modifications du contrĂŽleur `Humans` : suppression de `save()`, ajout de `create()` & `update()`*
- *Mise en conformité de `routes`*
- *Ajout d'un § : "Optimisation du code du contrÎleur"*

Avertissement

L’introduction (§ PrĂ©lude) est longue, c’est un “petit” coup de gueule, mais vous pouvez franchement passer directement Ă  la partie § “Faites du Play!>, pas du Scala, ou comment je me passe des templates ?” si vous voulez mettre les mains dedans tout de suite.

Cet article est destiné aux :

  • dĂ©veloppeurs Play!> allergiques Ă  Scala
  • dĂ©veloppeurs Javascript & Backbone : il n’y a pas que Javascript dans la vie, Play!> est une techno backend excellente
  • dĂ©veloppeurs JEE, Java & Play!> en gĂ©nĂ©ral : on peut faire autrement, il n’y a pas que Java dans la vie

Bonne lecture & remarques attendues.

P.

Prélude

Cela va faire presque un an maintenant que je faisais ma premiĂšre prĂ©sentation en publique en compagnie de @loic_d sur PlayFramework 1 (j’insiste sur le 1) lors d’un LyonJUG. Alors, triple stress pour moi :

  • d’habitude je m’adresse Ă  des clients, lĂ  je me retrouve fasse Ă  une foule de dĂ©veloppeurs (1)
  • je ne suis pas un dĂ©veloppeur java
  • la prĂ©sentation est une sorte de “battle” : “Stateless versus Statefull”, et notre “adversaire” est le brillant orateur, que dis-je ! Tribun ! @antoine_sd de chez Ippon. Je rĂȘve d’avoir son aisance en public. Donc, Stateless, @loic_d et moi, Statefull, c’est @antoine_sd.

Concernant @loic_d, au moins un motif de stress : je (moi donc) ne suis pas un développeur java ;)

En dĂ©pit d’une grosse envie de reculer, mais n’ayant trouvĂ© aucune excuse rĂ©ellement sĂ©rieuse, nous sommes partis “au front”, et finalement cela ne s’est pas trop mal passĂ© 
 Jusqu’à la sĂ©ance de questions/rĂ©ponses, pendant laquelle, en ce qui me concerne j’ai vĂ©cu un “petit” moment de solitude.

(1): je suis avant-vendeur = faux commercial + faux développeur

Solitude, ou comment Play!> 2 m’a tuĂ©

Ma vision Play!> 1 de Ă©tait la suivante :

  • Play!>, c’est mettre Java+”le Web” Ă  la portĂ©e de tous (mon argument principal : “mĂȘme moi j’y arrive”)
  • Play!>, c’est donner de l’espoir aux jeunes dĂ©veloppeurs dĂ©butants effrayĂ©s par JEE
  • Play!>, m’a fait comprendre et aimer MVC (faites du STRUTS vous comprendrez)
  • Play!>, pourrait mĂȘme faire kiffer un dĂ©veloppeur .Net
  • Et enfin Play!>, c’est productif ! (lĂ  c’est surtout pour mes clients, patrons, directeurs de projets, CTO, 
)

Il se trouve qu’au moment de notre prĂ©sentation, la version 2 de Play!> Ă©tait en train de pointer le bout de son nez. Et que :

  • apparaissait une version Scala de Play!> 2,
  • mais que mĂȘme dans la version Java de Play!> 2, tu (on se tutoie) est obligĂ© de faire du Scala, car les templates (les vues) se codent en Scala, et que c’est gĂ©nial, car comme c’est typĂ©, ça limite les erreurs !!!

Eh bien, NON !, ce n’est pas gĂ©nial, parce que :

  • j’ai du mal avec Scala (je pense ne pas ĂȘtre le seul),
  • j’ai dĂ©jĂ  eu du mal Ă  vendre une techno qui s’appelle “Play”, si en plus il faut vendre une nouvelle techno (qui en plus s’appelle “Scala”), que personne ne connaĂźt (je rappelle que je suis en province) 

  • Et 
 Oh purĂ©e ! (expression stĂ©phanoise) Comment fait-on pour la migration ?

Et c’est cette derniĂšre question qui nous a Ă©tĂ© posĂ©e !

Et lĂ , Guillaume Bort, si je t’avais eu sous la main (dĂ©solĂ©, on ne se connaĂźt pas, je me permets tout de mĂȘme de te tutoyer), je crois que je t’aurais lĂąchement laissĂ© rĂ©pondre Ă  cette foule de dĂ©veloppeurs anxieux de connaĂźtre le devenir de ce qu’ils pensaient ĂȘtre le graal du dĂ©veloppement “java web” (petite rĂ©fĂ©rence Ă  Grails) 
. D’un framework prĂ©sentĂ© comme l’outil de dĂ©veloppement des masses, accessible Ă  tous, on passait Ă  un outil destinĂ© Ă  l’élite !!! PS : je suis du cĂŽtĂ© des masses.

Je crois mĂȘme me souvenir d’avoir vu passer un tweet de @juliendubois pendant la prez, qui ricannait (gentiment) sur le sujet.

Je ne suis plus sĂ»r de ce que nous avons rĂ©pondu, mais sur le coup : “Solitude !” (Ah! @loic_d me dit dans l’oreillette qu’il avait parlĂ© du module de template Groovy en prĂ©paration Ă  l’époque). Je comprend mieux pourquoi, nous n’arrivons pas Ă  faire un “Cast-it” avec toi sur le sujet, au bout de presqu’un an, Guillaume, tu dois ĂȘtre super embĂȘtĂ© de nous avoir mis dans cette situation :)))

Tristesse et renaissance

Ce soir lĂ , j’étais prĂȘt Ă  abandonner Play!>. AprĂšs quelques semaines (mois ?) d’errements et d’égarements, j’ai quand mĂȘme appris Ă  utiliser node.js et express.js !!! (soit-dit en passant, c’est pas mal du tout), j’ai dĂ©cidĂ© que je n’allais pas m’avouer vaincu aussi facilement, moi aussi je veux faire partie de l’élite !, et je me suis “collĂ©â€ dans Play!> 2 (j’ai un peu grattĂ© sur ce que j’arrivais Ă  apprendre : http://3monkeys.github.com/play.rules/livre.play.deux.web/play2.rules.return.html).(@loic_d devrait vous concocter quelques trucs supplĂ©mentaires dans un futur proche 
 sur du Scala justement).

# RĂ©sultats ???

Alors, je suis arrivĂ© Ă  retrouver mes petits en ce qui concerne les modĂšles et les contrĂŽleurs, mais pour les vues, dĂ©cidĂ©ment je ne m’y fais pas, Scala, je n’y arrive pas (le 1er qui me dit que c’est comme javascript a intĂ©rĂȘt Ă  courrir vite), en dĂ©pit des efforts de @loic_d pour me vendre le bouzin, pour le moment ce n’est pas pour moi (Mais ne jamais dire jamais).

MalgrĂ©, cette lĂ©gĂšre problĂ©matique Ă  propos des vues, j’ai appris Ă  nouveau Ă  aimer Play!> (Guillaume, si un jour tu me lis, j’espĂšre que tu es rassurĂ© ;)) et que le concept initial Ă©tait respectĂ©.


 Et je me suis trouvĂ© mon propre arrangement (avec moi-mĂȘme) pour continuer Ă  faire du Play!> avec la version 2 
 Mais Sans les vues “Scala (Oh purĂ©e ! L’hĂ©rĂ©tique !!!)

Nous voilĂ  donc enfin dans le vif du sujet de cet article. Certes, c’était long, mais cela fait un an que je rumine :)

Faites du Play!>, pas du Scala, ou comment je me passe des templates ?

Il se trouve que je suis trĂšs fan (et j’y crois) du modĂšle “Single Page Application”. En gros entre la page web et le serveur, seules les donnĂ©es circulent, vous n’avez potentiellement qu’une seule page html (avec pas mal d’intelligence en javascript) et cĂŽtĂ© serveur, vos contrĂŽleurs vous crachent du json. Un exemple ? Gmail !!! (donc ne me dites pas que l’idĂ©e est “dĂ©bile”).

J’ai donc poussĂ© l’exercice jusqu’au bout ;), j’ai mĂȘme supprimĂ© le rĂ©pertoire views de mon projet Play!> 2.

Objectifs & Préparation du projet

# Objectifs

Mon but est de faire une application Play!> 2 sur les principes REST (Representational State Transfer) qui permettra de faire des opérations de type CRUD sur un modÚle java en utilisant des services basés sur le protocole http avec les verbes suivants :

  • Create : POST
  • Read : GET
  • Update : PUT
  • Delete : DELETE

Si cela vous paraĂźt obscur, pas d’inquiĂ©tude, la partie pratique du tuto devrait vous Ă©clairer. Mais je vous engage fortement Ă  lire http://naholyr.fr/2011/08/ecrire-service-rest-nodejs-express-partie-1/ de @naholyr.

# GĂ©nĂ©ration du squelette de l’application

Commencez donc par créer une application java Play!> 2 :

  • dans une console ou un terminal, tapez play new spa (spa pour single page application)
  • Ă  la question What is the application name ?, validez le choix par dĂ©faut
  • ensuite choisissez l’alternative 2 - Create a simple Java application, donc tapez 2 et validez
  • puis faites cd spa, puis play ~run pour dĂ©marrer votre application (vous n’ĂȘtes pas obligĂ©s de le faire tout de suite) (le ~ permet Ă  Play!> de scruter tous les changements et de compiler “en live”).

Faites un peu de rangement :

  • supprimez le rĂ©pertoire spa\app\views (et donc son contenu)
  • supprimez le contrĂŽleur Application.java du rĂ©petoire spa\app\controllers
  • crĂ©ez un rĂ©pertoire spa\app\models

# # Paramétrage de la persistance des données

Aller dans spa\conf\application.conf et changez (vers la ligne 25) :

#  db.default.driver=org.h2.Driver
#  db.default.url="jdbc:h2:mem:play"

par

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:file:play"

puis décommentez :

#  ebean.default="models.*"


 enregistrez.

# # DĂ©finition d’une route statique

Aller dans spa\conf\routes et supprimer la “route Home” :

#  Home page
GET     /                           controllers.Application.index()

Et ajoutez celle-ci Ă  la fin :

GET / controllers.Assets.at(path="/public", file="index.html")

Cela signifie, maintenant, que lorsque nous appellerons http://localhost:9000, ce sera la page statique index.html qui sera chargée.

# Préparation des éléments statiques du projet

Avant de “coder” notre page index.html nous aurons besoin des Ă©lĂ©ments suivants :

Et collez moi tout ça pour le moment dans spa\public\javascripts (vous pouvez supprimer jquery-1.7.1.min.js).

Remarque : YepNope est un loader de script, certains me diront que Play!> apporte le support de Require.js, mais je préfÚre YepNope, donc aprÚs vous pourrez adapter.

# Codons notre page index.html

Pour le moment pas besoin de grand chose, créez une page index.html dans spa\public avec le code suivant :

<!DOCTYPE html>

<html>
    <head>
        <title>Single Page Application</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
    <body>
    	<h1>My Single Page Application</h1>
    	<h2>by K33G_org</h2>

    </body>

    <script src="assets/javascripts/yepnope.js"></script>
    <script src="assets/main.js"></script>

</html>

Rien de trùs violent, jusqu’ici. Nous allons faire un peu plus de chose avec main.js.

# Codons le fichier main.js de chargement de script :

Créez un fichier main.js dans spa\public avec le code suivant :

yepnope({
    load: {
        jquery              : 'assets/javascripts/jquery-1.8.2.js',
        underscore          : 'assets/javascripts/underscore.js',
        backbone            : 'assets/javascripts/backbone.js',
        mustache            : 'assets/javascripts/mustache.js',
    },
    complete : function () {
        $(function (){
            console.log("Application chargée ...");

        });  
    }
});

Je pense que vous me voyez venir, je vais vous parler de Backbone, mais dans un premier temps c’est surtout jQuery qui va nous ĂȘtre utile.

Vous pouvez d’ores et dĂ©jĂ  tester votre page pour vĂ©rifier que les scripts javascript sont bien chargĂ©s.

Enfin un peu de Java : le modĂšle !

Codons notre 1er (et seul modĂšle), dans le rĂ©pertoire spa\app\models donc. Et je ne vais pas ĂȘtre original pour 2 sous : Human.java :

package models;

import play.db.ebean.Model;
import javax.persistence.*;

@Entity
public class Human extends Model{

    @Id
    public Long id;
    public String firstName;
    public String lastName;
    public Long age;

    public static Finder<Long, Human> find = 
            new Finder<Long, Human>(Long.class, Human.class);

}

Et maintenant 
 Le contrîleur !

LĂ  aussi, nous allons coder notre seul et unique contrĂŽleur Humans.java dans spa\app\controllers. Cette fois, il y a un peu plus de code, donc soyez attentifs. J’explique en commentaire dans le code Ă  quoi vont servir les mĂ©thodes :

package controllers;

import models.*;
import play.data.*;
import play.mvc.*;

import java.util.List;

import static play.libs.Json.toJson;

public class Humans extends Controller {

    /*
        Retourner une liste (au format JSON) de Humans
        cela correspond Ă  un appel http de type GET
    */
    public static Result getAll() { // GET

        List<Human> list = Human.find.orderBy("lastName").findList();
        return ok(toJson(list));
    }

    /*
        Retrouver un "Human" (au format JSON) par son id
        Cela correspond Ă  un appel http de type GET
        Si il n'existe pas on génÚre une erreur
    */
    public static Result getById(Long id) { // GET

        Human modelToFind = Human.find.byId(id);

        if(modelToFind!=null) {
            return ok(toJson(modelToFind));
        } else {
            return badRequest("not found");
        }
        
    }

    /*
        CrĂ©er ou sauvegarder un "Human", c'est une requĂȘte de type POST ou PUT.
        - On récupÚre les paramÚtres grùce à bindFromRequest
        - si l'id du modĂšle n'est pas null c'est une mise Ă  jour (PUT)
        - sinon c'est une création (POST)
    */
    public static Result create() { //POST

        Form<Human> form = form(Human.class).bindFromRequest();
        Human model = form.get();
		model.save();
        return ok(toJson(model));
    }

    public static Result update(Long id) { //PUT

        Form<Human> form = form(Human.class).bindFromRequest();
        Human model = form.get();
        model.id = id;
        model.update();
        return ok(toJson(model));
    }	    


    /*
        Retrouver un "Human" (au format JSON) par son id
        Puis le supprimer
        Cela correspond Ă  un appel http de type DELETE
        Si il n'existe pas on génÚre une erreur
    */
    public static Result delete(Long id) { // DELETE

        Human modelToFind = Human.find.byId(id);
        if(modelToFind!=null) {
            modelToFind.delete();
            return ok(toJson(true));
        } else {
            return badRequest("not found");
        }

    }

    /*
        RequĂȘtes de type GET pour ne ramener qu'un certain nombre d'enregistrements
    */
    public static Result query(String fieldName, String value) { // GET
        //humans/lastName/equals/morane
        List<Human> list = Human.find.where().eq(fieldName, value).findList();
        return ok(toJson(list));
    }      
}


 ça c’est fait.

Ensuite l’écriture indispensable des routes correspondantes (pour chacune des mĂ©thodes du contrĂŽleur) :

On ajoute ceci dans le fichier routes :

# Création
POST /humans  controllers.Humans.create()

# Mise Ă  jour
PUT /humans/:id  controllers.Humans.update(id: Long)		

# Rechercher par Id
GET  /humans/:id  controllers.Humans.getById(id: Long)

# Supprimer par Id
DELETE /humans/:id  controllers.Humans.delete(id: Long)

# Retrouver tous les éléments
GET /humans controllers.Humans.getAll()

# Retrouver certains éléments
GET /humans/:fieldName/equals/:value controllers.Humans.query(fieldName: String, value: String)

Nous avons maintenant tout ce qu’il faut pour faire nos 1Ăšres requĂȘtes ajax. Vous pouvez RaffraĂźchir votre page (Play!> va vous proposer de crĂ©er votre modĂšle de donnĂ©es. Ne refusez pas.)

De l’Ajax dans la console

Maintenant, ouvrez la console de votre navigateur, et nous allons essayer diverses requĂȘtes ajax (avec l’aide de jQuery).

# CrĂ©er des “Humans”

Une 1Úre création :

$.ajax({
    type:"POST", 
    url:"/humans", data:{firstName:"John", lastName:"Doe"}, 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Vous devriez obtenir une sortie de ce type dans la console. Vous notez au passage qu’un id a Ă©tĂ© automatiquement affectĂ© au modĂšle :

Alt "img"

Une 2Úme création :

$.ajax({
    type:"POST", 
    url:"/humans", data:{firstName:"Bob", lastName:"Morane"}, 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Et enfin une 3Ăšme :

$.ajax({
    type:"POST", 
    url:"/humans", data:{firstName:"Tom", lastName:"Jones"}, 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

A chaque fois vous pourrez noter que l’on obtient automatiquement un Id pour le model.

# Retrouver tous les enregistrements :

C’est trùs simple : (notez le changement d’url : /humans à la place de /human)

$.ajax({
    type:"GET", 
    url:"/humans", 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Vous devriez obtenir une sortie de ce type dans la console :

Alt "img"

# Modification des modĂšles

J’ai oubliĂ© de renseigner l’ñge :

$.ajax({
    type:"PUT", 
    url:"/humans/1", data:{age: 43, firstName : "John", lastName : "DOE"}, 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Remarque : l’id est passĂ© dans l’url.

Remarque : je suis allĂ© au plus simple dans mon exemple (code cĂŽtĂ© java), donc vous devez penser Ă  bien renseigner l’ensemble des champs lors de la mise Ă  jour.

# Faire une reqĂ»ete : je veux toute la famille “DOE”

Tout d’abord, ajouter un modĂšle avec un lastName Ă©gal Ă  “DOE”

$.ajax({
    type:"POST", 
    url:"/humans", data:{age: 22, firstName : "Jane", lastName : "DOE"}, 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Puis :

$.ajax({
    type:"GET", 
    url:"/humans/lastName/equals/DOE", 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Donc, lĂ  si tous va bien vous obtiendrez cette sortie :

Alt "img"

# Suppression d’un modùle :

Par exemple, je souhaite supprimer le modùle d’Id 4 (Jane) :

$.ajax({
    type:"DELETE", 
    url:"/humans/4", 
    error : function(err){console.log("Erreur", err);}, 
    success : function(data){ console.log(data);}
});

Et si vous tentez une seconde fois de supprimer le mĂȘme modĂšle, vous rĂ©cupĂšrerez bien une erreur dans la console javascript. avec une propriĂ©tĂ© responseText Ă©gale Ă  "not found" et une propriĂ©tĂ© statusText Ă©gale Ă  "Bad Request".

Nous pourrions bien sĂ»r programmer bien d’autres types de requĂȘtes, mais pour l’exercice, les requĂȘtes “de base” sont amplement suffisantes.

Backbone <3 Play!>

Il est temps de faire du MVC cÎté client avec Backbone.js.

# CrĂ©ation d’un Backbone.Model et d’une Backbone.Collection

Dans main.js, ajoutez application : 'assets/app.js'

yepnope({
    load: {
        jquery              : 'assets/javascripts/jquery-1.8.2.js',
        underscore          : 'assets/javascripts/underscore.js',
        backbone            : 'assets/javascripts/backbone.js',
        mustache            : 'assets/javascripts/mustache.js',
        application         : 'assets/app.js' 						//<-- l'ajout est ici
    },
    complete : function () {
        $(function (){
            console.log("Application chargée ...");
            App.start();	 										//<-- et ici aussi

        });  
    }
});

puis créez un fichier app.js qui contiendra notre application Backbone dans le répertoire spa\public :

/* Module */
var App = {
	Models : {},
	Collections : {},
	Views : {},
	start : function() { start(); }

}

App.Models.Human = Backbone.Model.extend({
	urlRoot : "/humans"
});

App.Collections.Humans = Backbone.Collection.extend({
	url : "/humans"
});


function start() {
	console.log("DĂ©marrage de l'application Backbone");
}

# Testons notre modĂšle et notre collection dans la console

Toujours dans la console du navigateur :

var angelina = new App.Models.Human({firstName:"Angelina", lastName:"Jolie"});
var sam = new App.Models.Human({firstName:"Sam", lastName:"LePirate"});

puis :

angelina.save({}, {
	success : function(data) { console.log(data); },
	error : function(err) { throw err; }
});

sam.save({}, {
	success : function(data) { console.log(data); },
	error : function(err) { throw err; }
});

Jusqu’ici, normalement, si tout s’est bien passĂ©, vous devriez avoir 2 “humans” supplĂ©mentaires en base de donnĂ©es. Nous allons le vĂ©rifier en crĂ©ant une collection Backbone (encore dans la console) :

var humans = new App.Collections.Humans();

Puis :

humans.fetch({
	success : function(data) { console.log(data); },
	error : function(err) { throw err; }
});

puis faites un :

humans.each(function(human){
	console.log(human.get("id"),human.get("firstName"), human.get("lastName"));
})

Et vous obtiendrez la liste complĂšte des enregistrements :

Alt "img"

Donc vous pouvez voir que nos “objets” Backbone savent trĂšs bien “discuter” avec nos services REST Play!> 2 (sans rin re-Ă©crire, ce qui est assez pratique).

Nous pouvons donc passer Ă  la partie affichage.

# Templating cÎté client

Pour cela nous utiliserons Mustache.js.

Nous allons dĂ©crire notre template d’affichage dans la page index.html :

        <!-- définition du template -->
        <script type="text/template" id="humans_list_template">

            <ul>{ {# humans} }
                <li>{ {id} } { {firstName} } { {lastName} } { {age} }</li>
            { {/humans} }</ul>
            
        </script>
        <!-- les résultats viendront ici -->
        <div id="humans_list"></div>

PS: Supprimez les espace entre { et { ou } } : j’ai un problùme d’affichage avec Jekyll, et l’option raw semble ne pas fonctionner.

Donc au final, notre page html aura le code suivant :

<!DOCTYPE html>

<html>
    <head>
        <title>Single Page Application</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
    <body>
    	<h1>My Single Page Application</h1>
    	<h2>by K33G_org</h2>

        <!-- définition du template -->
        <script type="text/template" id="humans_list_template">

            <ul>{ {# humans} }
                <li>{ {id} } { {firstName} } { {lastName} } { {age} }</li>
            { {/humans} }</ul>
            
        </script>
        <!-- les résultats viendront ici -->
        <div id="humans_list"></div>

    </body>

    <script src="assets/javascripts/yepnope.js"></script>
    <script src="assets/main.js"></script>

</html>

Ensuite, allons coller notre code Backbone dans app.js :

Nous allons ajouter une vue HumansListView :

App.Views.HumansListView = Backbone.View.extend({
    el : $("# humans_list"),
    initialize : function () {
        this.template = $("# humans_list_template").html();

        //dĂšs que la collection "change" j'actualise le rendu de la vue
        _.bindAll(this, 'render');
        this.collection.bind('reset', this.render);
        this.collection.bind('change', this.render);
        this.collection.bind('add', this.render);
        this.collection.bind('remove', this.render);

    },
    render : function () {
        var renderedContent = Mustache.to_html(this.template, {humans : this.collection.toJSON()} );
        this.$el.html(renderedContent);
    }
});

que nous allons utiliser dans la fonction start() qui est appelée une fois la page html chargée (cf. méthode complete() de yepnope dans main.js) :

function start() {

	console.log("DĂ©marrage de l'application Backbone");

	window.humansCollection = new App.Collections.Humans();

	window.humansListView = new App.Views.HumansListView({collection : humansCollection});

	humansCollection.fetch({
		success : function(data) { console.log(data); },
		error : function(err) { throw err; }
	});

}

Code final d’app.js

/* Module */
var App = {
	Models : {},
	Collections : {},
	Views : {},
	start : function() { start(); }

}

App.Models.Human = Backbone.Model.extend({
	urlRoot  : "/humans"
});

App.Collections.Humans = Backbone.Collection.extend({
	url : "/humans",
	model : App.Models.Human
	
});

App.Views.HumansListView = Backbone.View.extend({
    el : $("# humans_list"),
    initialize : function () {
        this.template = $("# humans_list_template").html();

        //dĂšs que la collection "change" j'actualise le rendu de la vue
        _.bindAll(this, 'render');
        this.collection.bind('reset', this.render);
        this.collection.bind('change', this.render);
        this.collection.bind('add', this.render);
        this.collection.bind('remove', this.render);

    },
    render : function () {
        var renderedContent = Mustache.to_html(this.template, {humans : this.collection.toJSON()} );
        this.$el.html(renderedContent);
    }
});

function start() {

	console.log("DĂ©marrage de l'application Backbone");

	window.humansCollection = new App.Collections.Humans();

	window.humansListView = new App.Views.HumansListView({collection : humansCollection});

	humansCollection.fetch({
		success : function(data) { console.log(data); },
		error : function(err) { throw err; }
	});
}

Maintenant, enregistrez et raffraichissez la page de votre navigateur, et “tadaaaa !” :

Alt "img"

# # Attendez ce n’est pas fini :

Dans la console de votre navigateur, essayez ceci :

humansCollection.add(new App.Models.Human({
	firstName : "K33g_org",
	lastName : "GrosGeek",
	age : 43 
}))

La liste va se mettre Ă  jour automatiquement.

Maintenant (toujours dans la console du navigateur), essayez ceci :

var philippe = new App.Models.Human({
	firstName : "Philippe",
	lastName : "CharriĂšre",
	age : 43 
})

puis :

philippe.save({},{
	success : function () { humansCollection.fetch(); },
	error : function (err){ throw err; }
})

Cette fois, le modÚle est enregistré en base, la collection rechargée et la liste remise à jour automatiquement.

Backbone, vous permet beaucoup d’autres petits tours de magie, mais ce n’est pas l’objet de ce tuto.

# # Une derniĂšre remarque Ă  propos du templating

Le template que nous avons utilisĂ© est “passif”, nous ne pouvons pas dĂ©crire de fonctions dans le template, tout est prĂ©parĂ© en amont par l’objet Backbone.View. Si vous avez besoin de faire autrement (un peu comme avec les templates Scala ;)), c’est Ă  dire “mettre” un peu d’intelligence dans le template (par exemple, afficher un message quand age n’est pas renseignĂ©), sachez que la librairie Underscore possĂšde elle aussi un moteur de template.

Si vous voulez essayez, votre template ressemblera Ă  ceci :

<!-- définition du template -->
<script type="text/template" id="humans_list_again_template">

    <ul>
        <% _.each(humans ,function(human){ %>
            <li> 
                <%= human.get("id") %> 
                <%= human.get("firstName") %> 
                <%= human.get("lastName") %> 
                <%= human.get("age") ? human.get("age") : "<b>???</b>" %>
            </li>
        <% }); %>
    </ul>

</script>
<!-- les résultats viendront ici -->
<div id="humans_list_again"></div>

Votre nouvelle vue HumansListAgainView aura cette tĂȘte :

App.Views.HumansListAgainView = Backbone.View.extend({
	el : $("# humans_list_again"),
	initialize : function (blog) {
		this.template = _.template($("# humans_list_again_template").html());

        _.bindAll(this, 'render');
        this.collection.bind('reset', this.render);
        this.collection.bind('change', this.render);
        this.collection.bind('add', this.render);
        this.collection.bind('remove', this.render);
	},
	render : function () {
        var renderedContent = this.template({humans : this.collection.models });
        this.$el.html(renderedContent);			
	}			
});

Vous l’instanciez comme la prĂ©cĂ©dente : window.humansListAgainView = new App.Views.HumansListAgainView({collection : humansCollection});

Sauvegardez, testez :

Alt "img"

# Optimisation du code du contrĂŽleur

Finalement, nous savons que nous n’échangeons que du JSON entre le client et le serveur, donc nous allons faire la mĂȘme chose que Form<Human> form = form(Human.class).bindFromRequest(); et Human model = form.get(); mais en plus simple. Vous pouvez remplacer ces 2 lignes par Human model = fromJson(request().body().asJson(), Human.class); et nous aurons finalement le code suivant :

package controllers;

import models.*;
import play.data.*;
import play.mvc.*;

import java.util.List;

import static play.libs.Json.toJson;

//Ajout de 2 imports
import static play.libs.Json.fromJson;
import org.codehaus.jackson.JsonNode;


public class Humans extends Controller {

    /*
        Retourner une liste (au format JSON) de Humans
        cela correspond Ă  un appel http de type GET
    */
    public static Result getAll() { // GET

        List<Human> list = Human.find.orderBy("lastName").findList();
        return ok(toJson(list));
    }

    /*
        Retrouver un "Human" (au format JSON) par son id
        Cela correspond Ă  un appel http de type GET
        Si il n'existe pas on génÚre une erreur
    */
    public static Result getById(Long id) { // GET

        Human modelToFind = Human.find.byId(id);

        if(modelToFind!=null) {
            return ok(toJson(modelToFind));
        } else {
            return badRequest("not found");
        }
        
    }

    /*
        CrĂ©er ou sauvegarder un "Human", c'est une requĂȘte de type POST ou PUT.
        - On récupÚre les paramÚtres grùce à bindFromRequest
        - si l'id du modĂšle n'est pas null c'est une mise Ă  jour (PUT)
        - sinon c'est une création (POST)

    */
    public static Result create() { //POST

        Human model = fromJson(request().body().asJson(), Human.class);        
        model.save();
        return ok(toJson(model));	        
    }

    public static Result update(Long id) { //PUT
        
        Human model = fromJson(request().body().asJson(), Human.class);
        model.id = id;
        model.update();
        return ok(toJson(model));
    }

    /*
        Retrouver un "Human" (au format JSON) par son id
        Puis le supprimer
        Cela correspond Ă  un appel http de type DELETE
        Si il n'existe pas on génÚre une erreur
    */
    public static Result delete(Long id) { // DELETE

        Human modelToFind = Human.find.byId(id);
        if(modelToFind!=null) {
            modelToFind.delete();
            return ok(toJson(true));
        } else {
            return badRequest("not found");
        }

    }

    /*
        RequĂȘtes de type GET pour ne ramener qu'un certain nombre d'enregistrements
    */
    public static Result query(String fieldName, String value) { // GET
        //humans/lastName/equals/morane
        List<Human> list = Human.find.where().eq(fieldName, value).findList();
        return ok(toJson(list));
    }    
  
}

Remarque : N’oubliez pas d’ajouter :

import static play.libs.Json.fromJson;
import org.codehaus.jackson.JsonNode;

Voilà, c’est tout pour aujourd’hui.

(Promis, j’essaierais un jour de faire la mĂȘme chose en Scala).

blog comments powered by Disqus

Related posts