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 tapez2
et validez - puis faites
cd spa
, puisplay ~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épetoirespa\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 :
- la derniĂšre version de jQuery : http://code.jquery.com/jquery-1.8.2.js
- la derniĂšre version dâUnderscore : http://underscorejs.org/underscore.js
- la derniĂšre version de Backbone : http://backbonejs.org/backbone.js
- la derniĂšre version de Mustache : https://raw.github.com/janl/mustache.js/master/mustache.js
- la derniĂšre version de YepNope : https://raw.github.com/SlexAxton/yepnope.js/master/yepnope.js
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 :
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 :
# 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 :
# 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 :
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 !â :
# # 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 :
# 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).
Tweet