JSF 2: Como alterar estilo de campos inválidos

Nesse post vou abordar três maneiras possíveis de destacar campos inválidos de uma tela JSF. Em muitos casos, principalmente quando um formulário é grande e as mensagens de erro são exibidas somente na seção superior da tela, é útil para o usuário que os campos que possuem erros sejam destacados de alguma forma para facilitar encontra-los:

Dois formulários, onde um destaca os campos inválidos e outro não.

Dois formulários, onde um destaca os campos inválidos e outro não.

Na imagem acima fica bem nítido que no formulário da direita é bem mais fácil encontrar os campos que precisam de intervenção, pois os dois inputs estão destacados em vermelho. Você pode até estar pensando consigo mesmo que também haveria outra maneira de atingir o mesmo resultado: exibindo a mensagem de erro ao lado do campo, e não em cima do formulário.

Apesar de possível, exibir as mensagens ao lado de cada campo em um formulário muitas vezes é uma tarefa bem mais difícil do que parece. Em um formulário complexo, exibir as mensagens ao lado de cada campo pode acabar deformando o formulário uma vez que vai ser necessário espaço para que as mensagens sejam exibidas. Se você já passou por isso, compartilho da sua dor.

Exibir as mensagens somente no início e destacar os campos como mostrei no exemplo acima passa a ser o mais viável. As três maneiras possíveis que vou demonstrar aqui são:

  1. Usando a variável EL #{component}.
  2. Usando a lib Omnifaces.
  3. Registrando um System Event Listener.

As duas primeiras, usando EL e Omnifaces, tem um problema em comum. E exatamente por causa desse problema que vou apresentar a solução também por meio de um System Listener. Os detalhes virão depois, mas só para adiantar:
Validações feitas dentro do próprio Managed Bean por exemplo, fora da fase de validação do JSF, acabam fazendo com que o componente validado não seja considerado “inválido” pelo JSF e assim causando problemas para quem usar as estratégias 1) ou 2). Os campos nesse cenário não seriam pintados.

Alguns de vocês já devem estar arrancando os cabelos e querendo me condenar a morte por heresia. Afinal de contas, fazer validação “fora da fase de validação”, não usando um Validator nem mesmo uma tag específica, não deveria ser uma opção. Mas na vida real meus amigos, exceções acontecem a toda hora. E o que a vida me ensinou é que o caminho feliz sempre tem seus percalços 🙂

Um exemplo clássico:
Validar um CPF de acordo com CPF’s já existentes na base de dados. Muitas vezes por questão de design não é aceitável criar um Validator JSF e permitir que esse Validator faça acesso ao banco. Essa até pode ser considerada uma validação de negócio, então seria verificada dentro de algum Service já na fase “INVOKE APPLICATION”, depois da fase de validação.

Agora vamos ao que interessa!

1. Usando a variável EL #{component}

Essa estratégia na verdade não é bem novidade para quem já usou JBoss Seam. Isso é inclusive um dos motivos pelos quais digo que, apesar da especificação JSF 2 ser um avanço considerável, ela na prática trás pouca novidade para quem já usava frameworks como JBoss Seam e Richfaces. Tudo que o JSF 2 faz hoje, muita gente (eu inclusive) já vazia parecido ou até melhor usando frameworks de terceiros. Tudo que a especificação JSF 2 fez foi incorporar funcionalidades que já existiam há muito tempo em outros frameworks. Bom, minha guerra declarada ao JSF vai ser assunto de outro post, então continuando… 🙂

Em JSF 2 agora existe uma variável EL implícita chamada component. Então para utilizá-la e conseguir o efeito dos campos pintados de vermelho, bastaria fazer algo parecido com:

<h:head>
    <style type="text/css">
        .invalid-field { /* Essa classe CSS faz a mágica de pintar as bordas dos inputs de vermelho */
            border: #FF0000 solid 1px !important;
            margin: 1px;
        }
    </style>
</h:head>

<h:body>
    <h:form>
        <h:messages globalOnly="false"/>
			
        <h:panelGrid columns="1">
            <h:inputText id="inputTextNome" value="#{myManagedBean.nome}" 
                styleClass="#{not component.valid ? 'invalid-field' : ''}" 
                required="true" size="50" label="Nome"/>
        </h:panelGrid>
   </h:form>
</h:body>

Um formulário como esse de cima se for submetido com o campo obrigatório em branco, vai fazer com que o atributo styleClass receba a classe CSS invalid-field. Essa abordagem funciona perfeitamente mas, além da limitação que já comentei anteriormente das validações feitas diretamente no Managed Bean, também está mais sujeita a erros uma vez que é preciso escrever sempre a mesma coisa em todos os atributos styleClass de todos os campos da tela.

Se você não conseguiu visualizar mentalmente ainda o problema que me referi sobre as validações feitas dentro de Managed Beans, vejam o código abaixo:

//chamado pelo botão "salvar" do form
public void salvar() {
    //teste para verificar se o nome foi preenchido
    if (nome == null || nome.isEmpty()) {
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Campo nome é obrigatório", "Campo nome é obrigatório");
        FacesContext.getCurrentInstance().addMessage("inputTextNome", message); //erro vinculado ao input
    }
}

No trecho acima, a validação foi feita na ação de salvar o próprio formulário, já na fase “Invoke Application” do JSF. Para o JSF, apesar da mensagem de erro ter sido adicionada nas linhas 5 e 6, o componente passou a fase de validação com sucesso e não está inválido. Por isso na hora de renderizar ele não teria seu estilo alterado! Repito, para uma simples validação de obrigatoriedade não faria sentido validar no bean, mas em casos que mencionei como uma validação de CPF com valores cadastrados em um banco de dados, esse cenário de validar no bean começa a ser mais provável.

2. Usando a lib Omnifaces

Ominifaces é uma biblioteca utilitária bem interessante para quem gosta de sofrer com JSF. Tem bastante coisa útil, inclusive a tag que vou falar aqui. Uma explicação sobre essa tag também pode ser encontrada no showcase do próprio Omnifaces, nesse link aqui.

Depois de instalar Ominifaces no seu projeto, basta utilizar a tag <o:highlight/> uma única vez no seu template principal. O vantagem óbvia com relação a usar #{component.valid} é que você precisa colocar essa tag uma única vez para o projeto inteiro. Mas essa estratégia sofre do mesmo problema das validações feitas dentro de Managed Beans! Exemplo:

&lt;h:head&gt;
    &lt;style type=&quot;text/css&quot;&gt;
        .invalid-field { /* Essa classe CSS faz a mágica de pintar as bordas dos inputs de vermelho */
            border: #FF0000 solid 1px !important;
            margin: 1px;
        }
    &lt;/style&gt;
&lt;/h:head&gt;

&lt;h:body&gt;
    &lt;o:highlight styleClass=&quot;invalid-field&quot; /&gt;
    &lt;h:form&gt;
        &lt;!-- campos do form aqui --&gt;
   &lt;/h:form&gt;
&lt;/h:body&gt;

3. Registrando um System Event Listener

Usar um listener é a abordagem que eu particularmente prefiro. As vantagens no meu entender são óbvias:
– É uma solução transparente, vai funcionar independente do que os desenvolvedores façam nas telas. Não vão ter que se preocupar em colocar nenhuma lógica específica como no caso do uso do #{component.valid}.
– Vai funcionar mesmo para as validações “manuais” feitas após a fase de validação.

Se você nunca ouviu falar em um listener JSF pode ser que tenha alguma dificuldade para entender o código. Para quem não sabe do que se trata, eu recomendo que dê uma olhada nesse suporte a eventos do JSF 2, e também em como funciona o ciclo de vida de uma aplicação JSF. Entender o ciclo de vida e todas as suas milhares de fases é fundamental para qualquer um que se considere um “JSF developer”.

Vou colocar aqui os trechos mais relevantes, mas no final do post haverá um link para baixar o projeto e poder olhar o código por completo.

Um listener é uma classe que ouve eventos. E em JSF 2, nós precisamos registrar esse listener no faces-config.xml como mostra o código abaixo:

&lt;application&gt;
    &lt;system-event-listener&gt;
        &lt;system-event-listener-class&gt;com.rodrigouchoa.jsf.style.validation.ValidationListener&lt;/system-event-listener-class&gt;
        &lt;system-event-class&gt;javax.faces.event.PreRenderViewEvent&lt;/system-event-class&gt;
    &lt;/system-event-listener&gt;
&lt;/application&gt;

Não tem mistério. Informamos a nossa classe listener na linha 3, e logo abaixo qual o evento JSF que nos estamos interessados em ser notificados, que é o PreRenderViewEvent. Para esse caso não é possível usar a annotation @ListenerFor, só funciona mesmo com a configuração no faces-config.xml (nessas horas sinto falta do JBoss Seam).

Agora o código do próprio listener. A idéia central é simples, nos percorremos a árvore de componentes do JSF e alteramos o atributo styleClass dos inputs que estejam inválidos, ou que possuam mensagens de erro vinculadas (para cobrir aquele caso das validações feita dentro de Managed Beans).

public class ValidationListener implements SystemEventListener {
    private static final String CSS_CLASS_NAME = &quot;invalid-field&quot;;

    // Percorrer toda a arvore de componentes do JSF, encontrando os inputs
    // que estejam com erro de validação, a alterando a sua classe CSS para &quot;invalid-field&quot;.
    // 
    //  A classe CSS será acrescentada ao componente caso outras classes já existam, sem excluir
    //  as antigas.
    //
    @Override
    public void processEvent(SystemEvent event) throws AbortProcessingException {
		
        UIViewRoot uiViewRoot = (UIViewRoot) event.getSource();
        List&lt;UIInput&gt; inputs = recuperarTodosInputs(uiViewRoot.getChildren());
		
        // Primeiro será necessário retirar a nossa classe CSS de erro de qualquer input
        // que já a possuir. Necessário pois se o componente tiver sido detectado com erro
        // anteriormente e tiver sido corrigido, ele não precisa mais estar destacado. 
        for (UIInput uiInput : inputs) {
            removerCss(uiInput);
        }
		
        /* Percorre os inputs novamente para atribuir a nossa classe CSS de erro
        * aqueles que estiverem inválidos. */
        for (UIInput uiInput : inputs) {
            if (!uiInput.isValid() || possuiMensagensDeErro(uiInput)) { //somente invalidos ou que possuam mensagens de erro
                acrescentarCss(uiInput);
            }
        }
    }

    //etc etc etc

Para quem ainda ta usando JSF 1.2, a solução acima funcionaria da mesma forma se fosse usado um Phase Listener, já que esses System Listeners são novidades do JSF 2.

É isso! Happy coding 🙂

Baixe o código fonte completo nesse link aqui jsf-style-validation.zip.

Quem quer entender um pouco mais sobre o ciclo de vida JSF, pode olhar nesse meu outro post aqui.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s