VIII. Création d'une interface d'administration en se basant sur du CRUD▲
Dans l'état actuel de notre blog, il n'est pas encore possible d'écrire de billets depuis l'interface web. Il n'est pas non plus possible de modérer les commentaires. Play fournit un module CRUD (Create, Read, Update and Delete) "clés en main" qui va nous permettre de créer rapidement une interface d'administration basique.
VIII-A. Activer le module CRUD▲
Une application Play peut être créée par l'assemblage de plusieurs modules. Cela nous permet de réutiliser ces composants au sein d'autres applications.
Le module CRUD est un module générique qui réalise une introspection du modèle de données pour créer des listes et des formulaires simples.
Pour activer le module CRUD, nous devons ajouter la ligne suivante dans le fichier /yabe/conf/application.conf :
# Import the crud module
module.crud=${play.path}/modules/crud
Ce module s'accompagne d'une liste de routes par défaut que nous pouvons réutiliser. Pour importer ces routes, nous avons simplement à ajouter dans notre fichier /yabe/conf/routes les lignes suivantes :
# Import CRUD routes
* /admin module:crud
Cela importera toutes les routes dont les URL seront préfixées par /admin. Nous pouvons désormais redémarrer notre application afin que ce nouveau module soit pris en compte.
VIII-B. Déclaration des contrôleurs CRUD▲
Pour chaque objet de notre modèle de données que nous souhaitons intégrer dans l'interface d'administration, nous devons déclarer un contrôleur qui étend la classe controllers.CRUD. Créons ainsi un contrôleur pour chacun de nos objets du modèle. Par exemple, pour l'objet Post, nous créons le contrôleur dans le fichier /yabe/app/controllers/Posts.java :
package
controllers;
import
play.*;
import
play.mvc.*;
public
class
Posts extends
CRUD {
}
La convention ici est que de pluraliser le nom de l'objet associé pour définir le nom du contrôleur (ainsi, le contrôleur de l'objet Post sera Posts). De cette façon, Play trouvera automatiquement l'objet associé pour chaque contrôleur. Si nous avons besoin d'utiliser un autre nom, alors il nous faut utiliser l'annotation @CRUD.For (voir la documentation pour plus d'informations).
Répétons cette opération pour chaque objet de notre modèle :
package
controllers;
import
play.*;
import
play.mvc.*;
public
class
Users extends
CRUD {
}
package
controllers;
import
play.*;
import
play.mvc.*;
public
class
Comments extends
CRUD {
}
package
controllers;
import
play.*;
import
play.mvc.*;
public
class
Tags extends
CRUD {
}
Maintenant, ouvrons notre navigateur sur la page http://localhost:9000/admin, ce qui devrait nous conduire à la page d'administration :
Si nous naviguons un peu dans cette page, nous pouvons noter que les noms des objets sont basiques. La raison est toute simple : Play utilise la méthode toString() pour trouver le nom des objets. Il suffit, pour corriger cela, de fournir une implémentation correcte de la méthode toString() pour les objets du modèle. Par exemple, pour la classe User :
public
String toString
(
) {
return
email;
}
VIII-C. Ajout de la validation▲
Notre nouvelle page d'administration ne contient, hélas, aucune forme de validation. Toutefois, le module CRUD est capable d'extraire les règles de validation depuis des annotations écrites dans notre classe de modèle de données. Modifions la classe User pour les y ajouter :
package
models;
import
java.util.*;
import
javax.persistence.*;
import
play.db.jpa.*;
import
play.data.validation.*;
@Entity
public
class
User extends
Model {
@Email
@Required
public
String email;
@Required
public
String password;
public
String fullname;
public
boolean
isAdmin;
...
Maintenant, si nous retournons dans notre interface d'administration et que nous éditons ou créons un utilisateur, nous voyons que Play a automatiquement ajouté des règles de navigation :
Pratiquons la même modification sur la classe Post :
package
models;
import
java.util.*;
import
javax.persistence.*;
import
play.db.jpa.*;
import
play.data.validation.*;
@Entity
public
class
Post extends
Model {
@Required
public
String title;
@Required
public
Date postedAt;
@Lob
@Required
@MaxSize
(
10000
)
public
String content;
@Required
@ManyToOne
public
User author;
@OneToMany
(
mappedBy=
"post"
, cascade=
CascadeType.ALL)
public
List<
Comment>
comments =
new
ArrayList
(
);
@ManyToMany
(
cascade=
CascadeType.ALL)
public
Set<
Tag>
tags =
new
HashSet
(
);
...
Et voyons le résultat :
Nous rencontrons un effet de bord intéressant : l'annotation @MaxSize a changé la façon dont Play affiche le formulaire. En effet, nous disposons désormais d'un champ de saisie de texte (textarea) pour saisir cette propriété.
Pour conclure, nous modifions les classes Comment et Tag :
package
models;
import
java.util.*;
import
javax.persistence.*;
import
play.db.jpa.*;
import
play.data.validation.*;
@Entity
public
class
Tag extends
Model implements
Comparable<
Tag>
{
@Required
public
String name;
...
package
models;
import
java.util.*;
import
javax.persistence.*;
import
play.db.jpa.*;
import
play.data.validation.*;
@Entity
public
class
Comment extends
Model {
@Required
public
String author;
@Required
public
Date postedAt;
@Lob
@Required
@MaxSize
(
10000
)
public
String content;
@ManyToOne
@Required
public
Post post;
...
VIII-D. Des libellés plus lisibles▲
Comme nous pouvons le constater sur les formulaires, les libellés sont un peu rudes ! Play utilise en effet le nom de la variable Java pour afficher le libellé correspondant dans le formulaire. Nous pouvons toutefois spécifier d'autres libellés en modifiant le fichier /yabe/conf/messages :
Nous pouvons différencier le fichier messages selon la langue. Par exemple, nous pouvons mettre les messages en français dans le fichier messages.fr.
Ajoutons ainsi les libellés dans notre fichier :
title=Title
content=Content
postedAt=Posted at
author=Author
post=Related post
tags=Tags set
name=Common name
email=Email
password=Password
fullname=Full name
isAdmin=User is admin
A nouveau, rafraichissons notre page pour voir les modifications :
VIII-E. Paramétrer la liste des commentaires▲
Le module CRUD a été développé de manière à être complètement paramétrable. Jetons un œil sur la page listant les commentaires, pour y constater que l'affichage n'est pas terrible. Nous voudrions ajouter plus de colonnes dans cette vue, particulièrement une colonne "billet concerné" qui nous permettra de filtrer facilement notre liste.
Comme c'est notre application qui reste maître, nous pouvons facilement surcharger n'importe quelle action ou n'importe quel template proposé par le module CRUD. Dans notre exemple, nous voulons paramétrer la vue des commentaires, et donc nous devons créer notre propre template /yabe/app/views/Comments/list.html.
Le module CRUD propose d'autres commandes, dès lors qu'il est activé. La commande crud:ov nous permet de surcharger (override) n'importe quel template. Depuis notre ligne de commandes, tapons ceci :
> play crud:ov --template Comments/list
Nous disposons alors d'un template /yabe/app/views/Comments/list.html :
#{extends 'CRUD/layout.html' /}
<div id
=
"crudList"
class
=
"${type.name}"
>
<h2 id
=
"crudListTitle"
>
&{'crud.list.title',
type.name}</h2>
<div id
=
"crudListSearch"
>
#{crud.search /}
</div>
<div id
=
"crudListTable"
>
#{crud.table /}
</div>
<div id
=
"crudListPagination"
>
#{crud.pagination /}
</div>
<p id
=
"crudListAdd"
>
<a href
=
"@{blank()}"
>
&{'crud.add',
type.modelName}</a>
</p>
</div>
La balise #{crud.table /} génère la table et son contenu. Il est possible de la paramétrer en utilisant l'attribut fields et en y spécifiant de nouvelles colonnes :
#{crud.table fields:['content', 'post', 'author'] /}
Notre tableau de commentaires affiche désormais trois colonnes :
Nous pouvons rencontrer ici un problème concernant la longueur du contenu du commentaire, qui peut s'avérer trop long pour un affichage correct. Nous avons la possibilité de dire à #{crud.table /} comment les informations doivent être affichées, et si cela est nécessaire, de les tronquer.
Nous allons spécifier à la balise #{crud.custom /} la manière d'afficher chaque champ :
#{crud.table fields:['content', 'post', 'author']}
#{crud.custom 'content'}
<a href
=
"@{Comments.show(object.id)}"
>
${object.content.length() > 50 ? object.content[0..50] + '…
' : object.content}
</a>
#{/crud.custom}
#{/crud.table}
Nous avons utilisé ici quelques raffinements du langage Groovy pour simplifier l'écriture du code !
VIII-F. Paramétrer le formulaire de publication▲
Nous avons vu que nous pouvions paramétrer les listes affichées par le module CRUD. Nous allons voir qu'il est aussi possible de configurer les formulaires de création. Par exemple, la façon dont nous sélectionnons les tags n'est guère pratique. Modifions le template Post/show :
> play crud:ov --template Posts/show
Cette commande nous crée un nouveau template /yabe/app/views/Posts/show.html :
#{extends 'CRUD/layout.html' /}
<div id
=
"crudShow"
class
=
"${type.name}"
>
<h2 id
=
"crudShowTitle"
>
&{'crud.show.title',
type.modelName}</h2>
<div class
=
"objectForm"
>
#{form action:@save(object.id), enctype:'multipart/form-data'}
#{crud.form /}
<p class
=
"crudButtons"
>
<input type
=
"submit"
name
=
"_save"
value
=
"&{'crud.save', type.modelName}"
/>
<input type
=
"submit"
name
=
"_saveAndContinue"
value
=
"&{'crud.saveAndContinue', type.modelName}"
/>
</p>
#{/form}
</div>
#{form @delete(object.id)}
<p class
=
"crudDelete"
>
<input type
=
"submit"
value
=
"&{'crud.delete', type.modelName}"
/>
</p>
#{/form}
</div>
Nous pouvons également corriger la balise #{crud.form /} pour faire en sorte de modifier le champ de sélection des tags :
#{crud.form}
#{crud.custom 'tags'}
<label for
=
"tags"
>
&{'tags'}
</label>
<style type
=
"text/css"
>
.tags-list
.tag
{
cursor:
pointer
;
padding:
1
px 4
px;
}
.tags-list
.selected
{
background:
#222
;
color:
#fff
;
}
</style>
<script type
=
"text/javascript"
>
var toggle =
function(
tagEl) {
var input =
document
.getElementById
(
'h'
+
tagEl.
id);
if(
tagEl.
className.indexOf
(
'selected'
) >
-
1
) {
tagEl.
className =
'tag'
;
input.
value =
''
;
}
else {
tagEl.
className =
'tag selected'
;
input.
value =
tagEl.
id;
}
}
</script>
<div class
=
"tags-list"
>
#{list items:models.Tag.findAll(), as:'tag'}
<span id
=
"${tag.id}"
onclick
=
"toggle(this)"
class
=
"tag ${object.tags.contains(tag) ? 'selected' : ''}"
>
${tag}
</span>
<input id
=
"h${tag.id}"
type
=
"hidden"
name
=
"${fieldName}"
value
=
"${object.tags.contains(tag) ? tag.id : ''}"
/>
#{/list}
</div>
#{/crud.custom}
#{/crud.form}
Tout ceci est un peu artisanal, mais cela a le mérite de fonctionner...
Nous avons réussi à créer une interface d'administration tout à fait convenable !