Limpando a árvore de componentes

June 7th, 2011

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:

  1. Submitted Value
  2. Local Value
  3. 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);
Na última fase (RENDER RESPONSE), o componente pode retornar qualquer um dos 3 valores, contudo seguindo essa ordem de prioridade:
  1. Se houver submitted value então retorne-o;
  2. Caso contrário, se o local value é não nulo então retorne-o;
  3. 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”:

  1. Navegação padrão do Faces para recriar toda a viewroot (não vale retornar null);
  2. Limpar de maneira educada todos os componentes de inputs;
  3. 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:

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.

Problema do rendered dinâmico com JSF

December 1st, 2010

Desde o surgimento dos conjuntos de componentes Ajax passou-se a aproveitar melhor as features do JSF e destes componentes para trazer ao usuário uma UI mais rica e na medida do possível mais leve. Graças a estes componentes ficou mais fácil desenvolver estes tipos de UI quase que transparentemente da mesma forma convencional (sem Ajax) que estávamos acostumados a escrever nossas páginas.

Entre tantos benefícios dessa abordagem apareceram alguns problemas que também não estávamos acostumados a lidar, como saber o que enviar e repintar antes de uma requisição Ajax, ou saber limpar um formulário adequadamente quando necessário ou mesmo repintar determinados componentes de exibição dinâmica.

Pegando este último problema como tema deste post, o que eu tenho notado é que ainda hoje em dia ele é um dos problemas mais comuns de acontecer e ao mesmo tempo um dos mais discutidos em fóruns e listas de discussão. Sendo, nada mais justo do que explana-lo aqui no blog e facilitar a vida de muitos, assim como a minha.

Problema

Este problema, que muitos ainda desconhecem, é comumente conhecido como “problema do rendered dinâmico” (bem, ao menos é como eu o chamo!) e só acontece quando trabalhamos com JSF e Ajax - independente do conjunto de componentes - mais especificamente após tentarmos repintar (ou rerenderizar, como você queira chamar) um componente com o atributo rendered dinâmico, como este:


    

Este componente poderia ser repintado após algum evento Ajax disparado pelo usuário na tela, como por exemplo ao selecionar o tipo de cadastro entre pessoa física ou jurídica, algo similar a isto:


    
    
    

Aparentemente não há qualquer problema com os trechos de código acima, principalmente se o componente iniciar visível para o usuário (ou seja, a EL ${myBean.pessoaFisica} do rendered estiver sendo avaliada para true).

Após mudar para o tipo pessoa jurídica é disparado um evento Ajax, o componente é repintado e desaparece da tela, como era de se esperar, claro. Contudo se você alterar a opção para o tipo pessoa física o componente não reaparecerá para o usuário. E é aí onde está o problema!

Se você for esperto, após alguns minutos analisando o problema, notará que a requisição Ajax foi disparada pelo componente tipoDePessoa (Firebug, plugin para Firefox, pode te ajudar aqui), o estado do managed bean foi alterado e o ciclo de vida do Faces foi processado sem qualquer erro.

E a partir daí, depois de refazer e analisar todos os passos básicos e sensatos, perde-se inúmeras horas tentando descobrir onde está de fato o problema neste simples cenário. A primeira vista parece ser um bug do JSF ou do conjunto de componentes, mas na verdade não é.

Solução

Sendo direto e sem rodeios: você não pode rerenderizar um componente na qual possua o atributo rendered dinâmico, ou seja, o componente não pode possuir EL para definir seu estado de visibilidade para o usuário.

Para resolver isso, repinte o componente parent do componente que possui o rendered dinâmico. Lembrando que este componente parent não pode ter o atributo rendered dinâmico, ou seja, ele sempre precisa ser renderizado.

Componentes como a4j:outputPanel ou mesmo h:panelGroup são excelentes componentes para servirem como componente parent. Sabendo disto, o código apropriado seria algo como:


    
        
    

E o componente responsável pelo evento Ajax deve repintar, no caso acima, o a4j:outputPanel (componente parent sem rendered dinâmico) e não mais o h:panelGroup.

Entendendo o problema

Para quem ainda não sabe, o atributo rendered (que por padrão é true) dos componentes é responsável por tornar o componente visível ou não para o usuário, ou seja, sempre que este atributo for true o componente poderá gerar código XHTML para ser exibido na página. Caso ele seja false, normalmente, nenhum código XHTML é gerado. Vale lembrar que mesmo o rendered sendo false o componente ainda existe na árvore de componentes no lado servidor.

Não é interessante tentar rerenderizar um componente com rendered dinâmico pois quando o atributo rendered é avaliado para false o componente (código XHTML na verdade) não aparece na árvore DOM da página. Sendo, após repintar este mesmo componente (agora com rendered avaliado para true) o código JavaScript do Richfaces responsável por atualizar o bloco de código onde se encontraria o componente na árvore DOM não consegue achar a posição exata para repintar na página (para injetar o novo código XHTML), já que o código XHTML do componente não existe mais. Então, no final, o Javascript do Richfaces acaba simplesmente não fazendo nada.

Por isso, quando você repinta um componente parent qualquer (que sempre tem o atributo rendered avaliado para true) o Richfaces consegue encontrar a posição exata deste componente na página e injetar o novo código XHTML, e como o componente com rendered dinâmico é um nó (filho) do componente parent ele também é repintado.

Concluindo

O problema é simples e a solução mais simples ainda! Nada como ler a documentação do seu conjunto de componentes favorito para entender e evitar este problema. Vale salientar que este problema ocorre com praticamente todos os conjuntos de componentes Ajax.

Acho que para um assunto simples como este eu falei mais do que devia. Espero que tenha ficado claro ou ao menos tenha dado uma luz para quem está enfrentando essa dor de cabeça. Caso queria saber mais sobre o assunto você poderá consultar a documentação do Richfaces, mais especificamente os tópicos 5.5 e 5.6.1.

Enfim, estou feliz por ter tirado mais um post antigo da minha lista de drafts! Atualmente minha rotina diária não tem me permitido investir o tempo que eu gostaria para blogar, mas cá estamos nós novamente, certo? :-)