Tapestry5/Ajax : Problème id dynamique dans une Loop

Tapestry est un puissant framework MVC en java qui utilise une approche par composant. Les contrôleurs sont des classes java. Chacune est associée à une vue, qui prend la forme d’un template xml propre à tapestry. Les modèles sont des pojo java qui sont comme dans tous les framework MVC trimbalés entre le controlleur et la vue.

Contrairement aux jsp, qui permettent l’inclusion de code java et qui sont par conséquent extrêmement permissives, les templates Tapestry de par leurs structures xml ne contient que très peu de logique. Ils permettent d’isoler un maximum les traitements et de revenir à la fonction principale de la vue qui est d’afficher la donnée. Cette philosophie est intéressante car elle oblige le développeur à factoriser son travail. La vue se retrouve ainsi considérablement allégée et sa lecture devient facilitée.

Par contre l’utilisation quasi magique des composants du framework peut parfois faire tourner la tête et rendre certaines problématiques plus complexe qu’initialement.

Mise en place du problème

Nous allons créer un composant dont le rôle sera d’afficher une liste d’éléments avec pour chacun d’entre eux, un lien avec la possibilité de charger de manière asynchrone un élément de cette liste.
Pour cela nous alons donc commencer par créer le model :

public class Foo {
    private String name ;
    private String about ;
    public Foo(String name, String about){
        this.name = name ;
        this.about = about ;
    }
    public String getName() {
        return name ;
    }
    public void String setName(String Name) {
        this.name = name ;
    }
    public String getAbout() {
        return about ;
    }
    public void String setAbout(String about) {
        this.about = about ;
    }
}

Nous allons mainenant créer note controlleur qui contiendra la liste de Foo que nous allons créer arbitrairement pour l’exemple.

public class FooList {
    @Property
    private List<Foo> fooList;
    @Property
    private Foo foo;   
 
    public FooList() {
        fooList = new ArrayList<Foo>();
        fooList.add(new Foo("one", "blablablabla"));
        fooList.add(new Foo("two", "blibliblibli"));
        fooList.add(new Foo("tree", "bloblobloblo"));
        fooList.add(new Foo("four", "blublublublu"));
    }
 
    @Inject
    private Block blockFoo;
 
    @Inject
    private Request request;
 
    @OnEvent(value = EventConstants.ACTION, component = "editZone")
    public Object editZone(String fooName) {
        for (Foo fooTmp : fooList) {
            if (fooTmp.getName().equals(fooName)) {
                foo = fooTmp;
            }
        }
        return request.isXHR() ? blockFoo : this ;
    }
}

Dans la vue :

<t:loop source="fooList" value="foo">
   <t:actionlink t:id="editZone" t:zone="zoneFoo" t:context="foo.name">
      View Foo
   </t:actionlink>
   <t:zone t:id="zoneFoo">
      <t:block t:id="blockFoo"> ${foo.about} </t:block>
   </t:zone>
</t:loop>

Voilà nous pouvons maintenant tester. Notre liste de lien s’affiche bien mais il est impossible de mettre à jour les différentes zones. Quoi que l’on fasse c’est toujours la première qui est affectée.

Solution

Pour expliquer celà, il faut commencer par identifier l’erreur. Regardons du côté de t:zone. Le composant est utilisé à l’intérieur d’une boucle et nous lui mettons toujours le même t:id. Cette notation est accepté par Tapestry mais évidement un id dans le DOM se doit d’être unique.

En HTML/Javascript classique, une démarche classique aurait été de générer un id spécifique pour chaque itération et de générer l’appel asynchrone directement en js. Rien de bien sorcier si on connait javacscript, mais on se prive de la logique et la magie de Tapestry pour la gestion des Zone et des Event. C’est pas le top.

On va plutôt essayer de prendre le problème dans le sens inverse. C’est à dire plutôt que de générer un id comme on sait le faire, on pourrait déterminer l’id que tapestry génère et voir si on peut dynamiquement pointer vers lui.

En regardant de plus près, du coté client, Tapestry génère un id différent pour chaque zone ayant le même t:id suivant la règle suivante :

1ère itération : zoneFoo
2nd itération : zoneFoo_0
3nd itération : zoneFoo_1
4ème itération : zoneFoo_2

Et dans l’actionLink t:zone="zoneFoo" fait référence au id HTML et non au t:id. Ce qui nous permet de déterminer le nom généré par Tapestry pour une certaine itération. Pour ce faire nous allons créer une méthode getZoneName.

Dans le controller :

    @Property
    private Integer index;
 
    public String getZoneName() {
        if (index.intValue() == 0)
            return "zoneFoo";
        return "zoneFoo_" + (index.intValue() - 1);
    }

Dans la vue :

<t:loop source="fooList" value="foo" t:index="index">
   <t:actionlink t:id="editZone" t:zone="zoneFoo" t:context="foo.name">
      View Foo
   </t:actionlink>
   <t:zone t:id="zoneFoo">
      <t:block t:id="blockFoo"> ${foo.about} </t:block>
   </t:zone>
</t:loop>

Avec cette petite astuce, on peut utiliser les zones de Tapestry dans une boucle sans problème.

Articles similaires :

Mots-clefs : , , , ,

Laisser une réponse