Développer une application avec le framework Play !


précédentsommairesuivant

IV. Construction du premier écran

Nous avons désormais notre premier jet du modèle de données. Il est temps de nous occuper de la création de la première page de l'application. Celle-ci sera dédiée à l'affichage des billets les plus récents, mais proposera également une liste des billets plus anciens.

Voici un brouillon de ce que nous souhaitons obtenir :

Image non disponible

IV-A. Amorçage avec des données par défaut

Avant de nous lancer corps et âme dans le codage de notre premier écran, nous devons réaliser une toute dernière chose. Travailler sur une application web sans données n'est pas vraiment fun. On ne peut même pas tester ce que l'on est en train de faire ! Hélas, nous n'avons pas encore développé les écrans d'écriture de billets, et de fait, nous ne pouvons alimenter la base nous-mêmes.

L'une des façons d'injecter des données par défaut est de charger un fichier Fixtures au démarrage de l'application. Pour cela, nous créons une tâche d'amorçage (bootstrap job). Une tâche Play est quelque chose qui s'exécute tout seul, sans aucun lien avec une quelconque requête HTTP, comme par exemple au démarrage de l'application, ou encore à intervalles donnés (via une planification CRON).

Créons la classe /yabe/app/Bootstrap.java qui va charger les données par défaut en utilisant Fixtures :

 
Sélectionnez
import play.*;
import play.jobs.*;
import play.test.*;
 
import models.*;
 
@OnApplicationStart
public class Bootstrap extends Job {
 
    public void doJob() {
        // Check if the database is empty
        if(User.count() == 0) {
            Fixtures.load("initial-data.yml");
        }
    }
 
}

Nous avons annoté notre classe avec @OnApplicationStart qui spécifie que nous voulons que Play exécute cette classe lors du démarrage de l'application.

Cette tâche va être exécutée différemment que l'on se trouve en mode de DEV ou en mode de PROD. En mode DEV, Play va en réalité attendre la première requête pour réellement démarrer l'application. Par conséquent, cette tâche ne sera exécutée qu'à partir du moment où notre serveur recevra sa première requête. Ainsi, dans l'hypothèse où cette tâche échoue, nous pourrons en être avertis au sein même de notre navigateur ! En mode PROD, la tâche sera exécutée au même moment que le démarrage de l'application, c'est-à-dire lors de l'exécution de la commande play run. En cas d'erreur, l'application ne sera pas démarrée.

Nous créons un fichier initial initial-data.yml dans le répertoire /yabe/conf. Bien entendu, nous pourrions réutiliser le fichier data.yml que nous avons créé précédemment.

Redémarrons l'application par la commande play run et rendons-nous à l'adresse http://localhost:9000.

IV-B. La page d'accueil du blog

Cette fois-ci est la bonne, nous pouvons démarrer le codage de la page d'accueil.

Souvenons-nous de la façon dont la toute première page était affichée. Le fichier de routage nous indiquait que l'URL "/" allait invoquer la méthode d'action controllers.Application.index(). Ensuite, cette méthode appelait la méthode parent render() et exécutait le template /yabe/app/views/Application/index.html.

Nous allons conserver ces composants, mais nous allons ajouter du code de façon à charger la liste des billets, et les faire afficher.

Ouvrons le fichier /yabe/app/controllers/Application.java et modifions la méthode index() afin de charger les billets :

 
Sélectionnez
package controllers;
 
import java.util.*;
 
import play.*;
import play.mvc.*;
 
import models.*;
 
public class Application extends Controller {
 
    public static void index() {
        Post frontPost = Post.find("order by postedAt desc").first();
        List<Post> olderPosts = Post.find("order by postedAt desc").from(1).fetch(10);
        render(frontPost, olderPosts);
    }
 
}

Remarquons la façon dont on passe les objets à la méthode de création du rendu. De cette façon, nous pouvons accéder à ces objets depuis le template en utilisant leurs noms. Dans notre exemple, les variables frontPost et olderPosts seront ainsi accessibles au sein du template.

Ouvrons le fichier /yabe/app/views/Application/index.html et modifions-le afin d'afficher ces objets :

 
Sélectionnez
#{extends 'main.html' /}
#{set title:'Home' /}
 
#{if frontPost}
    <div class="post">
        <h2 class="post-title">
            <a href="#">${frontPost.title}</a>
        </h2>
        <div class="post-metadata">
            <span class="post-author">by ${frontPost.author.fullname}</span>
            <span class="post-date">${frontPost.postedAt.format('MMM dd')}</span>
            <span class="post-comments">
                &nbsp;|&nbsp; 
                ${frontPost.comments.size() ?: 'no'} 
                comment${frontPost.comments.size().pluralize()}</a>
                #{if frontPost.comments}
                    , latest by ${frontPost.comments[0].author}
                #{/if}
            </span>
        </div>
        <div class="post-content">
            ${frontPost.content.nl2br()}
        </div>
    </div>
    
    #{if olderPosts.size() > 1}
        <div class="older-posts">    
            <h3>Older posts <span class="from">from this blog</span></h3>
        
            #{list items:olderPosts, as:'oldPost'}
                <div class="post">
                    <h2 class="post-title">
                        <a href="#">${oldPost.title}</a>
                    </h2>
                    <div class="post-metadata">
                        <span class="post-author">
                            by ${oldPost.author.fullname}
                        </span>
                        <span class="post-date">
                            ${oldPost.postedAt.format('dd MMM yy')}
                        </span>
                        <div class="post-comments">
                            ${oldPost.comments.size() ?: 'no'} 
                            comment${oldPost.comments.size().pluralize()}
                            #{if oldPost.comments}
                                - latest by ${oldPost.comments[0].author}
                            #{/if}
                        </div>
                    </div>
                </div>
            #{/list}
        </div>
        
    #{/if}
    
#{/if}
 
#{else}
    <div class="empty">
        There is currently nothing to read here.
    </div>
#{/else}

Si vous souhaitez en connaître d'avantage sur le langage du template, jetez un œil ici. Succinctement, cela nous permet d'accéder à nos objets Java de façon dynamique. Techniquement, c'est Groovy qui est utilisé. Certains éléments, tel que l'opérateur ?:, viennent du langage Groovy. Cependant, il n'est pas nécessaire d'apprendre Groovy pour écrire des templates Play. Si vous êtes déjà familiers avec des langages de template tels que JSP avec JSTL, vous ne devriez pas être trop perdus...

Rafraichissons maintenant notre page d'accueil de notre application :

Image non disponible

Avouons-le, le résultat n'est pas très joli, mais il a le mérite de fonctionner !

Vous avez peut-être remarqué que nous avons déjà commencer à dupliquer du code... Parce que nous souhaitons afficher les billets de différentes façons (complète, complète avec commentaires, résumé), il nous faudrait créer quelque chose comme une "fonction" que nous pourrions appeler depuis différents templates. C'est exactement ce que font les tags Play !

Pour créer un tag, il suffit de créer un fichier /yabe/app/views/tags/display.html. En réalité, un tag est tout simplement un autre template. Toutefois, ce tag peut prendre des paramètres, à l'instar d'une fonction. Dans notre cas, le tag #{display /} dispose de deux paramètres :

  • l'objet Post à afficher ;
  • le mode d'affichage qui peut valoir soit "home", soit "teaser", soit "full".
 
Sélectionnez
*{ Display a post in one of these modes: 'full', 'home' or 'teaser' }*
 
<div class="post ${_as == 'teaser' ? 'teaser' : ''}">
    <h2 class="post-title">
        <a href="#">${_post.title}</a>
    </h2>
    <div class="post-metadata">
        <span class="post-author">by ${_post.author.fullname}</span>,
        <span class="post-date">${_post.postedAt.format('dd MMM yy')}</span>
        #{if _as != 'full'}
            <span class="post-comments">
                &nbsp;|&nbsp; ${_post.comments.size() ?: 'no'} 
                comment${_post.comments.size().pluralize()}
                #{if _post.comments}
                    , latest by ${_post.comments[0].author}
                #{/if}
            </span>
        #{/if}
    </div>
    #{if _as != 'teaser'}
        <div class="post-content">
            <div class="about">Detail: </div>
            ${_post.content.nl2br()}
        </div>
    #{/if}
</div>
 
#{if _as == 'full'}
    <div class="comments">
        <h3>
            ${_post.comments.size() ?: 'no'} 
            comment${_post.comments.size().pluralize()}
        </h3>
        
        #{list items:_post.comments, as:'comment'}
            <div class="comment">
                <div class="comment-metadata">
                    <span class="comment-author">by ${comment.author},</span>
                    <span class="comment-date">
                        ${comment.postedAt.format('dd MMM yy')}
                    </span>
                </div>
                <div class="comment-content">
                    <div class="about">Detail: </div>
                    ${comment.content.escape().nl2br()}
                </div>
            </div>
        #{/list}
        
    </div>
#{/if}

Grâce à ce nouveau tag, nous pouvons revenir sur notre template principal et l'alléger :

 
Sélectionnez
#{extends 'main.html' /}
#{set title:'Home' /}
 
#{if frontPost}
    
    #{display post:frontPost, as:'home' /}
    
    #{if olderPosts.size()}
    
        <div class="older-posts">
            <h3>Older posts <span class="from">from this blog</span></h3>
        
            #{list items:olderPosts, as:'oldPost'}
                #{display post:oldPost, as:'teaser' /}
            #{/list}
        </div>
        
    #{/if}
    
#{/if}
 
#{else}
    <div class="empty">
        There is currently nothing to read here.
    </div>
#{/else}

Pour s'assurer que tout s'est bien passé, un rafraichissement de la page suffit.

IV-C. Améliorer la disposition

Comme nous le voyons, index.html étend le template main.html. Nous voulons proposer une disposition unique (un layout) pour toutes les pages de notre blog, avec un titre, des liens pour s'authentifier, etc. Nous devons donc modifier ce fichier (/yabe/app/views/main.html) :

 
Sélectionnez
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <title>#{get 'title' /}</title>		
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <link rel="stylesheet" type="text/css" media="screen" 
            href="@{'/public/stylesheets/main.css'}" />
        <link rel="shortcut icon" type="image/png" 
            href="@{'/public/images/favicon.png'}" />
    </head>
    <body>
        
        <div id="header">
            <div id="logo">
                yabe.
            </div>
            <ul id="tools">
                <li>
                    <a href="#">Log in to write something</a>
                </li>
            </ul>
            <div id="title">
                <span class="about">About this blog</span>
                <h1><a href="#">${blogTitle}</a></h1>
                <h2>${blogBaseline}</h2>
            </div>
        </div>
        
        <div id="main">
            #{doLayout /} 
        </div>
        
        <p id="footer">
            Yabe is a (not that) powerful blog engine built with the 
            <a href="http://www.playframework.org">play framework</a>
            as a tutorial application.
        </p>
        
    </body>
</html>

Une fois encore, rafraichissons la page et vérifions le résultat. Tout semble fonctionner, à l'exception de blogTitle et blogBaseLine qui ne sont pas affichés. Cela est dû au fait que nous n'avons pas donné ces valeurs lors de l'appel de la méthode render(...). Bien sûr, nous pourrions modifier notre classe Java pour les y ajouter. Toutefois, comme notre fichier main.html est considéré comme notre template principal pour toutes les pages, nous ne souhaitons pas ajouter les valeurs de ces variables à chaque fois.

L'une des façons d'ajouter un code commun pour chaque action d'un contrôleur (ou d'une hiérarchie de contrôleur) est d'ajouter un intercepteur @Before. Ajoutons la méthode addDefaults() dans notre contrôleur :

 
Sélectionnez
@Before
static void addDefaults() {
    renderArgs.put("blogTitle", Play.configuration.getProperty("blog.title"));
    renderArgs.put("blogBaseline", Play.configuration.getProperty("blog.baseline"));
}

Toutes les variables ajoutées au scope renderArgs sont accessibles au sein du template. Nous voyons également que la valeur de ces variables est directement tirée de l'objet Play.configuration. Cet objet contient toutes les propriétés qui sont définies dans le fichier /yabe/conf/application.conf. Par conséquent, il est nécessaire d'ajouter les lignes suivantes dans ce fichier :

 
Sélectionnez
# Configuration of the blog engine
# ~~~~~
blog.title=Yet another blog
blog.baseline=We won't write about anything

Rechargeons à nouveau notre page, et constatons l'effet désiré :

Image non disponible

IV-D. Ajoutons des styles

Notre page étant presque terminée, essayons de l'embellir un petit peu ! Nous allons ajouter des styles pour cela. Vous avez peut-être remarqué que le template main.html inclue le fichier CSS /public/stylesheets/main.css. Nous conservons ce fichier, mais nous allons le compléter.

Le fichier final peut être téléchargé ici. Remplacez l'ancien /public/stylsheets/main.css par celui-ci. Nous pouvons désormais rafraichir la page d'accueil pour voir le résultat :

Image non disponible

IV-E. Commitons !

Notre page d'accueil terminée, nous allons remonter (commiter) nos modifications :

 
Sélectionnez
> bzr st
> bzr add
> bzr commit -m 'Home page'

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.