A natureza stateful do JSF nos ajuda em muitos cenários ao desenvolver nossas aplicações Web, cenários estes que não são tão simples assim de implementar com frameworks de natureza estritamente stateless, como a maioria dos frameworks action-like. Por outro lado, problemas que são facilmente solucionados ao se utilizar de frameworks action-like são, algumas vezes, extremamente difíceis de se resolver com frameworks component-based, como JSF.
Durante os treinamentos em JSF da TriadWorks, nós nos preocupamos em, juntamente com os alunos, desenvolver uma aplicação Web que explore cenários o mais próximo da realidade, trazendo diversos problemas à tona para que desta forma possamos escrever a solução mais adequada utilizando o framework. Entre estes problemas, existe um absolutamente incomodo, que é a limpeza da árvore de componentes, mais precisamente dos formulários – ocasionado pela natureza stateful da tecnologia, claro.
Há mais ou menos um mês o @DaniloMagrini pediu uma ajuda no Twitter sobre um problema que a equipe dele estava enfrentando, e o problema era justamente a limpeza da árvore de componentes. Um dos membros da equipe do Danilo, o @NeiAlcantaraJr, postou o problema no GUJ e explicou o mesmo de maneira simples e objetiva, sendo, eu acabei respondendo na própria thread.
Problema
A primeira vez que bati de frente com este problema foi por volta de 2006-2007, logo quando comecei a trabalhar com JSF e os componentes do Ajax4Jsf, e a partir daí já expliquei o mesmo inúmeras vezes na lista de discussão da #javasf – você pode encontrar as threads no histórico do grupo – porém, até hoje, não entendi o porquê de eu nunca ter blogado sobre o assunto.
Para não prolongar muito esse post tentando exemplificar como e quando o problema ocorre, eu vou pegar o gancho na thread do GUJ e somente apresentarei a solução aqui no blog – quase que um copy’n paste da minha resposta na thread, porém com mais alguns poucos detalhes.
Portanto, se você não conhece ainda o problema você pode ler o inicio da thread no GUJ. Alias, melhor ainda se você ler a thread inteira.
Mas para os preguiçosos de plantão, o resumo da novela é: nem sempre limpar os dados no managed bean é suficiente para limpar o formulário da página.
Componentes de input
Antes de irmos direto à solução, vale a pena revisarmos como os componentes de input são processados durante do ciclo de vida do faces, pois eles são um pouquinho mais complicados do que você pensa.
Os componentes de input (todos que implementam EditableValueHolder) possuem 3 (três) tipos de valores que são alterados durante o ciclo de vida de uma requisição, eles são:
- Submitted Value
- Local Value
- Value Binding (aka model value)
Esses 3 valores mudam nos componentes durante o ciclo de vida da seguinte maneira:
- Quando você submete um formulário todos os inputs da página são submetidos como string (parâmetros de request) no corpo HTTP, e na APPLY REQUEST VALUES (2a fase) estes valores são setados nos seus devidos componentes de inputs como “submitted value”;
- Após isso, na fase de validação (PROCESS VALIDATIONS – 3a fase), cada componente de input tenta converter e validar seu “submitted value”, em caso de sucesso o componente define seu novo valor convertido como “local value” e seta para null seu “submitted value”, continuando assim o ciclo de vida. Em caso de erro de conversão ou validação o componente não define seu “local value” e marca o componente como inválido, que por fim pula para última fase (RENDER RESPONSE);
- Se não houver erro na fase de validação o ciclo de vida continua na UPDATE MODEL VALUES para popular o modelo (managed bean, entidades etc), ou seja, o “model value”. Para cada componente de input setado no modelo o seu “local value” é setado para null, daí o ciclo passa pela fase INVOKE APPLICATION e termina na RENDER RESPONSE exibindo os valores do modelo (através das EL’s);
- Se houver submitted value então retorne-o;
- Caso contrário, se o local value é não nulo então retorne-o;
- Caso contrário, avalie a EL, ou seja, chame o getter do modelo;
Solução
Entender a prioridade acima é importante, pois assim você mata a charada do problema.
Esse problema independe do conjunto de componentes que seu projeto utiliza e ocorre muito comumente quando trabalhamos com a mesma árvore de componentes, ou seja, quando utilizamos muito AJAX e/ou navegação orientada a estados. Quando utilizamos a navegação padrão do JSF dificilmente isso ocorre!
Todas as soluções que conheço envolve limpar a árvore de componentes que está “suja”:
- Navegação padrão do Faces para recriar toda a viewroot (não vale retornar null);
- Limpar de maneira educada todos os componentes de inputs;
- Limpar os inputs de maneira “brute force” (parentComponent.getChildren().clear() – eles serão recriados na RENDER RESPONSE novamente, porém só funciona com JSF 1.x, pois o 2.x parece seguir corretamente a spec);
Na maioria das vezes eu me utilizo da segunda opção, a tal da “maneira educada” de limpar a árvore de componentes. Contudo, o @DaniloMagrini relatou que esta abordagem não funciona muito bem quando existem composite components na página. Eu sinceramente não lembro de ter tido esse problema, assim como também não pesquisei sobre o assunto. De qualquer forma, o Danilo melhorou a implementação do método cleanSubmittedValues para funcionar com composite components.
Tendo um dos cuidados acima antes de exibir o formulário ou parte dele (via AJAX) vai garantir que os componentes estejam limpos para que o usuário possa entrar novos dados.
No mais, segue alguns links para complementar o que eu disse:
- http://cagataycivici…/jsf_component_s_value_local/
- http://ovaraksin…/submitted-va…e-local-value-model-value.html
- http://stackoverflow…/jsf-getvalue-v-s-getsubmittedvalue
- http://seamframework.org/Community/NoRerenderAfterValidationFailed
- http://livedemo.exadel.com/richfaces-local-value-demo/
- http://wiki.apache.org/myfaces/ClearInputComponents
Concluindo
Como podem ver, o problema está intimamente ligado a natureza stateful do JSF, e querendo ou não, nós temos que lidar com ela.
Se você estava apressado e pulou a thread no GUJ então eu aconselho novamente: leia a thread por completo, pois com certeza entender o problema, saber como soluciona-lo e principalmente como os componentes de input trabalham durante o ciclo de vida do faces vai te poupar muitas horas de dor de cabeça.
Ótimo post Rafael!!
Utilizo sua ‘maneira educada’ de limpar a árvore de componentes e nunca tive problemas.
Fugindo um pouco do tema.
Entender o ciclo de vida do JSF não é uma tarefa fácil e você sempre fala do Ciclo de Vida, esses dias em uma Thread do grupo JSF vi que algumas pessoas pediram pra você fazer um Post sobre ciclo de vida, reforço o pedido dos amigos da Thread. (Sei que seu tempo deve ser curto, mais faz um esforço aii)
Abraço!
Eae Rafael, blz? Mais um post simples e objetivo como sempre. Lendo teu post lembrei que “esses dias” vi na lista do EG do jsf rolou esse assunto (mas está parado lá) http://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2011-06/message/0
Fala Gilliard,
Eu não tinha visto essa thread na lista do EG, para falar a verdade eu nem mesmo estou inscrito na lista.
Ao ler a thread eu diria ao Cagatay que isso se deve ao modelo “pull” do faces e que ele não pode evitar de “cachear” o local value pois o faces não tem como garantir que o managed bean estará num estado válido ao pular para a RENDER RESPONSE, isto é, no último estado antes da requisição.
No mais, a thread me fez lembrar do antigo post da Apache sobre o problema, http://wiki.apache.org/myfaces/ClearInputComponents . Eu já atualizei meu post e coloquei o link, pois com certeza é um artigo antigo e muito importante sobre o assunto.
Desculpe-me pela ignorância, Gilliard, mas a lista do EG é aberta ao pública?
Como sempre um ótimo post Rafael, como disse o amigo acima sei que seu tempo é pouco e até já fazia um tempo que não postava, mas sempre que há um novo post vira referencia, como o do redered dinamico, que onde se fala nesse ‘problema’ existe junto um link pro seu post, gostaria de reforçar o pedido de um post sobre o ciclo de vida do jsf, talvez nem de pra cobrir tudo em um único post, mas seria de grande ajuda e mais um post de referencia na área.
Excelente post, gostei mesmo muito.
Mais do que uma simples âviagemâ, como se costuma chamar algo diferente, à preciso muito estudo e entendimento do público alvo para determinar como uma peà a especà fica vai ficar.
Legal seus posts Rafael, coloque um sobre padronização e boas praticas do JSF e JPA/Hibernate juntos.
Enrolou com um texto inutil e nao falou como limpa a droga do formulario
Olá lixo,
Agradecemos a preferência 🙂
Tem bater pra tudo! E ainda não saber ler!
Recomendo muito esse post para os meus alunos.. Se for algum deles vou dar eh porradaaa!
Olá Rafael,
Estou apanhando deste erro faz algum tempo. Acompanhei toda thread no GUJ e seu post. Tenho a dizer que vc é o cara!! rsss
Mas no meu caso não consegui resolver.
Eu tenho um dialog que o conteudo é montado dinamicamente através de 3 listas (uma dentro da outra). Onde na primeira uso pra montar um accordionPanel, dentro deste accordion eu itero sobre a segunda lista (ui:repeat) onde escrevo algumas coisas e por fim crio um datatable usando a terceira lista. Dentro deste datatable eu tenho alguns inputText cujo value é apontado para os itens da ultima lista. Ocorre que mesmo fazendo a limpeza da arvore conforme vc mencionou não resolve.
Se ficou confuso posto o codigo pra vc ver.
Oi Rodnei,
Que bom que gostou do post!
Bem estranho nenhuma destas soluções terem funcionado com você, mesmo se tratando de uma caso mais complexo como é o seu.
Você está limpando a árvore de componentes e os dados no managed bean também? Pois é necessário limpar dos dois.
Outra coisa, se puder postar o código, poste no http://gist.github.com/, pois talvez aqui nos comentários não fique bem formatado e pode nem aparecer como deveria.
Olha ai Rafael o código. Estou usando a opção 2 para limpar os componentes. Quanto aos dados do ManagedBean eu atualizo a lista com dados vindos do banco (novos valores).
gistfile1.txt
hosted with ❤ by GitHub
Deixa eu ver se entendi.
O que não está limpando são os inputs dentro dos datatables?
Quando provoco algum erro de validação em algum input e depois fecho o dialog e abro com outros dados, ele mantem os mesmos dados do registro que deu erro. Ele não atualiza o datatable…
Rodney,
Se não me engano o componente de dialog é criado no lado cliente (browser), ou seja, após criado ele fica somente escondido.
O que você precisa fazer é, após limpar a árvore de componentes, repintar o componente de dialog antes de exibi-lo para que as mudanças façam efeito no lado cliente.
Quando vc diz “repintar”, é o mesmo que usar o atributo update?
Muito obrigado pela ajuda!
Sim, sim. É o mesmo que o atributo
update
no Primefaces ourender
dof:ajax
oureRender
no Richfaces.Eu faço isso, tanto que quando não provoco erro de validação a pagina aparece atualizada com os dados do outro registro. O problema ocorre quando da erro e ai clico em outro registro, neste momento o que estava sendo exibido quando deu o erro continua, ou seja, não atualiza.
Você está limpado o bloco (ou
h:form
) de componentes correto? Digo isso, pois normalmente se utiliza um formulário para página principal e um para cada dialog.No caso eu estou usando apenas form. Vou testar com form separado e posto o resultado.