• 30 minutes
  • Moyenne
Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Introduction du cours

Bonjour à tous!

Si vous lisez cet article, c'est que vous vous déjà demandé comment réaliser une système de pagination en Java ou alors, vous vous demandez ce que c'est qu'un système de pagination. Cela tombe bien puisque cet article a été conçu pour pouvoir vous proposer un élément de réponse.:D

Introduction et définitions

Un peu de vocabulaire

En français le mot pagination signifie "numérotation des pages d'un livre". Rien de surprenant vu que ce mot dérive du mot page. Le verbe paginer qui est de la même famille signifie "numéroter les pages d'un livre". Jusque-là, il n'existe pas de rapport direct avec le domaine informatique.Mais en français, il existe un autre mot qui permettra cette fois d'établir une relation sémantiquement logique. Ce mot est paginable. Paginable  est un adjectif qualificatif qui permet de signifier qu'un élément peut être organisé sous forme de pages numérotées.

Et où est donc la relation ?

Il arrive parfois, lorsque nous listons des données dans nos systèmes, que la liste soit très
longue. Cela entraine en général un effet touffu et par conséquent les données sont quasiment illisibles. Un génie a alors établi que notre liste était paginable conformément à la définition du mot paginable. Autrement dit, que l'on pouvait organiser notre liste en plusieurs pages et ainsi limiter le nombre maximum de données affichées à la fois.:magicien: Dès ce moment le mot pagination a eu le droit d'être utilisé dans un contexte purement informatique.

Dans le contexte informatique, paginer signifie donc subdiviser une liste de données sous forme de pages dans l'optique d'améliorer la lisibilité lors de l'affichage. Le système de pagination regroupe alors l'ensemble des données et des fonctions permettant de paginer nos listes.

Tout au long de cet article, je vais vous montrer comment créer un système de pagination en Java. Sachez que dans presque tous les langages dynamiques, il est possible de créer un système de pagination.

Maintenant que vous connaissez ce que nous voulons réaliser, voyons tous ensemble les caractéristiques du système que nous allons réaliser.

Pour ceux qui n'ont pas encore vu à quoi ressemble le panneau de commande d'un système de pagination, voici quelques aperçus:

Panneau de commandes du système de pagination que nous allons réaliser.
Panneau de commandes du système de pagination que nous allons réaliser.
Panneau de commandes du système de pagination de Joomla 2.5
Panneau de commandes du système de pagination de Joomla 2.5

Ce que nous voulons réaliser

Le système de pagination que nous allons réaliser devra naturellement permettre de subdiviser
une liste de données en plusieurs pages. Il devra pouvoir à chaque fois nous fournir les données de la page actuelle. On devrait aussi pouvoir connaitre toutes les informations de la page actuellement affichée, le nombre total de pages disponible.

Notre système de pagination ne se souciera pas de l'affichage des données. De cette manière, on pourra le réutiliser dans n'importe quel projet.>_<

Notre système devra fournir un panneau de commande. Le panneau de commande sera identique à celui de la figure ci-dessous:

Ce que nous allons réaliser.
Ce que nous allons réaliser.

Les boutons de navigation seront gérés par le système de pagination.

Concevons notre système

Notre système de pagination sera constitué de deux éléments principaux rangés dans le paquetage miu.openc.pagination :

Une classe générique nommée PaginationPanel : Cette classe est le modèle du panneau de commande de notre système de pagination. C'est dans cette classe que nous allons coder toutes les actions de notre système de pagination. Autrement dit, cette classe va encapsuler la liste à paginer puis va juste fournir la page nécessaire à un instant donné.

Une interface générique nommée PaginationObserver : C'est grâce à cette interface que l'application pourra communiquer avec notre système de pagination et être ainsi au courant des changements de l'utilisateur. C'est le design pattern Observer que nous mettons en œuvre ici.

Voici un modèle UML de nos deux éléments principaux:

Diagramme UML de notre classe et de notre interface.
Diagramme UML de notre classe et de notre interface.

L'interface PaginationObserver

Cette interface possède une seule méthode : La méthode update(). C'est grâce à cette méthode que les observateurs du système de pagination seront informés des changements (de
page, de données, etc..). Le paramètre data:List<T> de cette méthode représente la liste de données qui est valide suivant les spécifications actuelles de l'utilisateur.

La classe PaginationPanel

Ce diagramme ne le montre pas mais la classe PaginationPanel hérite de la classe JPanel.:euh: Elle représente le panneau de commande de notre système de pagination. Nous allons donc réaliser un système de pagination compatible Swing. Mais on aurait pu faire mieux. Faisons un tour d'horizon de ses attributs et méthodes:

Les attributs

  • filterObserverList : Cet attribut est une liste des observateurs de ce système de pagination. Ce sont ces observateurs qui recevront des notifications lors des différents changements.

  • data : Cet attribut représente la liste originale des données à paginer. Vous remarquerez que cet attribut est de type java.util.List et que ses données doivent de type T. Le type T étant le paramètre générique que nous avons définis précédemment.

  • pageDisplayer : C'est un JLabel qui permettra d'afficher à chaque fois le numéro de la page actuellement visitée et le nombre total de pages disponible.

  • label : C'est un JLabel qui nous permettra de réaliser diverses tâches.

  • displayRange : C'est une liste déroulante qui contiendra les différentes structures de pages. C'est grâce à cette liste que l'utilisateur pourra décider du nombre d'éléments à afficher par page.

  • first, prev, next et last sont les différents boutons de navigation.

  • totalPages : Cet attribut contient le nombre total de pages disponible.

  • currentPage : Cet autre attribut contient le numéro de la page courante.

Les méthodes

Les méthodes addPaginationObserver() et removePaginationObserver()

Les méthodes addPaginationObserver() et removePaginationObserver() sont deux méthodes qui permettent respectivement d'ajouter et de retirer un observateur à la liste des observateurs de ce système de pagination.

La méthode initComponents()

Cette méthode privée ne sera appelée qu'une seule fois, lors de l'instanciation de la classe PaginationPanel. C'est dans cette méthode que nous allons initialiser tous les éléments de ce système de pagination.

Les méthodes next(), previous(), toStart() et toEnd()

La méthode next() sera exécutée lorsque l'utilisateur cliquera sur le bouton next. De même les méthodes previous(), toStart() et toEnd() seront exécutées lorsque l'utilisateur aura cliqué respectivement sur les boutons prev, first et last. Ces méthodes permettront d'afficher la page suivante, la page précédente, la première page et la dernière page respectivement.

La méthode updateComponent()

Cette méthode va permettre de mettre à jour tous les éléments du panneau de commande de notre système.

Les méthodes calculate(), reset() et notify()

La méthode calculate() va permettre de calculer le nombre total de pages en fonction du nombre d'éléments à afficher par page et du nombre total de données.

La méthode reset() va permettre de réinitialiser (changer) la liste des données à ranger sous forme de pages.

Il existe une surcharge de la méthode reset() qui ne prend aucun paramètre. Cette surcharge aura pour rôle de rafraichir le système et de se replacer sur la première page.

La méthode notify() va se charger d'informer tous les observateurs de la présence d'une nouvelle liste à afficher.

La méthode getCurrentData()

Cette méthode renverra un sous ensemble de la liste de données disponibles. La liste renvoyée correspondra à la liste des données à afficher pour la page courante.

Pour que notre panneau de commande ressemble à un composant digne de ce nom, il nous faut des images.>_< Vous pouvez donc télécharger celles-ci. Placez ces images dans le même paquetage (miu.openc.pagination) que nos différents éléments.

Maintenant que tout est en place, il ne nous reste plus qu'à coder.

Codons notre système

Nous allons à présent implémenter l'interface PaginationObserver et la  classePaginationPanel que nous avons conçu précédemment.

L'interface PaginationObserver

Tel un algorithme vorace, nous dévorons d'abord le morceau le plus alléchant. L'interface PaginationObserver ne contient qu'une seule et unique méthode. Nous avons déjà expliqué son rôle précédemment. Voici donc le code de notre interface:

package miu.openc.pagination;
import java.util.List;

public interface PaginationObserver<T> {
    public void update(List<T> data);
}

Je vous l'avais dit que c'était le morceau le plus alléchant.:honte:

La classe PaginationPanel

Je vais commencer par vous donner le code complet de cette classe puis nous décortiquer pas à pas les différents éléments.

package miu.openc.pagination;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class PaginationPanel<T> extends JPanel{
    //Attributs
    private List<PaginationObserver> filterObserverList;
    private List<T> data;
    private JLabel pageDisplayer, label;
    private JComboBox displayRange;
    private JButton first, prev, next, last;
    private int totalPages, currentPage;
    private ActionListener listener;

    //Constructeur
    public PaginationPanel(List<T> data) {
        this.data = data;
        filterObserverList = new ArrayList<PaginationObserver>();
        this.setPreferredSize(new Dimension(350, 50));
        this.setOpaque(false);
        
        this.initComponents();
    }

    private void initComponents() {
        listener = new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                String command = e.getActionCommand();
                if(command.equals("next"))
                    next();
                if(command.equals("prev"))
                    previous();
                if(command.equals("first"))
                    toStart();
                if(command.equals("last"))
                    toEnd();
                if(command.equals("range")){
                    try{
                        reset(data);
                    }catch(Exception ex){
                        
                    }
                }
            }
            
        };
        label = new JLabel("#Par page");
        label.setPreferredSize(new Dimension(65, 30));
        this.add(label);
        
        displayRange = new JComboBox();
        displayRange.addItem(5);
        displayRange.addItem(10);
        displayRange.addItem(15);
        displayRange.addItem(20);
        displayRange.addItem(50);
        displayRange.addItem(100);
        displayRange.addItem("Tout");
        displayRange.setPreferredSize(new Dimension(60, 25));
        displayRange.setActionCommand("range");
        displayRange.addActionListener(listener);
        this.add(displayRange);
        
        label = new JLabel("");
        label.setPreferredSize(new Dimension(35, 30));
        this.add(label);
        
        first = new JButton();
        first.setPreferredSize(new Dimension(25, 25));
        first.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/first.png")));
        first.setBorderPainted(false);
        first.setFocusPainted(false);
        first.setActionCommand("first");
        first.addActionListener(listener);
        first.setContentAreaFilled(false);
        this.add(first);
        
        prev = new JButton();
        prev.setPreferredSize(new Dimension(25, 25));
        prev.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/prev.png")));
        prev.setBorderPainted(false);
        prev.setFocusPainted(false);
        prev.setActionCommand("prev");
        prev.addActionListener(listener);
        prev.setContentAreaFilled(false);
        this.add(prev);
        
        pageDisplayer = new JLabel(currentPage+"/"+totalPages);
        pageDisplayer.setOpaque(true);
        pageDisplayer.setBackground(new Color(250, 250, 250));
        pageDisplayer.setBorder(BorderFactory.createEtchedBorder());
        pageDisplayer.setHorizontalAlignment(JLabel.CENTER);
        pageDisplayer.setPreferredSize(new Dimension(60, 30));
        this.add(pageDisplayer);
        
        next = new JButton();
        next.setPreferredSize(new Dimension(25, 25));
        next.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/next.png")));
        next.setBorderPainted(false);
        next.setFocusPainted(false);
        next.setActionCommand("next");
        next.addActionListener(listener);
        next.setContentAreaFilled(false);
        this.add(next);
        
        last = new JButton();
        last.setPreferredSize(new Dimension(25, 25));
        last.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/last.png")));
        last.setBorderPainted(false);
        last.setFocusPainted(false);
        last.setActionCommand("last");
        last.addActionListener(listener);
        last.setContentAreaFilled(false);
        this.add(last);
    }
    
    public void addPaginationObserver(PaginationObserver obs){
        filterObserverList.add(obs);
    }
    
    public void removePaginationObserver(PaginationObserver obs){
        filterObserverList.remove(obs);
    }
    
    public void next(){
        calculate();
        if(currentPage < totalPages){
            currentPage++;
            notify(getCurrentData());
            updateComponent();
        }
    }
    
    public void previous(){
        calculate();
        if(currentPage > 1){
            currentPage--;
            notify(getCurrentData());
            updateComponent();
        }
    }
    
    public void toStart(){
        calculate();
        if(currentPage != 1){
            currentPage = 1;
            notify(getCurrentData());
            updateComponent();
        }
    }
    
    public void toEnd(){
        calculate();
        if(currentPage != totalPages){
            currentPage = totalPages;
            notify(getCurrentData());
            updateComponent();
        }
    }
    
    public void updateComponent(){
        if(currentPage == 1){
            prev.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/prev_dea.png")));
            first.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/first_dea.png")));
            if(totalPages > 1){
                next.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/next.png")));
                last.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/last.png")));
            }else{
                next.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/next_dea.png")));
                last.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/last_dea.png")));
            }
        }else{
            if(currentPage == totalPages){
                next.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/next_dea.png")));
                last.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/last_dea.png")));
                if(totalPages > 1){
                    prev.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/prev.png")));
                    first.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/first.png")));
                }else{
                    prev.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/prev_dea.png")));
                    first.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/first_dea.png")));
                }
            }else{
                prev.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/prev.png")));
                first.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/first.png")));
                next.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/next.png")));
                last.setIcon(new ImageIcon(getClass().getResource("/miu/openc/pagination/last.png")));
            }
        }
        pageDisplayer.setText(currentPage+"/"+totalPages);
    }
    
    private void notify(List<T> data){
        for(PaginationObserver obs : filterObserverList)
            obs.update(data);
    }
    
    public void reset(List<T> data){
        this.data = data;
        currentPage = 1;
        calculate();
        notify(getCurrentData());
        updateComponent();
    }
    
    public void reset(){
        currentPage = 1;
        calculate();
        notify(getCurrentData());
        updateComponent();
    }
    
    private void calculate(){
        if(displayRange.getSelectedIndex() == 6)
            totalPages = 1;
        else{
            try{
                totalPages = Math.round(data.size() / Integer.parseInt(displayRange.getSelectedItem().toString()));
                if(data.size() > Integer.parseInt(displayRange.getSelectedItem().toString()) * totalPages)
                    totalPages++;
                if(totalPages == 0)
                    totalPages++;
            }catch(Exception e){
                totalPages = 1;
            }
        }
    }
    
    private List<T> getCurrentData(){
        if(totalPages > currentPage)
            return (data.subList((currentPage - 1) * Integer.parseInt(displayRange.getSelectedItem().toString()), 
                    (currentPage) * Integer.parseInt(displayRange.getSelectedItem().toString())));
        else{
            if(totalPages == 1)
                return data;
            else
                return (data.subList((currentPage - 1) * Integer.parseInt(displayRange.getSelectedItem().toString()), 
                    data.size()));
        }
    }
    
    public int getCurrentPage() {
        return currentPage;
    }

    public List<T> getData() {
        return data;
    }

    public int getTotalPages() {
        return totalPages;
    }
}

L'initialisation

Dans notre constructeur, nous initialisons la liste de données à paginer avec la liste prise en paramètre. Ensuite nous créons une nouvelle liste d'observateurs et nous passons la main à la méthode initComponents().

Dans la méthode initComponents(), nous initialisons notre seul écouteur. Cet écouteur écoutera tous les éléments de notre panneau de commande. Par la suite nous créons et nous affichons les différents éléments de l'IHM de notre panneau de commande.

Les méthodes next(), previous(), toStart() et toEnd()

Dans toutes ces méthodes, nous calculons le nombre de pages disponible, nous faisons varier le numéro de la page courante suivant les cas et ensuite nous informons tous les observateurs du changement de statut en leur fournissant la liste de données pour la page actuellement affichée. Ces méthodes se terminent en mettant à jour les composants de notre panneau de commandes.

La méthode updateComponent()

La mise à jour des composants de notre panneau de commandes consiste au changement des images des différents boutons (suivant que c'est nécessaire ou non). Nous mettons aussi à jour le numéro de la page actuelle.

Les méthodes notify() et reset()

Dans la méthode notify(), nous informons tous les observateurs en leur envoyant le message leur demandant de se mettre à jour.

Dans la méthode reset() nous changeons toute la liste de données à paginer puis nous nous replaçons sur la première page. Dans la méthode reset() sans paramètre, nous nous replaçons juste sur la première page.

Les méthodes calculate() et getCurrentData()

Dans ces méthodes, nous réalisons des petits calculs pour trouver le nombre de pages disponible et la plage de données à afficher respectivement.

Je vous conseille de bien regarder le code de chacune de ces méthodes afin de bien comprendre ce que nous réalisons.:p

Testons notre système

Pour pouvoir tester notre joli système de pagination je vous ai fourni une classe nommée TestPagination. Vous retrouverez le code de cette classe ci-dessous :

package miu.openc.pagination;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class TestPagination extends JFrame{
    //Notre Panneau de pagination
    private PaginationPanel paginationPanel;
    //Un observateur
    private PaginationObserver paginationObserver;
    //Le panneau qui va afficher les données et le panneau principal
    private JPanel dataLayer, contentPane;
    
    public TestPagination(){
        this.setTitle("Test du système de pagination");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(400, 400);
        this.setResizable(false);
        this.setLocationRelativeTo(null);
        this.initComponents();
    }

    private void initComponents() {
        dataLayer = new JPanel();
        
        contentPane = new JPanel();
        contentPane.setLayout(new BorderLayout());
        
        /*
         * Construction de notre système de pagination pour la liste
         * fournie par la méthode getList()
         */
        paginationPanel = new PaginationPanel<String>(getList());
        //Instanciation avec classe anonyme de notre observateur
        paginationObserver = new PaginationObserver<String>(){

            /*
             * Implémentation de la méthode update de l'interface
             */
            @Override
            public void update(List<String> data) {
                dataLayer.removeAll();
                dataLayer.repaint();
                dataLayer.setPreferredSize(new Dimension(360, data.size()*30));
                JLabel label;
                for(String st : data){
                    label = new JLabel(st);
                    label.setPreferredSize(new Dimension(360, 25));
                    label.setBorder(BorderFactory.createEtchedBorder());
                    dataLayer.add(label);
                }
                dataLayer.repaint();
                dataLayer.updateUI();
            }
            
        };
        //Ajout de l'observateur
        paginationPanel.addPaginationObserver(paginationObserver);
        
        contentPane.add(new JScrollPane(dataLayer));
        contentPane.add(paginationPanel, BorderLayout.SOUTH);
        this.setContentPane(contentPane);
        paginationPanel.reset();
    }
    
    /*
     * Création d'une liste de données à paginer
     */
    private List<String> getList(){
        ArrayList<String> list = new ArrayList<String>();
        for(int i = 1; i <= 500; i++)
            list.add(i + " - Element numéro " + i + " de la liste");
        return list;
    }
    
    public static void main(String[] args){
        TestPagination testView = new TestPagination();
        testView.setVisible(true);
    }
}

Et voici ce que nous obtenons :

Notre système de pagination en image.
Notre système de pagination en image.

Comme vous pouvez le constater, l'utilisation de notre système de pagination est plutôt très intuitive et simple.;) Les avantages d'un tel système sont très nombreux. Vous pouvez l'utiliser dans n'importe quel projet et c'est vous qui décidez de la façon avec laquelle vous affichez vos données. C'est à présent l'heure des perspectives.

Les perspectives

Le système que nous avons mis sur pieds marche certainement mais vous pouvez l'améliorer. Voici quelques idées d'amélioration:

  • Proposer plusieurs méthodes pour la personnalisation de l'IHM du panneau de commandes;

  • Proposer même un ensemble de template pour cet IHM;

  • Adapter le composant pour les projets utilisant AWT comme bibliothèque d'IHM;

  • Adapter le système pour l'utilisation d'autres types de collection;

Vous pouvez ajouter vos idées à celles-ci pour pouvoir réaliser un système de pagination que vous transformerez peut être en Bibliothèque plus tard. Vous pouvez en faire le projet de toute une vie.:p

Conclusion

C'est ici que s'achève cet article. Merci de l'avoir lu ! J'espère que vous l'avez apprécié. N'oubliez pas de me faire parvenir toutes vos remarques et suggestions par message privé.

Exemple de certificat de réussite
Exemple de certificat de réussite