Présentation
Dans quasiment toutes les applications web J2EE que nous devons développer, il nous a été demandé d’afficher une série d’enregistrements provenant d’une base de données ou une liste d’objets sous forme de tableaux (balise <table>). Il faut pour cela itérer sur la liste, gérer la première ligne d’entête du tableau et assurer la pagination. Cela devient vite lourd et répétitif.
C’est pourquoi j’aimerai vous présenter la bibliothèque DisplayTag qui offre une solution rapide, pratique et efficace à ce problème. Cette Taglib offre un mécanisme très simple d’itération sur une collection, celui-ci s’exécutant directement depuis le code d’une JSP. Le résultat donne l’affichage d’un tableau html avec une quantité d’options telles que les tris sur les colonnes, la pagination, le regroupement de données ou encore l’export du tableau aux formats csv, excel, xml, pdf ou rtf.
L’exemple décrit dans cette article porte sur l’ajaxification de DisplayTag avec Struts mais cela reste simple à porter vers DWR : la rubrique « Sources » plus bas vous donnera un exemple pour les deux.
Problème
Il est plus convivial d’actualiser uniquement les données de nos tableaux, en utilisation la technologie Ajax (voir précédent article : Ajax, comment a-t’il révolutionné le Web), pourtant la bibliothèque DisplayTag ne le permet pas, elle fait une requête au contrôleur pour chacune des actions qui lui sont permises comme le tri et la pagination, cela se traduisant par un rafraîchissement complet de la page.
Nous allons donc dans cet article vous proposer une solution afin de contourner ce problème en mettant en place une bibliothèque de balises JSP : une TagLib. Celle-ci nous évitera le rafraîchissement de la page entière et permettra de rafraîchir uniquement le tableau.
Contournement
Le but de l’exercice consiste donc à changer dynamiquement le code source généré par DisplayTag, plus précisément transformer les liens <a href= »URI?page=1&sort=3… »> en des liens <a href= »javascript:methodName(page:1, sort:3…). La méthode ‘methodName’ utilise Ajax dans l’appel de son action Struts afin de récupérer le nouveau tableau généré.
Je vous renvoie à mon article précédent pour comprendre ce mécanisme : Struts, comment récupérer nos JSP en Ajax.
- écriture de la TagLib qui va permettre cette manipulation du code source généré (fichier customization.tld à déposer dans /WEB-INF/tld). Cet TagLib va utiliser le parseur HTML Jericho, prévoyez donc d’inclure ce jar dans votre projet.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <!-- ============== Tag Library Description Elements ============= --> <tlibversion>1.0</tlibversion> <jspversion>1.1</jspversion> <!-- ===================== Control Flow Tags ====================== --> <tag> <name>displayTag</name> <tagclass>fr.geeks.presentation.taglib.CustomDisplayTag</tagclass> <bodycontent>JSP</bodycontent> <attribute> <name>pagerMethodName</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
– classe CustomDisplayTag
package fr.geeks.presentation.taglib;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyTagSupport;
import au.id.jericho.lib.html.Attribute;
import au.id.jericho.lib.html.Attributes;
import au.id.jericho.lib.html.Element;
import au.id.jericho.lib.html.HTMLElementName;
import au.id.jericho.lib.html.OutputDocument;
import au.id.jericho.lib.html.Segment;
import au.id.jericho.lib.html.Source;
import au.id.jericho.lib.html.StartTag;
/**
* Utilise le parseur HTML Jericho pour récupérer le code source généré par DisplayTag et le remanier
*/
public class CustomDisplayTag extends BodyTagSupport {
/** method name of pager */
private String pagerMethodName;
/**
* (non-Javadoc)
*
* @see javax.servlet.jsp.tagext.Tag#doEndTag()
*/
public int doEndTag() throws JspException {
JspWriter out = pageContext.getOut();
String body = bodyContent.getString();
Source source = new Source(body);
OutputDocument outputDocument = new OutputDocument(source);
// interception des liens de pagination
List spanElements = source.findAllElements(HTMLElementName.SPAN);
Iterator iterSpanElements = spanElements.iterator();
while (iterSpanElements.hasNext()) {
Element spanElement = (Element) iterSpanElements.next();
Attributes spanAttributes = spanElement.getAttributes();
Attribute classAttribute = spanAttributes.get("class");
if (classAttribute != null && classAttribute.getValue() != null && classAttribute.getValue().equalsIgnoreCase("pagelinks")) {
rewriteAnchors(spanElement, outputDocument);
}
}
// interception des liens de tri
List thElements = source.findAllElements(HTMLElementName.TH);
Iterator iterThElements = thElements.iterator();
while (iterThElements.hasNext()) {
Element thElement = (Element) iterThElements.next();
Attributes spanAttributes = thElement.getAttributes();
Attribute classAttribute = spanAttributes.get("class");
if (classAttribute != null && classAttribute.getValue() != null && classAttribute.getValue().contains("sortable")) {
rewriteAnchors(thElement, outputDocument);
}
}
try {
out.println(outputDocument.toString());
} catch (IOException e) {
throw new JspException(e.getMessage());
}
return SKIP_PAGE;
}
/**
* (non-Javadoc)
*
* @see javax.servlet.jsp.tagext.Tag#doStartTag()
*/
public int doStartTag() throws JspException {
return EVAL_BODY_BUFFERED;
}
/**
* Relace href redirection to onclick call method
*
* @param segment Html Element
* @param outputDocument The output document
*/
private void rewriteAnchors(Segment segment, OutputDocument outputDocument) {
List anchors = segment.findAllStartTags(HTMLElementName.A);
for (Iterator i = anchors.iterator(); i.hasNext();) {
StartTag anchorTag = (StartTag) i.next();
Attributes attributes = anchorTag.getAttributes();
Attribute hrefAttribute = attributes.get("href");
if (hrefAttribute == null) {
continue;
}
String href = hrefAttribute.getValue();
if (href == null) {
continue;
}
Map attrreplace = outputDocument.replace(attributes, true);
OptionBuilder onclickOptions = getOptions(href);
attrreplace.put("href", "javascript://nop/");
attrreplace.put("onclick", getOnclickAjaxInvokePreFunction(onclickOptions));
}
}
/**
* Constucts javascript called method with href option into parameters
*
* @param options
* @return
*/
private String getOnclickAjaxInvokePreFunction(OptionBuilder options) {
StringBuffer onclick = new StringBuffer(this.getPagerMethodName() + "(");
onclick.append("{");
onclick.append(options.toString());
onclick.append("}");
onclick.append("); return false;");
return onclick.toString();
}
/**
* Extract parameters to href string
*
* @param href
* @return
*/
private OptionBuilder getOptions(String href) {
OptionBuilder options = new OptionBuilder();
href = href.substring(href.indexOf("?"));
href = href.substring(1);
String[] optionList = href.split("[&]");
for (int i = 0; i < optionList.length; i++) {
String[] option = optionList[i].split("[=]");
String value = "";
if (option.length > 1) {
value = option[1];
}
options.add(option[0], value, true);
}
return options;
}
public String getPagerMethodName() { return pagerMethodName; }
public void setPagerMethodName(String methodName) { this.pagerMethodName = methodName; }
}
– class OptionBuilder utilisé ci-dessus
package fr.geeks.presentation.taglib;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Permet de construire les paramètres à injecter dans notre méthode javascript qui fera l'appel ajax
*/
public class OptionBuilder {
private Map parameters = new HashMap();
private Map parameterQuotes = new HashMap();
public OptionBuilder add(String parameter, String value, boolean quoted) {
this.parameters.put(parameter, value);
this.parameterQuotes.put(parameter, Boolean.valueOf(quoted));
return this;
}
public OptionBuilder remove(String parameter) {
this.parameters.remove(parameter);
this.parameterQuotes.remove(parameter);
return this;
}
public String toString() {
StringBuffer sb = new StringBuffer();
for (Iterator iter = this.parameters.keySet().iterator(); iter.hasNext();) {
String key = (String) iter.next();
String value = (String) this.parameters.get(key);
boolean quoted = ((Boolean) this.parameterQuotes.get(key)).booleanValue();
sb.append(key).append(": ");
if (quoted) {
sb.append("\"").append(value).append("\"");
} else {
sb.append(value);
}
if (iter.hasNext()) {
sb.append(",");
}
sb.append("\n");
}
return sb.toString();
}
}
- une action Struts permet de récupérer la liste d’objets à afficher selon les paramètres envoyés par DisplayTag, à savoir le tri, la page et la direction. Attention il faut absolument que l’objet utilisé par displayTag implémente la classe suivante org.displaytag.pagination.PaginatedList
package fr.geeks.presentation.action;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import fr.geeks.metier.bean.TablePaginatedList;
import fr.geeks.metier.service.ServiceMetier;
public class GeekAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
// récupération des paramètres du tableau
String page = request.getParameter("page");
String sort = request.getParameter("sort");
String dir = request.getParameter("dir");
// construction de la liste paginée
TablePaginatedList tablePaginatedList = new TablePaginatedList();
tablePaginatedList.setPageNumber(StringUtils.isBlank(page) ? 1 : Integer.parseInt(page));
tablePaginatedList.setSortCriterion(StringUtils.isBlank(sort) ? TablePaginatedList.DEFAULT_SORT_CRITERION : sort);
tablePaginatedList.setSortDirection(StringUtils.isBlank(dir) ? TablePaginatedList.DESCENDING_SORT : (dir.equals(TablePaginatedList.DESCENDING_SORT) ? TablePaginatedList.DESCENDING_SORT : TablePaginatedList.ASCENDING_SORT));
// appel métier pour récupérer la liste des enregistrements selon les paramètres précédents
// le métier n'est pas décrit dans cet article mais c'est une méthode qui attaque la base de données et renvoie les données sous forme d'une liste
// dans les sources présentées plus bas vous verrez un exemple à partir d'un tableau en dur
int fullObjectsList = ServiceMetier.getFullObjectsListSize();
tablePaginatedList.setFullListSize(fullObjectsList);
// index
int startIndex = (tablePaginatedList.getPageNumber() - 1) * tablePaginatedList.getObjectsPerPage() + 1;
int stopIndex = startIndex + tablePaginatedList.getObjectsPerPage() - 1;
List partialObjectsList = ServiceMetier.getPartialObjectsList(startIndex, stopIndex, tablePaginatedList.getSortCriterion(), tablePaginatedList.getSortDirection().getName());
tablePaginatedList.setPartialList(partialObjectsList);
// envoi de données à la JSP recevante
request.setAttribute("objectsList", tablePaginatedList);
request.setAttribute("pageSize", tablePaginatedList.getObjectsPerPage());
request.setAttribute("resultSize", fullObjectsList);
return mapping.findForward("success");
}
}
- cette action Struts renvoie sur une JSP qui affiche la liste d’objets reçus sous forme de tableau ajaxifié
<%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=utf-8"%>
<%@ taglib uri="http://displaytag.sf.net" prefix="display"%>
<%@ taglib uri="/WEB-INF/tld/customization.tld" prefix="custom"%>
<%-- surchage du displaytag pour externaliser la pagination --%>
<custom:displayTag pagerMethodName="changePage">
<%-- displaytag normal --%>
<display:table name="objectsList" pagesize="2" partialList="true"
pagesize="${requestScope.pageSize}" size="${requestScope.resultSize}" sort="external">
<display:column property="id" title="N°" sortable="true" />
<display:column property="nom" title="Nom" sortable="true" />
<display:column property="prenom" title="Prénom" />
<display:column property="email" title="Email" />
</display:table>
</custom:displayTag>
- voici la fameuse méthode JavaScript (paramétré via le tag « custom:DisplayTag ») qui nous permet de ne plus rafraichir la page entière lors que l’utilisateur passe de page en page dans son tableau ou lorsqu’il le tri
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=utf-8"%>
<% request.setAttribute("ctx", request.getContextPath()); %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="${ctx}/scripts/prototype.js"></script>
</head>
<body>
<script type="text/javascript">
// affiche la table au chargement de la page
function displayTable() {
var url = "${ctx}/table.do";
ajaxRequest(url);
}
// fonction lors de la pagination du tableau
function changePage(params) {
var url = "${ctx}/table.do?";
if (params['page']) { url += "&page="+params['page']; }
if (params['sort']) { url += "&sort="+params['sort']; }
if (params['dir']) { url += "&dir="+params['dir']; }
ajaxRequest(url);
}
// requête au serveur de façon asynchrone
function ajaxRequest(url) {
// make a HTTP request to the specified URL
new Ajax.Request(url, {
method:'get',
asynchronous:true,
onSuccess: function(xhr) {
$("DIV_TABLE_OBJECTS").innerHTML = xhr.responseText;
}
});
}
// au chargement de la page
window.onload = function() {
displayTable();
}
</script>
<!-- endroit où le tableau sera affiché dans la page courante -->
<div id="DIV_TABLE_OBJECTS"></div>
</body>
</html>
- côté Struts le strict minimum à savoir :
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <!-- Data Sources --> <data-sources></data-sources> <!-- Form Beans --> <form-beans></form-beans> <!-- Global Exceptions --> <global-exceptions></global-exceptions> <!-- Global Forwards --> <global-forwards></global-forwards> <!-- Action Mappings --> <action-mappings> <!-- Home page --> <action path="/table" type="fr.geeks.presentation.action.GeekAction"> <forward name="success" path="/WEB-INF/jsp/ajaxTable.jsp" /> </action> </action-mappings> <!-- Message Resources --> <message-resources parameter="ApplicationResources" /> </struts-config>
Avantages
- Facilité d’utilisation : il suffit de rajouter une seule ligne pour surcharger le DisplayTag original et de profiter de la puissance d’Ajax
- Facilité de réutilisation : une TagLib pourra se réutiliser dans n’importe quel projet, il suffit d’en faire un .jar
Sources
- Ajax DisplayTag avec Struts : Disponibles ici
- Ajax DisplayTag avec DWR : Disponibles là





Affichez votre portrait
{ 24 commentaires… à vous de vous exprimer ! }
Article à digguer
je vous remercie pour cet exemple,est-ce que vous pouviez m’envoyer l’exemple sur ma boite(asmaoui_iga@hotmail.com),je vous serai très reconnaissant,parce que j’ai essayé de développer cet exemple, mais j’échoue toujours et Merci d’avance
Salut Asmaoui, je te refais ça proprement dans un projet sous Eclipse et je zipperai le tout dès que j’aurai fini, voire je le rajouterai aussi dans l’article pour plus de facilité, en tout cas merci pour ton commentaire, j’essaie de faire ça vite
Merci bcp , c’est gentil de ta part
Voilà c’est chose faite !!
J’avais effectivement oublier deux trois explications pour que ce soit plus clair à reproduire … Les sources sont dans l’article au niveau du chapitre « Sources » (testées avec le JDK 6, Tomcat 6 et Eclipse Europa).
Je modifierai l’article légèrement pour apporter les explications manquantes notamment la plus importante est que nous devons passer un objet implémentant « org.displaytag.pagination.PaginatedList » à displayTag et pas une Collection directement pour que ça fonctionne.
N’hésites pas si tu as des questions, ++
Merci c’est tellement gentil de ta part, je vous remercie infinement.en fait j’ai télécharger les sources et je les ai testé,et ç marche bien. Merci bcp,Demain je vais essayer de le comprendre et Merci une 2émé fois +@ et dsl pr ce dérangement
Aucun problème, content de t’avoir aidé
Bonsoir, En fait je trouve de difficulté pour comprendre la classe CustomDisplayTag ,ainsi j’ai pas pu générer le code source de DispalayTag..
Est-ce que je peux changer prototype.js pour utiliser AJAX-DWR et je garde le même Code,parce-que je voudrai travailler avec JSF,AJAX-dwr. Et merci
voila un exemple d’utilisation de displayTag et ajax-DWR
http://www.ajaxlines.com/ajax/stuff/article/how_to_make_displaytag_ajax_enabled_using_dwr.php
Dans cette exemple on n’essaie pas de changer le code source de DisplayTag
Salut, j’avais parcouru l’exemple que tu cites et je t’avoue que ce n’est pas clair son truc et ça a l’air compliqué, il change bien le code source de DisplayTag avec ça :
- html = DisplayTagReplacementUtil.updatePagingHtml(html, page, maxResults, numberOfObjects, displayTagPage);
- html = DisplayTagReplacementUtil.updateSortOrderHtml(html, ascending, displayTagSortOrder);
- html = DisplayTagReplacementUtil.updateHtmlLinks(html);
Une taglib est complètement dans son rôle pour faire cette manip’, je n’aime pas la façon de faire de son exemple. La classe CustomDisplayTag ne peut s’utiliser qu’en l’appelant dans la JSP comme ceci [plain]<custom:displayTag/>[/plain]
Par contre pour utiliser DWR je ne vois pas où tu coinces car tu peux appeler tes classes DWR dans la méthode javascript changePage() : au lieu d’appeler mon action Struts tu appelles ta méthode DWR comme par exemple :
[javascript]
YourObjectDwrJs.getTable(params['page'],params['sort'], params['dir'], {
callback:function() { … }
}
);
[/javascript]
Bonsoir, c’est moi toujours :$ ,en fait j’ai essayé d’utiliser DisplayTag et DWR mais je me échoue toujours .
J’ai conservé la structure de l’exemple que vous nous avez montré, avec quelque modification je vais les citer maintenant :
– Suppression du dossier Struts (les .jar relative à Struts)
– Modification de la classe GeekAction, Devient :
package fr.geeks.presentation.action;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import fr.geeks.metier.bean.TablePaginatedList;
import fr.geeks.metier.service.ServiceMetier;
public class Demo
{
public List getTable(String page,String sort,String dir)
{
WebContext wctx = WebContextFactory.get();
HttpServletRequest request = wctx.getHttpServletRequest();
// construction de la liste paginée
TablePaginatedList tablePaginatedList = new TablePaginatedList();
tablePaginatedList.setPageNumber(StringUtils.isBlank(page) ? 1 : Integer.parseInt(page));
tablePaginatedList.setSortCriterion(StringUtils.isBlank(sort) ? TablePaginatedList.DEFAULT_SORT_CRITERION : sort);
tablePaginatedList.setSortDirection(StringUtils.isBlank(dir) ? TablePaginatedList.DESCENDING_SORT : (dir.equals(TablePaginatedList.DESCENDING_SORT) ? TablePaginatedList.DESCENDING_SORT : TablePaginatedList.ASCENDING_SORT));
// appel métier pour récupérer la liste des enregistrements selon les paramètres précédents
// le métier n’est pas décrit dans cet article mais c’est une méthode qui attaque la base de données et renvoie les données sous forme d’une liste
int fullObjectsList = ServiceMetier.getFullObjectsListSize();
tablePaginatedList.setFullListSize(fullObjectsList);
// index
int startIndex = (tablePaginatedList.getPageNumber() – 1) * tablePaginatedList.getObjectsPerPage() + 1;
int stopIndex = startIndex + tablePaginatedList.getObjectsPerPage() – 1;
List partialObjectsList = ServiceMetier.getPartialObjectsList(startIndex, stopIndex, tablePaginatedList.getSortCriterion(), tablePaginatedList.getSortDirection().getName());
tablePaginatedList.setPartialList(partialObjectsList);
//request.setAttribute(« objectsList », tablePaginatedList);
request.setAttribute(« pageSize », tablePaginatedList.getObjectsPerPage());
request.setAttribute(« resultSize », fullObjectsList);
TablePaginatedList objectsList = tablePaginatedList;
return (List) objectsList;
}
}
ainsi j’ai changer le fichier web.xml pour prendre en charge de DWR
ainsi le ficher DWR :
ainsi la page page.jsp Devient
function changePage {
Demo.getTable(page,sort,dir, {
callback:function(data) {
dwr.util.setValues(« table », data);
}});
}
dont j’effectue l’inclus de la page ajaxTable.jsp
mais ne fonctionne pas :S
Merci de m’aider
Salut Asmaoui, je me suis sérieusement penché sur ton problème, finalement j’ai trouvé la solution que je te fais partager ainsi qu’à tous dans la rubrique « Sources »
J’espère t’avoir aidé, à plus tard ^^
Rien à dire,je vous remerci infinement pour votre aide.
bn voila un lien que je juge utile pour compléter votre serie d’exemple
(DisplayTag + AjaxAnywhere)
http://raibledesigns.com/rd/entry/the_future_of_the_displaytag
Et merci pour la 2éme fois. ++@
Bon ben plus ça va plus ton article s’améliore, Mimie
C’est vrai que le sujet donne de quoi creuser..
je souhaite que vous m’envoyer les codes sources de pagination,de tri et de filtre
en ajax
@laury : les sources sont disponibles en fin d’article, dans la rubrique « Sources »
Bjr Mimie, j’espère que vous allez bien..ca fait longtemps que vous m’avez aidé..et je suis très reconnaissant..je vous demande si vous avez déja travailler avec le framewok Tapestry 5,j’ai essayé de trouver des tutoriaux ou des livres en français pour les débutants,mais j’ai echoué tjrs..si vous pourriez m’aider et Merci d’avance
@asmaoui : je n’ai jamais utilisé ce framework, la documentation du site officiel user guide ne te suffit pas à démarrer ?
Sinon voici un lien intéressant à mon avis, bon courage
Merci Mimie pour votre aide,je suis très reconnaissant..je vais me débrouiller
Merci encore une fois
Une façon peut-être plus élégante et plus simple de permettre cela avec le framework jQuery : http://plugins.jquery.com/project/displaytag-ajax
@Mimie : Tout d’abord je tiens à vous remercier pour cette article même si je l’ai pas encore testé parce que je viens juste de lire le code des class , svp j’ai besoin d’un exemple d’appel métier pour récupérer la liste des enregistrements selon les paramètres indiqués ( je parle de l’appel mentionné dans la classe : GeekDWR.java .
Merci d’avance.
P.S : j’ai vraiment besoin de ça en urgence
@Yassine : n’y a t’il pas déjà un exemple dans les sources disponibles en fin d’article ?
@Mimie : Oui je l’ai télécharger mais je ne sais pas comment faire pour l’appel métier que vous avez citer :
// appel métier pour récupérer la liste des enregistrements selon les paramètres précédents
// le métier n’est pas décrit dans cet article mais c’est une méthode qui attaque la base de données et renvoie les données sous forme d’une liste
// dans les sources présentées plus bas vous verrez un exemple à partir
alors de ça moi et merci d’avance de me citer un exemple.
Bonjour , est ce quelqu’un a pu changer l’exemple des données , en utilisant un appel métier vers la base de données pour récupérer les enregistrements d’une table.
je parle de cette partie du code :
/ appel métier pour récupérer la liste des enregistrements selon les paramètres précédents
// le métier n’est pas décrit dans cet article mais c’est une méthode qui attaque la base de données et renvoie les données sous forme d’une liste
parce que dans l’exemple les données associées sont passées en dur alors que je veux les récupérer via la table (Couche service , couche Dao …)
Merci d’avance