Modificando a árvore de componentes JSF em Phaselistener
Pergunta
Estou tendo um problema.
Eu implementei um Phaselistener, que visa adicionar uma classe de estilo a qualquer componente de UIInput na árvore que tenha mensagens anexadas a eles e remove a classe de estilo se não tiver nenhuma mensagem anexada a eles.
O Phaselistener é executado na fase Render_Response e faz o trabalho nos métodos antes da fase e da fase pós -fase durante a depuração. Durante a depuração, descobri que a fase anterior não tem acesso à árvore completa dos componentes, mas a fase após. Quaisquer mudanças feitas na fase pós -fase não são renderizadas.
Como faço para fazer isso? Eu quero que este seja um lado completamente do servidor.
Obrigado,
James
Solução 2
Implementado usando um viewhandler, no entanto, não é eficiente. Phaselistener na fase de resposta de renderização não tem acesso à árvore componente.
Outras dicas
A árvore de componentes JSF está disponível apenas após o tempo de construção. o RENDER_RESPONSE
A fase não é necessariamente um bom momento para ter acesso à árvore completa do componente JSF antes de ser renderizada. Durante uma solicitação inicial inicial sem nenhum <f:viewAction>
, a árvore de componentes completos está disponível apenas no afterPhase
como está sendo construído durante o RENDER_RESPONSE
. Durante um postback, a árvore de componentes completa está disponível no beforePhase
, no entanto, quando uma navegação para uma visão diferente ocorreu, então ela seria alterada durante a RENDER_RESPONSE
fase, então quaisquer modificações se perderiam.
Para saber o que exatamente é o tempo de construção da visão, vá para a pergunta Qual é o tempo de criação de criação?
Você basicamente quer se conectar em "Visualizar tempo de renderizar" em vez de beforePhase
do RENDER_RESPONSE
Estágio. A JSF oferece várias maneiras de prender:
Em algum modelo mestre, anexe um
preRenderView
ouvinte para<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 implementar um global
SystemEventListener
porPreRenderViewEvent
.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. // ... } }
Para executá -lo, registre -o como abaixo em
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 forneça um costume
ViewHandler
onde você faz o trabalhorenderView()
.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; } }
Para executá -lo, registre -se como abaixo em
faces-config.xml
:<application> <view-handler>com.example.YourViewHandler</view-handler> </application>
Ou, enganchar
ViewDeclarationLanguage#renderView()
, mas isso está um pouco no limite, pois não é realmente pretendido manipular a árvore dos componentes, mas manipular como renderizar a vista.
Não relacionado Para o problema concreto, tudo isso não é a solução certa para o requisito funcional concreto, conforme declarado em sua pergunta:
que visa adicionar uma classe de estilo a qualquer componente da UIInput na árvore que tenha mensagens anexadas a eles e remove a classe de estilo se não tiver nenhuma mensagem anexada a eles
Você realmente seria melhor para uma solução do lado do cliente, em vez de manipular a árvore de componentes (que acabaria no estado do componente JSF!). Imagine o caso de entradas em componentes de iteração, como <ui:repeat><h:inputText>
. Existe fisicamente apenas um componente de entrada na árvore, não múltiplo! Manipulando a classe de estilo via UIInput#setStyleClass()
seria apresentado em todas as rodadas de iteração.
É melhor você visitar a árvore de componentes usando UIViewRoot#visitTree()
como abaixo e coletar todos os IDs de clientes de componentes de entrada inválidos (este visitTree()
A abordagem levará em consideração transparentemente os componentes de iteração):
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;
}
});
E depois depois invalidInputClientIds
em sabor de uma matriz JSON para JavaScript que os agarrará via document.getElementById()
e alterar o className
atributo.
for (var i = 0; i < invalidInputClientIds.length; i++) {
var invalidInput = document.getElementById(invalidInputClientIds[i]);
invalidInput.className += ' error';
}
A Biblioteca de Utilitário JSF Omnifaces tem um <o:highlight>
componente que faz exatamente isso.