Tentarei ilustrar o problema com um exemplo bem básico. Imagine um formulário com um campo de entrada (h:inputText) para inserir um valor qualquer e um combobox (h:selectOneMenu) que dispara um evento onchange. O evento deve apenas atualizar um terceiro componente (h:outputText) formatando de alguma forma com o valor de entrada.
Para fazer o que é proposto devemos utilizar a propriedade valueChangeListener do componetente selectOneMenu informando o ManagedBean (MB) e o método que deverá ser disparado. No entanto não basta apenas fazer isso pois o método não será disparado automaticamente. Ele só será executado ao submeter o formulário. Para que o método seja disparado logo após a alteração do valor do combo devemos configurar a propriedade onchange do componente com o valor “submit();”.
Veja abaixo um exemplo de um trecho de código da página com os componentes configurados.
<h:form> <h:outputLabel value="Nome:" for="nome" /> <h:inputText id="nome" value="#{nomeMB.nome}" /> <h:selectOneMenu id="opcao" valueChangeListener="#{nomeMB.alteraNome}" onchange="submit();"> <f:selectItem itemLabel="Selecione:" itemValue="0" /> <f:selectItem itemLabel="Primeiro nome" itemValue="1" /> <f:selectItem itemLabel="Nome completo" itemValue="2" /> </h:selectOneMenu> <h:outputText id="nomeExibicao" value="#{nomeMB.nomeExibicao}" /> </h:form>
O exemplo em questão serve apenas para que o usuário escreva um nome completo no campo “nome” e depois troque entre os valores do combo. O valor do campo “nomeExibicao” irá variar entre o primeiro nome e nome completo do valor que foi informado.
Segue o trecho do código do MB.
@ManagedBean @ViewScoped public class NomeMB { private String nome; private String nomeExibicao; ... get e set public void alteraNome(ValueChangeEvent event) { nomeExibicao = ""; if (event.getNewValue().equals("1")) { if (nome != null && nome.trim() != "") { nomeExibicao = nome.split(" ")[0]; } } else if (event.getNewValue().equals("2")) { nomeExibicao = nome; } } }
O problema nesse caso é que o exemplo não funcionará na primeira vez que o valor do combo for alterado. O valor da propriedade nome não será preenchido no momento que o valueChangeListener for chamado.
Para entendermos o por quê devemos compreender o processo de ciclo de vida do JSF.
Cada requisição disparada por uma aplicação JSF segue um ciclo de vida bem definido, dividido em 6 fases.
1. Restaurar visão (Restore View)
O JSF, ao ler um arquivo de interface (XHTML), monta uma hierarquia de componentes, com seus devidos tratadores de eventos e validações registrados. Esta hierarquia é criada na primeira visita ou recuperada nas demais. Isto ocorre nesta fase.
2. Aplicar valores de requisição (Apply Requests)
Nesta fase, todos os dados enviados na requisição podem ser inseridos/atualizados nos componentes JSF. Para isto os conversores são acionados. Caso haja alguma falha de conversão uma mensagem de erro é armazenada no FacesContext, porém apenas será utilizada na fase Render Response.
3. Processar validações (Process Validations)
Nesta fase, o JSF irá processar todos os validadores que foram encontrados na hierarquia de componentes, montada na primeira fase. Caso ocorra uma falha na validação as mensagens são adicionadas no FacesContext e o ciclo de vida é desviado para a última fase, Render Response, assim a página é renderizada, novamente, com as mensagens de erro.
4. Atualizar os valores do modelo ( Update Model Values)
Agora os componentes repassaram seus valores para as propriedades dos beans, que foram mapeadas. Durante este processo, novamente são chamados os conversores. Caso ocorra uma falha na conversão o desvio do fluxo ocorre igual ao desvio de falhas na validação.
5. Invocar a aplicação ( Invoke Application)
Nesta fase, os beans já se encontram preparados para executar a lógica da aplicação. Neste momento os métodos da aplicação serão invocados, por exemplo, na submissão de um form.
6. Renderizar a resposta ( Render Response)
Esta é a última fase do ciclo, quando a interface é construída. A hierarquia de componentes pode ser atualizada e a tela será renderizada.
Voltando ao nosso exemplo, podemos concluir que o evento é disparado antes da atualização do modelo. Neste caso o que pode ser feito é forçar o JSF a executar a fase de atualização do modelo para pegar o novo valor de “nome”. Para fazer isso, no início do método “alteraNome” (método disparado pelo valueChangeListener), adicione as linhas abaixo.
PhaseId phaseId = event.getPhaseId(); if (phaseId.equals(PhaseId.ANY_PHASE)) { event.setPhaseId(PhaseId.UPDATE_MODEL_VALUES); event.queue(); return; }
Isso resolve o problema, embora existam soluções mais elaboradas envolvendo ajax por exemplo.