Re: [Cantharella-devel] Tableau wicket de la liste des molécules
Bonjour,
Personnellement, je préfère la première car cela représente un cas particulier d'affichage et cela permet de ne pas polluer le modèle de données avec des problématiques d'affichage. Je suis d'accord, il serait en effet dommage de devoir modifier le modèle pour régler un problème de page web. Je ne vois pas par contre comment régler le problème d'itération sur List<AbstractModel> sans rajouter une interface commune. Mais c'est un moindre mal si cela nous permet de garder le modèle intact.
Par contre, je pense qu'elle ne convient pas exactement au problème, car l'objet à manipuler devrait être un javaBean car wicket affiche un propriété d'un bean, par exemple: columns.add(new PropertyColumn<Molecule>(new Model<String>(getString("Molecule.nomCommun")), "nomCommun", "nomCommun")); il va appeler la méthode getNomCommun() de la ligne courant. Il suffit de ne pas utiliser PropertyColumn et de créer à la place une classe interne anonyme qui hérite d'AbstractColumn<Molecule>. Exemple :
column.add(new AbstractColumn<Molecule>(new Model<String>(getString("Molecule.nomCommun"))) { @Override public void populateItem(Item<ICellPopulator<Molecule>> item, String componentId, IModel<Molecule> rowModel) { item.add(new Label(componentId, RowMoleculeFinder.getNomCommun(rowModel.getObject())); } });
ou mieux encore : garder PropertyColumn<Molecule> mais utiliser la stratégie de surchage non pour trouver la valeur mais le nom de propriété. Ainsi pour continuer avec l'exemple du nom commun, la méthode RowMoleculeFinder.getNomCommunProperty(Molecule molecule) rendrait "nomCommun", tandis que RowMoleculeFinder.getNomCommunProperty(MoleculeProvenance moleculeProvenance) rendrait "molecule.nomCommun". Par ailleurs, je viens de remarquer que vous étendez systématiquement dans cette page une PropertyColumn (ou ces dérivés tel DecimalPropertyColum, ...) au lieu d'AbstractColumn (les propriétés déclarées dans le constructeurs n'étant ensuite pas utilisées).
Nous allons également essayer de réfléchir à une solution.
Pour revenir a la même problématique pour les Produit, les molécules portant sur les produits, cela devient également lorsque l'on doit afficher le nom du programme sachant que: * la ligne est soit une Molecule, soit une MoleculeProvenance * le produit est soit une Fraction, soit un extrait
Par exemple, pour la référence du lot, je dois faire deux cas encore suivant le type de produit: * "produit.purification.lotSource.ref" * "produit.extraction.lot.ref" Dans ce cas particulier où on doit afficher " - " (valeur nulle) quand c'est une Molecule, et la valeur de deux propriétés différentes (suivant si c'est une Fraction ou un Extrait) quand c'est une MoleculeProvenance, je conseillerais également d'utiliser la solution de surcharge pour obtenir la bonne propriété. Les méthodes aurait alors deux arguments comme par exemple : RowMoleculeFinder.getProgrammeProperty(Molecule, Fraction) (au total 4 surcharges pour chacun des cas).
Puisque Wicket est écrit intelligemment, cette solution devrait marcher même pour les deux surcharges avec Molecule. En effet, dans le PropertyResolver de Wicket nous avons :
/** * Looks up the value from the object with the given expression. If the expression, the object * itself or one property evaluates to null then a null will be returned. * * @param expression * The expression string with the property to be lookup. * @param object * The object which is evaluated. * @return The value that is evaluated. Null something in the expression evaluated to null. */ public final static Object getValue(final String expression, final Object object) Ainsi les deux surcharges utilisant Molecule peuvent rendre la valeur null et le résultat de l'évaluation du PropertyColumn rendra bien null. Il suffit alors de rajouter le behavior RemplaceEmptyLabelBehavior sur le cellItem du populateItem pour obtenir la chaîne désirée pour ces cas.
Adrien -- Adrien Cheype Ingénieur en Systèmes d'Information Service « Informatique Scientifique et Appui aux Partenaires du Sud » Direction du Système d'Information (DSI) http://www.ird.fr/dsi/ http://www.ird.fr/informatique-scientifique/ INSTITUT DE RECHERCHE POUR LE DEVELOPPEMENT BP A5 - 98848 Nouméa - Nouvelle Calédonie Tél. +687 260 789
Le 13/02/2013 07:26, Adrien Cheype a écrit :
Il suffit de ne pas utiliser PropertyColumn et de créer à la place une classe interne anonyme qui hérite d'AbstractColumn<Molecule>. Exemple : column.add(new AbstractColumn<Molecule>(new Model<String>(getString("Molecule.nomCommun"))) { @Override public void populateItem(Item<ICellPopulator<Molecule>> item, String componentId, IModel<Molecule> rowModel) { item.add(new Label(componentId, RowMoleculeFinder.getNomCommun(rowModel.getObject())); } });
ou mieux encore : garder PropertyColumn<Molecule> mais utiliser la stratégie de surchage non pour trouver la valeur mais le nom de propriété. Ainsi pour continuer avec l'exemple du nom commun, la méthode RowMoleculeFinder.getNomCommunProperty(Molecule molecule) rendrait "nomCommun", tandis que RowMoleculeFinder.getNomCommunProperty(MoleculeProvenance moleculeProvenance) rendrait "molecule.nomCommun".
Par ailleurs, je viens de remarquer que vous étendez systématiquement dans cette page une PropertyColumn (ou ces dérivés tel DecimalPropertyColum, ...) au lieu d'AbstractColumn (les propriétés déclarées dans le constructeurs n'étant ensuite pas utilisées). Finalement, nous avons trouvé quelque chose de plus simple: creer un bean MoleculeProvenanceBean instancié avec une Molécule ou une Molecule provenance.
Le bean a des getter/setter pour les propriétés à afficher dans la table. Et le code wicket est très simple et de la même forme que les autres tables: columns.add(new PropertyColumn<MoleculeProvenanceBean>(new Model<String>(getString("Molecule.nomCommun")), "nomCommun", "nomCommun")); Pendant que nous faisions ceci, nous avons détecter une chose étrange dans la gestion des tableaux wicket. Par exemple, pour le tableau des lots, voilà comment le provider du tableau est instancié: final List<Lot> lots = lotService.listLots(getSession().getUtilisateur()); LoadableDetachableSortableListDataProvider<Lot> lotsDataProvider = new LoadableDetachableSortableListDataProvider<Lot>(lots, getSession().getLocale()); Donc la liste est récupérée du service en une seule opération et cela devrait suffire à l'afficher. Cependant, dans le provider la methode getModel() est surchargée comme cela: public GenericLoadableDetachableModel<M> model(M object) { // return new Model<M>(object); return new GenericLoadableDetachableModel<M>(object); } qui, lorsqu'elle est appelée, va récupérer en base l'objet qui a le même id que l'objet actuel ??? Pourquoi ce genre de fonctionnement est-il en place ? Pourquoi n'utilise-t-on pas simplement la liste que l'on a chargé du service ? Le problème est encore plus remarquable ici: columns.add(new LinkableImagePropertyColumn<Lot>("images/edit.png", getString("Update"), getString("Update")) { @Override public void onClick(Item<ICellPopulator<Lot>> item, String componentId, IModel<Lot> model) { setResponsePage(new ManageLotPage(model.getObject().getIdLot(), currentPage)); } }); Lors du onClick() , la methode "model.getObject()" recharge dans la base de donnée l'object qui a le même id que l'objet du modèle courant sur lequel on récupère... son id :D Et dans la classe ManageLotPage, elle va le recharger encore pour l'afficher. -- Éric Chatellier - Code Lutin Tel: 02.40.50.29.28 - http://www.codelutin.com
On Wed, 13 Feb 2013 10:08:31 +0100 Eric Chatellier <chatellier@codelutin.com> wrote:
final List<Lot> lots = lotService.listLots(getSession().getUtilisateur()); LoadableDetachableSortableListDataProvider<Lot> lotsDataProvider = new LoadableDetachableSortableListDataProvider<Lot>(lots, getSession().getLocale());
Donc la liste est récupérée du service en une seule opération et cela devrait suffire à l'afficher. Cependant, dans le provider la methode getModel() est surchargée comme cela: public GenericLoadableDetachableModel<M> model(M object) { // return new Model<M>(object); return new GenericLoadableDetachableModel<M>(object); }
C'est pourquoi j'ai créé un SimpleSortableListDataProvider qui a les même caractéristique que LoadableDetachableSortableListDataProvider sauf qu'il utilise les objets de la liste plutôt que de rechargé les objets depuis la base. Cela permet aussi de ne plus avoir de contrainte sur le type d'objet de la liste (précédemment AbstractModel) Donc si personne ne voit d'intérêt de recharger l'objet de la base plutôt que de prendre celui de la liste, je préconiserais d'utiliser ce SimpleSortableListDataProvider pour les tables, ce qui évite potentiellement le n+1 requêtes. -- Benjamin POUSSIN -------------------- tél: +33 (0) 2 40 50 29 28 email: poussin@codelutin.com http://www.codelutin.com
On Wed, 13 Feb 2013 10:08:31 +0100 Eric Chatellier <chatellier@codelutin.com> wrote:
Finalement, nous avons trouvé quelque chose de plus simple: creer un bean MoleculeProvenanceBean instancié avec une Molécule ou une Molecule provenance.
Le bean a des getter/setter pour les propriétés à afficher dans la table.
Et le code wicket est très simple et de la même forme que les autres tables: columns.add(new PropertyColumn<MoleculeProvenanceBean>(new Model<String>(getString("Molecule.nomCommun")), "nomCommun", "nomCommun"));
Je commit le code, si jamais cela ne convenait pas comme solution, nous pourrons revenir dessus. -- Benjamin POUSSIN -------------------- tél: +33 (0) 2 40 50 29 28 email: poussin@codelutin.com http://www.codelutin.com
On Wed, 13 Feb 2013 10:08:31 +0100 Eric Chatellier <chatellier@codelutin.com> wrote:
Donc la liste est récupérée du service en une seule opération et cela devrait suffire à l'afficher. Cependant, dans le provider la methode getModel() est surchargée comme cela: public GenericLoadableDetachableModel<M> model(M object) { // return new Model<M>(object); return new GenericLoadableDetachableModel<M>(object); }
qui, lorsqu'elle est appelée, va récupérer en base l'objet qui a le même id que l'objet actuel ??? Pourquoi ce genre de fonctionnement est-il en place ? Pourquoi n'utilise-t-on pas simplement la liste que l'on a chargé du service ?
Je pense avoir un début de piste d'explication dans le code on trouve ça: /** Data list */ // TODO penser à remettre "transient" à la liste private List<M> list; Donc en fait la liste ne devrait plus exister entre deux appels (si elle est vraiment transient). Mais est-ce que cela fonctionne vraiment si on remet le transient ? Personnellement, je ne pense pas car, la méthode iterator utilise directement la liste, alors qu'elle devrait refaire appel au service pour récupérer ses informations. Codé convenablement cet objet demanderait énormément de travail (réécriture de nouvelles méthodes sur les services, réécriture de cette classe, prise en compte du detach, modification du code où est utilisé cet objet; en gros remise en cause profonde de beaucoup de chose actuellement utilisée) pour un gain qui ne se verrait que dans un environnement clusterisé avec réplication. Or la encore, même si on met l'application en cluster, la réplication n'est pas obligatoire car si un serveur tombe, au pire, l'utilisateur doit se réauthentifier et reprendre son travail. Donc lors d'une mise en cluster, la réplication n'est pour moi pas nécessaire voir contre productive par rapport à l'implication sur charge réseau par rapport au gain fonctionnel pour l'utilisateur. Bien sur ce que je dis (si j'ai pas tout faux ;)) ne vaut que pour le court terme. Car vu que la liste des éléments d'un tableau est chargé en une seule fois, plus la base contient d'élément, plus la liste chargé est longue et plus on pousse d'information dans la session :(. Il faudrait au moins prendre en compte la pagination (mais cela veut dire des services capable de récupérer les éléments que l'on souhaite, dans l'ordre que l'on souhaite en prenant en compte les droits de l'utilisateur) -- Benjamin POUSSIN -------------------- tél: +33 (0) 2 40 50 29 28 email: poussin@codelutin.com http://www.codelutin.com
participants (3)
-
Adrien Cheype -
Benjamin POUSSIN -
Eric Chatellier