Accueil > Non classé > La pagination facile avec Spring 3.x et jQuery.

La pagination facile avec Spring 3.x et jQuery.


Introduction

Qu’est ce que je veux faire ?

Mon objectif principal est la présentation de résultats de recherche sous forme de tableau avec gestion de la pagination. Du grand classique!

Quelles sont mes contraintes ?

  • Pour des raisons de performance et de bon sens, je veux que la pagination soit gérée côté serveur pour éviter de charger entièrement tous les résultats;
  • J’aimerais que la navigation entre les différentes pages de recherche soit fluide;
  • Enfin, j’aimerais en tant que développeur que ça marche et que ça soit relativement beau avec le minimum d’effort.

Comment je vais m’y prendre ?

Comme j’ai envie que ça soit beau sans faire trop d’effort, je pense très rapidement à jQuery.  Après une rapide recherche, j’opte pour un plugin très populaire de présentation de données tabulaires : dataTable. En effet, celui-ci permet en très peu de configuration de gérer la “présentation des résultats paginés” tout en déléguant la gestion de la pagination  au serveur : bingo!

Par contre, si j’opte pour cette solution les résultats à afficher devront être récupérés grâce à des requêtes AJAX. D’un côté ça m’arrange car ça me permet de coller à ma deuxième contrainte, qui était de rendre la navigation plus fluide;  mais d’un autre côté, ça m’embête un peu parce que je n’ai pas envie de rentrer dans les problématiques techniques d’AJAX.

Je fais donc ma petite enquête et me rends compte que Spring 3.x apporte un support intéressant pour AJAX censé faciliter la vie du développeur. Spring et jQuery, je signe sans hésitation et me lance dans l’aventure.

Présentation de l’exemple

Avant de commencer les choses sérieuses, voici un aperçu de la page des résultats paginés :

Recherche Articles

  • L’exemple porte sur un écran de recherche d’articles de presse.  Un article possède un titre, un auteur, une date de publication et appartient à une rubrique;
  • L’application “exemple” utilise Spring MVC (version 3.x) comme framework de présentation. J’utiliserai essentiellement les annotations comme élément de configuration (pour les contrôleurs notamment). Par souci de lisibilité, je n’aborderai pas  la configuration élémentaire de Spring MVC. Pour plus d’information concernant Spring MVC se référer à cette page.
  • Dans le même esprit, je vais essentiellement me concentrer sur les éléments relatifs à la pagination en faisant l’impasse sur certaines “bonnes pratiques”. Ainsi les articles de presse seront stockés dans une liste static en mémoire et pas dans une base de données relationnelle avec gestion des transactions et tout le tralala. Amis puristes, vous êtes prévenus ;-)
  • Les fragments de code présentés par la suite servent uniquement de support et sont intentionnellement incomplets. Vous pouvez toujours consulter les sources jointes à cet article pour avoir plus de détails.

Fonctionnement général

Voici un diagramme qui présente le fonctionnement général de l’affichage des résultats paginés :

 

 

[1] : dataTable envoie une requête AJAX au contrôleur Spring qui contient à la fois les données du formulaire de recherche ainsi que certaines informations nécessaires à la pagination comme l’index du premier prochain résultat (iDisplayStart) et le nombre maximum de résultats que l’on veut afficher par page.

[2] [3] Le contrôleur récupère le nombre total de résultats correspondant aux critères de recherche (sans prendre en compte la pagination) et récupère par la suite uniquement les résultats à afficher sur une page : il se sert pour cela des informations de pagination envoyées dans la requête AJAX.

[4] Spring retourne une réponse JSON contenant toutes les informations nécessaires à l’affichage des résultats.

[5] dataTable construit et affiche le tableau des résultats.

Mise en place

Coté Spring…

Dans l’introduction, j’ai mentionné le fait que Spring 3.x possédait un meilleur support pour AJAX. Ce n’est pas tout à fait juste car ce support n’est pas exclusivement destiné à AJAX même s’il en facilite grandement l’utilisation. Voici les principaux protagonistes :

  • HttpMessageConverter: Dans le protocole HTTP le client et le serveur communiquent en échangeant des données textuelles. Cependant dans les méthodes des contrôleurs Spring, on peut avoir un objet d’un type spécifique comme argument ou comme type de retour. La sérialisation/dé-sérialisation de l’objet en/à partir d’un format textuel est réalisé par les objets implémentant l’interface HttpMessageConverter.
  •  MappingJacksonHttpMessageConverter: En ce qui nous concerne, on aimerait bien avoir un objet qui convertisse  les requêtes contenant du JSON en un javabean et vice versa. C’est ce que permet de faire la classe MappingJacksonHttpMessageConverter. Cette implémentation de HttpMessageConverter est basée sur un framework puissant de gestion du format JSON : Jackson.
  • @RequestBody : Placée sur un paramètre d’une méthode, cette annotation permet de convertir le corps d’une requête (body) HTTP en un type Java donné en utilisant le HttpMessageConverter approprié (fonction du type mime).
  • @ResponseBody : Placée sur une méthode, cette annotation indique qu’il faut écrire directement l’objet de retour dans la réponse et non dans le modèle de la vue.  En fonction du type mime attendu, l’objet retourné sera converti dans le format approprié par le HttpMessageConverter associé au type mime. Dans notre cas, le type mime est application/json  et MappingJacksonHttpMessageConverter convertit l’objet de retour en texte au format JSON.

Pour pouvoir utiliser MappingJacksonHttpMessageConverter couplé avec @RequestBody et @ResponseBody il faut rajouter quelques éléments de configuration à Spring MVC :

  1. Utiliser l’élement <mvc:annotation-driven/> dans le fichier xml de configuration de Spring. Ceci permet de configurer (entre autre) les différentes implémentations de HttpMessageConverter dont celle qui nous intéresse à savoir : MappingJacksonHttpMessageConverter.
  2. Rajouter la dépendance : jackson-databind.jar (et toutes les dépendances transitives ). Avec Maven ça donne :

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.1.1</version>
</dependency>

Une fois la configuration mise en place, i l ne reste plus qu’à implémenter le contrôleur dont voici les principaux éléments:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Controller
@RequestMapping("articles/search.htm")
public class ArticleController {

    ...

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public ArticleReponseJson search(@ModelAttribute(COMMAND_NAME)ArticleSearchBean searchBean,
                                     @RequestBody MultiValueMap<String, String>parametresAjax) {

        int count = articleService.count(searchBean);
        Integer debut = getIntFirstValue(parametresAjax, "iDisplayStart");
        Integer nbElements = getIntFirstValue(parametresAjax, "iDisplayLength");
        List<Article> listeArticles = articleService.search(searchBean, debut, nbElements);
        return prepareJsonResponse(listeArticles, count, parametresAjax);
    }

    private ArticleReponseJson prepareJsonResponse(List<Article> articles, int count,
                                                    MultiValueMap<String, String> parametresAjax) {

        ArticleReponseJson reponseJson = new ArticleReponseJson();
        reponseJson.setsEcho(getIntFirstValue(parametresAjax, "sEcho"));
        reponseJson.setiTotalDisplayRecords(count);
        reponseJson.setiTotalRecords(count);
        reponseJson.setAaData(articles);
        return reponseJson;
    }

    private Integer getIntFirstValue(MultiValueMap<String, String> parametres, String key) {

        Integer res = null;
        String s = parametres.getFirst(key);
        if (StringUtils.isNotEmpty(s)) {
            res = Integer.parseInt(s);
        }
        return res;
    }

    ...
}

Concernant le contrôleur, on peut noter les éléments suivants :

  • La méthode est annotée par @ResponseBody afin que l’objet de retour soit directement sérialisé dans la réponse. Grâce à la configuration ci-dessus, l’objet sera sérialisé au format JSON avant d’être écrit dans la réponse.
  • Les critères de recherche sont récupérés dans l’objet de commande ArticleSearchBean.
  • Un des paramètres est annoté par @RequestBody ce qui permet de récupérer les paramètres au format JSON passés dans la requête (ceux qui seront envoyés par  dataTable pour gérer la pagination). On les récupère dans une MultiValueMap (extension d’une Map proposée par Spring permettant de stocker plusieurs valeurs pour une clé). Encore une fois, ceci est possible grâce à l’intervention “implicite” de MappingJacksonHttpMessageConverter.

  • Une fois le nombre de résultats total et la liste des articles à afficher récupérés du service, on initialise un bean de type ArticleReponseJson  qui sera sérialisé pour former la réponse JSON :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArticleReponseJson {

    /* Parametre de controle */
    private Integer sEcho;

    /* Nombre total de résultats correspondant aux critères de recherche */
    private Integer iTotalDisplayRecords;

    /* Liste des resultats
       Par défaut dataTable cherche les résultats dans une propriété nommée aaData.
       On peut configurer le nom de la propriété.
    */

    private List<Article> aaData = new ArrayList<Article>();

    ...

}

… puis coté jQuery…

Le plugin dataTable est très configurable au point de s’y perdre. Je vais présenter une configuration “très simple” qui met uniquement en évidence les propriétés relatives à la pagination.  Il faut toutefois garder à l’esprit que dataTable offre beaucoup de possibilités comme le filtrage des résultats par mot clé, le tri et bien d’autres encore.

Ceci étant dit, voici la configuration que j’ai retenue :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function search(){
$("#resultats").dataTable({
    "bFilter" : false,
    "bInfo" : false,
    "bSort" : false,
    "sDom" : '<"#top"p>rt<"#bottom"p>',
    "oLanguage" : {
        "oPaginate" : {
            "sFirst" : "<<",
            "sLast" : ">>",
            "sNext" : ">",
            "sPrevious" : "<"
        }
    },
    "sPaginationType" : "full_numbers", //Style de la pagination
    "iDisplayLength" : 5, //Résultats affichés 5 par 5
    "bServerSide" : true, //Pagination gérée coté serveur
    "bProcessing" : true,//Affichage d'une icone pendant le chargement
    "bDestroy" : true,//La grille d'affichage est détruite avant chaque chargement
    "sAjaxSource" : "search.htm", //URL de recupération des résultats
    "fnServerData" : function(sSource, aoData, fnCallback) { // Définition de la requete AJAX
        $.ajax({
            "dataType" : 'json',
            "type" : "POST",
            "url" : sSource,
            "data" : aoData,
            "success" : fnCallback,
            "timeout" : 30000
        });
    },
    "fnServerParams" : function(aoData) { // Paramètres supplémentaires à ajouter au corps du POST
        $.each($("#form-search").serializeArray(), //Données du formulaire
                function(index, elt) {
                    aoData.push(elt);
                });
    },
    "aoColumns" : [ { //Définition des colonnes à afficher
        "mDataProp" : "titre"
    }, {
        "mDataProp" : "auteur"
    }, {
        "mDataProp" : "rubrique"
    }, {
        "mDataProp" : "datePublication"
    }
    ]
});
}

J’ai pris le soin de commenter les lignes relatives à la pagination. Pour une description plus détaillée des différentes options, veuillez vous référer à la documentation officielle du plugin dataTable.

et enfin coté JSP.


<html>
<head>
    <script type="text/javascript">
        $(document).ready(function(){
            search();

        });
    </script>
</head>
<body>
    <div id="contenu">
    <form:form id="form-search" commandName="searchBean" method="POST" >
        ...
    </form:form>

    <table id="resultats" width="100%">
        <caption>Liste des articles</caption>
        <thead>
            <tr>
                <th>Titre</th>
                <th>Auteur</th>
                <th>Catégorie</th>
                <th>Date de publication</th>
            </tr>
        </thead>
        <tbody>
            <!-- Contenu géré par dataTable -->
        </tbody>
    </table>

    </div>
</body>

On notera que seul le “squelette” du tableau a été déclaré dans la JSP : en effet, le contenu de la balise tbody est vide car c’est à la charge de dataTable de construire le contenu du tableau avec les résultats de recherches.

Pour aller plus loin

Comme mentionné plus haut, MappingJacksonHttpMessageConverter utilise en coulisse une librairie de gestion de JSON nommé Jackson. Celle-ci permet de configurer finement le mapping entre un champ JSON et une propriété Java. En ce qui nous concerne, on voudrait bien que la date de publication d’un article (de type java.util.Date) soit affichée dans un format lisible (ex : 2012-12-25). Ceci peut être réalisé en annotant la propriété datePublication de la classe Article par @JsonFormat(“yyyy-MM-dd”)


public class Article {
...

    @JsonFormat(pattern = "yyyy-MM-dd")
    public Date getDatePublication() {
        return datePublication;
    }

...
}

Liens :

Share
  1. Issam
    19/03/2014 à 11:58 | #1

    Salut Mohamed,

    Excellent Tuto ! merci infiniment.

    A+

  1. Pas encore de trackbacks