IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JSF Java Discussion :

[JSF] Création de formulaire dynamique, Pb UIViewRoot


Sujet :

JSF Java

  1. #1
    Membre à l'essai
    Inscrit en
    Avril 2004
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Avril 2004
    Messages : 24
    Points : 17
    Points
    17
    Par défaut [JSF] Création de formulaire dynamique, Pb UIViewRoot
    Bonjour à tous,

    Mon problème je pense pourras peut-être en intéresser plus d'un. J'ai fait quelques recherches sur le net mais je n'arrive pas à trouver de solution alors j'en appel aux experts JSF.

    Mon but est de faire un formulaire dynamique. En gros, j'ai une page JSF dans laquelle se trouve un bouton "Add". A chaque clique sur le bouton j'ajoute un composant dans la page, en l'ajoutant simplement à un panelGrid.
    J'ai un bean dans lequel j'initialise la grid, puis ensuite une action méthod qui ajoute à chaque clique un composat à la grid.

    dans getGrid() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    UIOutput output = new UIOutput();
    output.setId("myoutput");
    output.setValue("Hello");
    grid = new HtmlPanelGrid();
    grid.getChildren().add(output);
    dans addComponentToGrid() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    nbCells++;
    for(int i=0;i<nbCells;i++) {
    UIOutput output = new UIOutput();
    output.setId("myOutputID" + <quelquechosedunique);
    output.setValue("Valeur ajoutée");
    grid.getChildren().add(output);
    }
    return null; // on recharge la même page
    ici nbCells est un attribut du bean qui stock le nombre de fois où je passe dans la méthode addComponentToGrid().

    Le problème est le suivant : cela marche une fois, deux fois, trois fois puis le comportement devient complètement irréaliste et des anciens objets sont ajoutés à la liste...
    J'ai alors été voir dans le debugger dans l'objet UIViewRoot du FaceContext et là je vois que des fois d'anciens objets réapparaissent dans la hièrarchie puis des fois disparraissent!!! Comme si un cache était géré par JSF mais que certains objets disparaissaient et réapparaissaient.
    Bref je ne comprends pas grand chose à la gestion du UIViewRoot donc si quelqu'un pouvait m'expliquer ce qui se passe je lui en serais très reconnaissant.

    Merci d'avance à tous.
    ++
    Tum ti doum...

  2. #2
    Membre à l'essai
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    31
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 31
    Points : 19
    Points
    19
    Par défaut
    Salut,

    j'ai récemment dû créer dynamiquement des UIComponents dans mon viewroot et j'ai eu un problème similaire.

    Lorsque j'ajoutais un composant, j'avais l'impression qu'il parcourait d'abord son arbre pour contrôler si un composant similaire n'avait pas été déjà crée. Et donc j'avais des comportements très étranges. Parfois il ne me créait pas le composant, parfois il l'initialisait avec des anciennes valeur etc...

    Ce que j'ai fait pour contourner le problème, c'est que pour toute création de composant je passe par une méthode qui contrôle l'existence du composant avant de le créer.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        /**
         * Création ou récupération d'un composant déjà existant ayant le même nom.
         * @param componentType Type du composant à créer
         * @param componentName Nom du composant à créer
         * @return Le composant crée ou trouvé
         */
        private UIComponent createComponent(final String componentType, final  String componentName) {
    	final Application application = FacesContext.getCurrentInstance().getApplication();
    	UIComponent component = null;
    	try {
    	    component = FacesContext.getCurrentInstance().getViewRoot().findComponent(componentName);
    	    LOG.info("Le composant " + componentName + " a été trouvé.");
    	} catch (Exception e) {
    	    LOG.info("Le composant " + componentName + " n'a pas été trouvé, création. " + e);
    	}
    	if (component == null)
    	    component = application.createComponent(componentType);
     
    	return component;
        }
    Et pour créer mon composant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     HtmlInputHidden inputHidden = (HtmlInputHidden) createComponent(HtmlInputHidden.COMPONENT_TYPE, "parameterModulation");
    ça ne répond pas directement à ton problème à mon avis mais ça pourra sans doute te guider.

  3. #3
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    ta logique me semble relativement bonne. J'ai déjà travaillé avec des formulaires dynamiques par le passé et l'ajout de children dans un arbre ne pose aucun problème en JSF. On peut même, dans un renderer, crée un composant temporaire histoire de l'utiliser pour du rendu (si ma mémoire est bonne tomahawk le fait sur certains composant implicant plusieurs textfield :p)

    par contre deux points important.

    Très important: on n'appelle jamais le constructeur d'un composant JSF. Il y a beaucoup plus dans les composant JSF que leur simple construction. Utilise application.createComponent(componentType)

    Utilise un componentBinding sur ton panelgrid, qui point sur une propriété de ton bean, ca permettra une manipulation facile du datagrid

    N'oublie pas l'id

    attention, l'arbre pour une vue (en tout cas avec facelets) est construit une fois pour toute et mis dans la session user, tout changement est définitif pour l'utilisateur en cours -> te faudra réinitialiser aussi en force après l'action


    Autre suggestion: pourquoi y aller en force en augmentant le nombre de composant dans l'arbre alors que tu pourrais avoir facilement un comportement similaire avec un <h:dataTable/> qui itérerait sur un bean?

  4. #4
    Membre à l'essai
    Inscrit en
    Avril 2004
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Avril 2004
    Messages : 24
    Points : 17
    Points
    17
    Par défaut
    Merci à vous deux d'avoir répondu aussi vite.

    dachman : j'ai suivi ton raisonnement, je récupère mon objet à partir de son id et essaie ensuite de lui ajouter des composants. La récupération est ok, j'arrive à ajouter des children au composant mais malheuresement un pb se pose : d'anciens composants apparaissent parfois dans la liste, parfois non. Si je te comprend bien, il me semble que c'est le même problème que tu as rencontré. Du coup, je me retrouve parfois avec trop de composants dans l'objet, puisque d'anciens composants réapparaissent.
    J'ai bien essayé de réinitialiser l'objet récupéré en utilisant le createComponent() sur celui-ci mais là c'est encore pire, l'objet ne change plus du tout...

    tchize_ : j'utilise facelets et je me demande si effectivement ce n'est pas ça qui pose pb.
    Pour répondre à ta dernière question je ne comprends pas tout à fait ce que tu veux faire En fait à chaque fois que je clique sur submit je reload le même formulaire en ajoutant une série de composants à la grille, mais ces composants peuvent être différents. Si je loop sur une liste de composants
    comment puis-je faire le binding dans la page? Aurait-tu un petit example de code?

    Merci encore à vous deux,
    ++
    Tum ti doum...

  5. #5
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    effectivement, le datatable ou le datagrid marche moins bien si les composants sont de types différents. Cependant, si le nombre de type est limités, tu peux faire ceci: (tomahawk datalist)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <t:datalist value="#{bean.collectionDeTrucs}" var="truc">
       <h:inputText value="#{truc.value}" rendered="#{truc.type='Text'}"/>
       <h:selectBoolean value="#{truc.value}" rendered="#{truc.type='Booleen'}"/>
        <t:inputCalendar  value="#{truc.value}" renderAsPopup="true"
        renderPopupButtonAsImage="true" 
        popupDateFormat="MM/dd/yyyy"
        alt="Calendar" title="Calendar" renderd="#{truc.type='Date'}">
     </t:inputCalendar>
    </t:datalist>
    A chaque itération de la boucle on va donc afficher l'un ou l'autre composant en fonction du type de la variable courante. Avantage, tu ne gère pas l'ajout d'élément dans ton backing bean (tu préserve en quelque sorte la séparation vue / modèle.

    Inconvénient, t'as besoin d'un modèle dans lequel chaque élément de la collection a la forme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    public class MonObjetDeCollection {
        public Object getValue() {...}
        public void setValue(Object object){....}
        public String getType() {.....}
    }
    (ça pourrait être évidement beaucoup plus beau à lire avec des génériques mais le principe reste le même).

    Ce genre de méthode est bien adaptée si la quantité de types différents est faible par rapport à la quantité de donnée (exemple, 100 données de 10 type différents, ok. 15 données de 20 types potentiellement différent, moins ok :p)


    Ce genre de méthode marche moins bien si tu dois faire des insertion ailleurs qu'en fin de liste, mais en modifiant l'arbre de composant c'est de toutes façons aussi le bordel en milieu de liste. De plus cette méthode avec le rendered permet d'ajouter ce que tu vex comme décoration autour facilement dans ton xhtml, tandis qu'en faisant ça avec du code, tu perd tout chance de gérer l'aspect ton interface via le xhtml

  6. #6
    Membre à l'essai
    Profil pro
    Inscrit en
    Juin 2007
    Messages
    31
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 31
    Points : 19
    Points
    19
    Par défaut
    Citation Envoyé par JCitrouille Voir le message
    dachman : j'ai suivi ton raisonnement, je récupère mon objet à partir de son id et essaie ensuite de lui ajouter des composants. La récupération est ok, j'arrive à ajouter des children au composant mais malheuresement un pb se pose : d'anciens composants apparaissent parfois dans la liste, parfois non. Si je te comprend bien, il me semble que c'est le même problème que tu as rencontré. Du coup, je me retrouve parfois avec trop de composants dans l'objet, puisque d'anciens composants réapparaissent.
    J'ai bien essayé de réinitialiser l'objet récupéré en utilisant le createComponent() sur celui-ci mais là c'est encore pire, l'objet ne change plus du tout...
    Pour les anciens composants qui réapparaissent, tu peux essayer de leur mettre un .setRendered(false) pour éviter qu'ils soient interprétés au rendu. Mais, même si tu les vois dans l'arbre, es-tu sûr qu'ils sont reproduits sur ton interface?

  7. #7
    Membre à l'essai
    Inscrit en
    Avril 2004
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Avril 2004
    Messages : 24
    Points : 17
    Points
    17
    Par défaut
    Salut,

    tchize_ : ça y est j'ai compris, pas mal du tout ton idée, faut voir si ça s'adapte dans mon cas. En fait j'ai un bouton en haut de la page qui lors de chaque click me génère le bout de formulaire. Comme je fais un reload de la page je peux alimenter la liste et reconstruire l'arbre à chaque fois, c'est pas mal.

    dachman : en fait je ne comprends toujours pas comment JSF gère l'arbre de composants. J'ai fait pas mal de tests et la façon dont les anciens composants réapparaissent est aléatoire... Bon il doit y avoir une explication derrière mais disons que je ne l'ai pas encore trouvée... Pour répondre à ta question oui, les composants sont effectivement affichés dans la page JSF.
    Ce que je fais pour l'instant c'est que je reconstruit la datagrid à chaque coup en récupérant celle-ci du UIViewRoot. Une fois l'objet récupéré je fais un remove sur tout ses composants de cette grid pour être sûr de ne pas en avoir en cache et je reconstruits la grid. C'est pas super car à chaque fois je dois faire les contrôles et ensuite reconstruire la liste. Mais bon ça marche. Je préfèrerais comprendre le mécanisme de cache de JSF.

    Merci encore à vous, je vous tiens au courant de mes avancées.
    ++
    Tum ti doum...

  8. #8
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Citation Envoyé par JCitrouille Voir le message
    en fait je ne comprends toujours pas comment JSF gère l'arbre de composants
    En fait il le gère assez simplement. Lors du create view, l'arbre est construit. lors des restore view il est récupéré de la session utilisateur:

    viewID -> session -> arbre de composant.

    Le problème, c'est que ton view handler (que ce soit jsp ou facelet) s'y mèle et décide d'ajouter lors du restore view les "nouveau trucs" qui ont été ajouté depuis (jsp modifié, xhtml modifié, etc). Et si l'arbre restoré ne correspond plus à ce qui était dans le jsp au départ, le view handler peut un peu s'emeller les pinceaux et ne plus reconnaître toute ce qu'il avait précédement créé. Et tenter de les recréer.

  9. #9
    Membre régulier
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    76
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2006
    Messages : 76
    Points : 96
    Points
    96
    Par défaut
    La solution de tchize_ est a mon sens beaucoup plus propre.

    J'ai appliqué la meme méthode en créant une interface avec une méthode getUrl.

    Ca m'a permit d'abstraire l'accès à mes composants :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
     
    <t:datalist value="#{bean.collectionDeTrucs}" var="truc">
       <ui:include src="#{truc.url}">
          <ui:param name="component" value="#{truc}"/>
       </ui:include>
    </t:datalist>
    Mais bon je dois dire que JSF au contraire de par exemple Wicket est moins fait pour s'amuser avec l'arbre.

  10. #10
    Membre à l'essai
    Inscrit en
    Avril 2004
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Avril 2004
    Messages : 24
    Points : 17
    Points
    17
    Par défaut
    Re,

    Le problème, c'est que ton view handler (que ce soit jsp ou facelet) s'y mèle et décide d'ajouter lors du restore view les "nouveau trucs" qui ont été ajouté depuis (jsp modifié, xhtml modifié, etc). Et si l'arbre restoré ne correspond plus à ce qui était dans le jsp au départ, le view handler peut un peu s'emeller les pinceaux et ne plus reconnaître toute ce qu'il avait précédement créé. Et tenter de les recréer.
    Effectivement, ça y ressemble bien!

    Nithril, je pense effectivement que je vais utiliser la méthode de tchize_, car après une journée d'essais avec ma méthode je me retrouve coincé avec ces satanés binding dynamic dont je ne comprends définitivement pas l'intérêt. Peut-être me manque-t-il quelques infos sur le sujet mais je n'arrive pas a submiter les valeurs d'un formulaire lorsque celui-ci a été construit dynamiquement.
    Désolé pour l'absence de code dans mes post mais je ne peux pas sortir le code de mon poste de dev, tout est bloqué ici...

    Mais voici quand même un aperçu du problème :

    Ceci marche :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    <h:inputText value="#{dynController.formFields['aField']}" />
    et ceci non...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    <h:inputText binding="#{dynController.grid}" />
    En fait quand je dis que ça marche c'est que lorsque je submit avec un commandButton et que met un point d'arrêt dans mon action méthode, la map de mon bean dynController est mise à jour avec les données de mon formulaire, dans l'autre cas non...
    Dans le deuxième cas je construit une grid avec dedans un champ input et je fais un value binding dynamic en utilisant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    String valueBinding = "#{dynController.formFields['aField']}";
    ValueBinding myBinding = context.getApplication().createValueBinding(valueBinding);
    input.setValueBinding("value", myBinding);
    input est ici un UIInput que j'attache à ma grid.

    En fait le binding fonctionne en lecture, je verais la valeur dans mon champ input lors du load de la page, mais après il est impossible de changer cette valeur... Le pire c'est que lors du submit je vois mes valeur dans l'ExternalContext du Facecontext... Mais le bean n'est pas mis à jour.

    J'ai déjà remarqué plusieurs fois que l'attribut "binding" empêchait toute modification ultérieure... Si quelqu'un sait m'expliquer? Il semble que le binding dynamique permet simplement de créer un "template" de page, ensuite il est impossible de récupérer ou modifier les valeurs...

    Bref tout ça pour dire que faire un formulaire dynamique en utilisant le binding de JSF ressemble un peu à l'ascencion du K2... Donc tchize_, Nithril, je vais me diriger vers votre méthode mais j'aimerais quand même comprendre pourquoi le binding ne permet pas la modification de valeur.

    Merci encore, A+
    Tum ti doum...

Discussions similaires

  1. CRÉATION DE FORMULAIRE DYNAMIQUE
    Par blueette dans le forum Modélisation
    Réponses: 0
    Dernier message: 26/04/2013, 13h49
  2. [AC-2003] Création de formulaire dynamique
    Par newtownz dans le forum VBA Access
    Réponses: 8
    Dernier message: 17/08/2009, 15h47
  3. [2.0] Création de formulaires dynamiques
    Par mister3957 dans le forum ASP.NET
    Réponses: 2
    Dernier message: 13/08/2007, 16h22
  4. [VB6] Création de formulaire dynamique de recherche
    Par Mat_76 dans le forum VB 6 et antérieur
    Réponses: 6
    Dernier message: 07/05/2007, 09h41
  5. Création de formulaires dynamique?
    Par akademiks dans le forum JSF
    Réponses: 4
    Dernier message: 27/04/2007, 10h18

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo