Développer une application avec le framework Play !


précédentsommairesuivant

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 :

 
Sélectionnez
# 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 :

 
Sélectionnez
# 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 :

 
Sélectionnez
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 :

 
Sélectionnez
package controllers;
 
import play.*;
import play.mvc.*;
 
public class Users extends CRUD {    
}
 
Sélectionnez
package controllers;
 
import play.*;
import play.mvc.*;
 
public class Comments extends CRUD {    
}
 
Sélectionnez
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 :

Image non disponible

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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

Image non disponible

Pratiquons la même modification sur la classe Post :

 
Sélectionnez
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 :

Image non disponible

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 :

 
Sélectionnez
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;
 
...
 
Sélectionnez
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 :

 
Sélectionnez
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 :

Image non disponible

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 :

 
Sélectionnez
> play crud:ov --template Comments/list

Nous disposons alors d'un template /yabe/app/views/Comments/list.html :

 
Sélectionnez
#{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 :

 
Sélectionnez
#{crud.table fields:['content', 'post', 'author'] /}

Notre tableau de commentaires affiche désormais trois colonnes :

Image non disponible

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 :

 
Sélectionnez
#{crud.table fields:['content', 'post', 'author']}
 #{crud.custom 'content'}
  <a href="@{Comments.show(object.id)}">
   ${object.content.length() > 50 ? object.content[0..50] + '&#8230;' : 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 :

 
Sélectionnez
> play crud:ov --template Posts/show

Cette commande nous crée un nouveau template /yabe/app/views/Posts/show.html :

 
Sélectionnez
#{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 :

 
Sélectionnez
#{crud.form}
    #{crud.custom 'tags'}
        <label for="tags">
            &{'tags'}
        </label>
        <style type="text/css">
	        .tags-list .tag {
	            cursor: pointer;
	            padding: 1px 4px;
	        }
	        .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...

Image non disponible

Nous avons réussi à créer une interface d'administration tout à fait convenable !


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.