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:
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.
// ...
}
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>
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>
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.
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