2010-03-18 25 views
1

Je rencontre un problème. J'ai implémenté un PhaseListener, qui est destiné à ajouter une classe de style à tous les composants UIInput de l'arborescence auxquels sont attachés des messages, et supprime la classe de style si aucun message n'y est associé. Le PhaseListener s'exécute dans la phase RENDER_RESPONSE et fonctionne dans les deux méthodes beforePhase et afterPhase pendant le débogage. Pendant le débogage, j'ai trouvé que beforePhase n'a pas accès à l'arborescence complète des composants, mais afterPhase. Toute modification effectuée dans afterPhase n'est pas rendue.Modification de l'arborescence des composants JSF dans PhaseListener

Comment procéder? Je veux que ce soit complètement côté serveur.

Merci,

James

Répondre

0

Mis en œuvre en utilisant un ViewHandler, mais ce n'est pas efficace. PhaseListener dans la phase de réponse de rendu n'a pas accès à l'arborescence des composants. L'arborescence des composants JSF est uniquement disponible après l'heure de génération de la vue.

2

La phase RENDER_RESPONSE n'est pas nécessairement un bon moment pour accéder à l'arborescence complète des composants JSF avant qu'elle ne soit rendue. Au cours d'une requête GET initiale sans aucune <f:viewAction>, l'arborescence complète des composants est disponible uniquement dans le afterPhase car il est en cours de construction pendant le RENDER_RESPONSE. Au cours d'une publication, l'arborescence complète des composants est disponible dans le beforePhase, cependant, lorsqu'une navigation vers une autre vue a eu lieu, elle sera modifiée pendant la phase, de sorte que toute modification sera perdue.

Pour en savoir exactement ce que le temps de construction de vue est, la tête à la question What's the view build time?

Vous voulez essentiellement à accrocher « vue de rendre le temps » plutôt que beforePhase de la phase RENDER_RESPONSE. JSF offre plusieurs façons de crochet sur elle:

  1. Dans certains modèle maître, veuillez joindre un auditeur preRenderView-<f:view>.

    <f:view ...> 
        <f:event type="preRenderView" listener="#{bean.onPreRenderView}" /> 
        ... 
    </f:view> 
    
    public void onPreRenderView(ComponentSystemEvent event) { 
        UIViewRoot view = (UIViewRoot) event.getSource(); 
        // The view is the component tree. Just modify it here accordingly. 
        // ... 
    }   
    
  2. Ou, mettre en œuvre une SystemEventListener mondiale pour PreRenderViewEvent.

    public class YourPreRenderViewListener implements SystemEventListener { 
    
        @Override 
        public boolean isListenerForSource(Object source) { 
         return source instanceof UIViewRoot; 
        } 
    
        @Override 
        public void processEvent(SystemEvent event) throws AbortProcessingException { 
         UIViewRoot view = (UIViewRoot) event.getSource(); 
         // The view is the component tree. Just modify it here accordingly. 
         // ... 
        } 
    
    } 
    

    Pour le faire fonctionner, inscrivez-vous comme ci-dessous faces-config.xml:

    <application> 
        <system-event-listener> 
         <system-event-listener-class>com.example.YourPreRenderViewListener</system-event-listener-class> 
         <system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class> 
        </system-event-listener> 
    </application> 
    
  3. Ou, fournir une coutume ViewHandler dans laquelle vous faites le travail en renderView().

    public class YourViewHandler extends ViewHandlerWrapper { 
    
        private ViewHandler wrapped; 
    
        public YourViewHandler(ViewHandler wrapped) { 
         this.wrapped = wrapped; 
        } 
    
        @Override 
        public void renderView(FacesContext context, UIViewRoot view) { 
         // The view is the component tree. Just modify it here accordingly. 
         // ... 
    
         // Finally call super so JSF can do the rendering job. 
         super.renderView(context, view); 
        } 
    
        @Override 
        public ViewHandler getWrapped() { 
         return wrapped; 
        } 
    
    } 
    

    Pour le faire fonctionner, vous inscrire comme ci-dessous faces-config.xml:

    <application> 
        <view-handler>com.example.YourViewHandler</view-handler> 
    </application> 
    
  4. Ou, crochet ViewDeclarationLanguage#renderView(), mais cela est un peu sur le bord car il est pas vraiment intented à manipuler l'arborescence des composants, mais pour manipuler comment rendre la vue.


non relié au problème concret, tout cela n'est pas la bonne solution pour l'exigence fonctionnelle concrète comme indiqué dans votre question:

qui vise à ajouter un style classe à tous les composants UIInput dans l'arborescence qui ont des messages attachés à eux, et supprime la classe de style si aucun message ne leur est associé

Vous feriez mieux de vous diriger vers une solution côté client plutôt que de manipuler l'arborescence des composants (qui finirait dans l'état du composant JSF!). Imaginez le cas des entrées dans les composants itératifs tels que <ui:repeat><h:inputText>. Il n'y a physiquement qu'un seul composant d'entrée dans l'arbre, pas plusieurs! Manipuler la classe de style via UIInput#setStyleClass() serait présenté dans chaque tour d'itération.

Vous feriez mieux de visiter l'arborescence des composants en utilisant UIViewRoot#visitTree() comme ci-dessous et recueillir tous les ID client des composants d'entrée invalides (cette approche visitTree() prendra transparente des composants itération en compte):

Set<String> invalidInputClientIds = new HashSet<>(); 
view.visitTree(VisitContext.createVisitContext(context, null, EnumSet.of(VisitHint.SKIP_UNRENDERED)), new VisitCallback() { 

    @Override 
    public VisitResult visit(VisitContext context, UIComponent component) { 
     if (component instanceof UIInput) { 
      UIInput input = (UIInput) component; 

      if (!input.isValid()) { 
       invalidInputClientIds.add(input.getClientId(context.getFacesContext())); 
      } 
     } 

     return VisitResult.ACCEPT; 
    } 
}); 

Et puis passer par la suite invalidInputClientIds dans la saveur d'un tableau JSON en JavaScript qui va ensuite les saisir via document.getElementById() et modifier l'attribut className.

for (var i = 0; i < invalidInputClientIds.length; i++) { 
    var invalidInput = document.getElementById(invalidInputClientIds[i]); 
    invalidInput.className += ' error'; 
} 

La bibliothèque d'utilité JSF OmniFaces<o:highlight> a une composante qui fait exactement cela.

+0

merci pour cela. En fait, je n'ai pas touché JSF depuis des années :) mais peut-être que c'est utile pour les autres. – jamiebarrow