[RESOLVIDO] JSF 2: Problema com required=true no h:inputText!

Pessoal, boa tarde !

Estou com um problema no “h:inputText” com atributo “required=true”.
Para entender minha pergunta no final, considere o seguinte exemplo:
Obs.: Segue abaixo um link com um exemplo montado para testarem também, porém vou explicar:

http://www.mandamais.com.br/download/8beq2542011165533

Ao executar o exemplo, irá abrir uma grid com duas colunas preenchidas da seguinte forma:

JOÃO nome   | JOÃO sobreNome
MARIA nome  | MARIA sobreNome

Agora siga os seguintes passos:

* Cliquei no “Editar” do nome JOÃO e repare que na próxima tela aparecerá assim:

Nome      : JOÃO nome;
Sobrenome : JOÃO sobreNome;

* Feito isso clique em voltar;
* Cliquei no “Editar” do nome MARIA e repare que na próxima tela aparecerá assim:

Nome      : MARIA nome;
Sobrenome : MARIA sobreNome;

* Feito isso clique em voltar;
* Depois cliquei novamente no “Editar” do nome JOÃO;
* Na próxima tela apague input nome (“JOÃO nome”) deixando em branco e clique em “Atualizar e Voltar”. Observe que no input nome aparecerá uma mensagem de “Campo Obrigatório”;
* Clique no botão “Voltar”;
* Na grid clique no “Editar” do nome “MARIA nome”;
* Repare que na próxima tela aparecerá aparecerá da seguinte forma:

Nome      : MARIA nome;
Sobrenome : JOÃO sobreNome;

* Obs.: O correto era aparecer da seguinte forma:

Nome      : MARIA nome;
Sobrenome : MARIA sobreNome;

[color=blue]Minha pergunta é:[/color] Porque isso acontece quando tenho um input que é requerido na tela ?
O correto não era ignorar a execução do botão “Atualizar e Voltar” que quando clico em “Voltar” e quando “Editar” novamente entrar com os valores correspondentes ao objeto que selecionei ?

[color=red]Obs.:[/color] Não quero saber como contornar essa questão, isso eu já sei, quero saber por que ocorre isso neste caso.
Isso é um comportamento correto do JSF ou não ?
Eu estou fazendo algum procedimento errado ? Se sim, qual seria a forma correta neste caso ?

Olá nei.junior,

Antes de mais nada, esse problema não está relacionado somente a validação “required”, mas sim qualquer erro na fase de validação (seja conversação ou validação).

Vou tentar explicar de maneira simples, o problema e as possíveis soluções que eu conheço.

Os componentes de inputs (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:

[quote]1) Submitted Value
2) Local Value
3) Value Binding (aka model value)
[/quote]
Vamos entender agora como esses 3 valores mudam nos componentes durante o ciclo de vida:

[quote]** Quando você submete um formulário todos os inputs da página são submetidos como String (parâmetros de request) e na APPLY REQUEST VALUES (2a fase) este valores são setados nos seus devidos componentes de inputs como “submitted value”;

** Após isso, na fase de validação (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 invalido, 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 etc), para cada componente de input setado no modelo o seu “local value” é setado para null e o ciclo termina na RENDER RESPONSE exibindo os valores do modelo (através das EL’s);
[/quote]
O “problema” realmente ocorre na RENDER RESPONSE porque o componente pode retornar qualquer um dos 3 valores, contudo seguindo essa ordem de prioridade:

[quote]** 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;
[/quote]
Entendendo essa prioridade acima você mata a charada do problema :slight_smile:

Esse problema 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 caímos nesse problema.

Todas as soluções que conheço envolve limpar a árvore de componentes que está “suja”:

[quote]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);
[/quote]
Eu acho que acabei falando de mais, isso daria até um post no blog, mas a preguiça nunca me deixou escrever, rss.

No mais, segue alguns links para complementar o que eu disse,



Um abraço e espero que tenha ficado claro!

1 curtida

Viveriamos melhor se todos nos conhecessemos tanto de JSF quanto o Ponte!

Quando eu crescer quero ser igual ao Ponte e ao VinyGodoi! :smiley:

[quote]** Após isso, na fase de validação (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 invalido, 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 etc), para cada componente de input setado no modelo o seu “local value” é setado para null e o ciclo termina na RENDER RESPONSE exibindo os valores do modelo (através das EL’s);
[/quote]

Só fazendo uma observação aqui Rafael, o local value é setado logo após o submitted value ser convertido (se existir um converter associado) e sequer atinge o estágio de validação pois se assim fosse o local value nunca serviria pra nada já que se a conversão foi correta e a validação também então o modelo já seria atualizado e o local value passaria para null correto?

Bom dia a todos !

Rafael, primeiramente quero agradecer pela resposta, foi bastante esclarecedora.
Fazendo alguns testes aqui com o @DaniloMagrini, achamos um procedimento um pouco estranho.
Vou exemplificar o procedimento que fiz primeiramente para depois perguntar:

* No setCliente do “mbTesteRequired” e no método “editar” fiz o seguinte procedimento:

    //Getters e Setters
    //...
    public void setCliente(Cliente cliente) {
        System.out.println("======= Setter =======");
        System.out.println("Nome      = " + cliente.getNome());
        System.out.println("SobreNome = " + cliente.getSobreNome());
        this.cliente = cliente;
    }

    //Métodos
    public String editar() {
        System.out.println("======= Método =======");
        System.out.println("Nome      = " + cliente.getNome());
        System.out.println("SobreNome = " + cliente.getSobreNome());
        this.setRenderizaCadastro(Boolean.TRUE);
        return null;
    }

* Levando em consideração o procedimento que ja expliquei acima, onde clico para editar o “JOÃO”, deixo em branco o nome e clico em “Atualizar e voltar” para dar “Campo Obrigatório”, depois clico no botão “Voltar”, e por fim na grid clico em editar “MARIA” e la na tela de cadastro vem como:

Nome      : MARIA nome;  
Sobrenome : JOÃO sobreNome; 

* Quando pego o que imprimiu no “output” do netBeans esta assim:

INFO: ======= Setter =======
INFO: Nome      = MARIA nome
INFO: SobreNome = MARIA sobreNome

INFO: ======= Método =======
INFO: Nome      = MARIA nome
INFO: SobreNome = MARIA sobreNome

* Agora no momento que cliquei no botão “Editar” do nome “MARIA” ele atualizou o modelo, significa que ele executou todos os outros processos corretamente, ou seja, o “Submitted Value” e o “Local Value” estão como “NULL”.
Agora porque que na tela continua errado ? Não era para vir certo ?

Obrigado por enquanto !

Oi Danilo,

[quote=danilo.magrini]
Só fazendo uma observação aqui Rafael, o local value é setado logo após o submitted value ser convertido (se existir um converter associado) e sequer atinge o estágio de validação pois se assim fosse o local value nunca serviria pra nada já que se a conversão foi correta e a validação também então o modelo já seria atualizado e o local value passaria para null correto?[/quote]

Na verdade eu gostaria de garantir isso para você, mas já li artigos que afirmam que o local value é setado após a validação e outros dizem que é setado logo após a conversão, na qual a validação só cuidaria de invalidar o componente. Mas no geral, para nós “usuários” do framework que não criamos componentes não faz tanta diferença assim. No mais, o local value também é utilizado para eventos que ocorrem imediatamente após a conversão e validação dentro da própria fase de validação, como por exemplo valueChangeListener’s.

Nei,

Lembre-se que a árvore de componentes ficou “suja” após o erro de validação (mas especificamente os inputs do formulário), e o que você fez após clicar no botão “Voltar” foi apenas esconder o formulário (rendered=false). O formulário, independente de quantas requisições AJAX ocorram, ainda permanece “sujo” em memoria e seus componentes de inputs não são processados no ciclo de vida pois o componente parent está rendered=false, por isso os valores internos não mudam (que já estavam “sujos” com o local value antigo, caindo naquela ordem de prioridade do valores: local value antes do model value).

Não sei se ficou claro. Caso tenha ficado confuso é só falar que tento explicar melhor.

Um abraço.

[quote=rponte]
Nei,

Lembre-se que a árvore de componentes ficou “suja” após o erro de validação (mas especificamente os inputs do formulário), e o que você fez após clicar no botão “Voltar” foi apenas esconder o formulário (rendered=false). O formulário, independente de quantas requisições AJAX ocorram, ainda permanece “sujo” em memoria e seus componentes de inputs não são processados no ciclo de vida pois o componente parent está rendered=false, por isso os valores internos não mudam (que já estavam “sujos” com o local value antigo, caindo naquela ordem de prioridade do valores: local value antes do model value).[/quote]
Rafael,

Entendi sim, porém essa questão do AJAX foi o que pensei por priemiro. Portanto nesse exemplo que criei, não sei se você baixou ai, mas se sim pode observar que eu NÃO utilizo AJAX, é tudo requisição normal para recarregar a página toda e mesmo assim continua errado.
Como não é AJAX ele recarrega toda a página novamente, correto ? Com isso ele deveria reconstruir tudo novamente e vir correto, não é ?

Obrigado por enquanto !

Abraço.

[quote=nei.junior]
Rafael,

Entendi sim, porém essa questão do AJAX foi o que pensei por priemiro. Portanto nesse exemplo que criei, não sei se você baixou ai, mas se sim pode observar que eu NÃO utilizo AJAX, é tudo requisição normal para recarregar a página toda e mesmo assim continua errado.
Como não é AJAX ele recarrega toda a página novamente, correto ? Com isso ele deveria reconstruir tudo novamente e vir correto, não é ?

Obrigado por enquanto !

Abraço.[/quote]

Nei,

Eu havia baixado sim seu código. Quando comentei sobre o AJAX foi somente para reforçar, pois normalmente o problema ocorre quando utilizamos muito AJAX. Mas como eu havia dito, seja requisição comum ou AJAX, você continua trabalhando com a mesma árvore de componentes. No seu método editar() você retorna null como regra de navegação, simbolizando para o faces navegar para a mesma página, porém utilizando a mesma árvore de componentes em vez de recriar uma nova árvore.

O problema real está na árvore de componentes “suja”. Nas soluções que eu havia dado mais acima eu comentei sobre utilizar a navegação do faces, mas sem retornar null.

[quote=rponte]
Nei,

Eu havia baixado sim seu código. Quando comentei sobre o AJAX foi somente para reforçar, pois normalmente o problema ocorre quando utilizamos muito AJAX. Mas como eu havia dito, seja requisição comum ou AJAX, você continua trabalhando com a mesma árvore de componentes. No seu método editar() você retorna null como regra de navegação, simbolizando para o faces navegar para a mesma página, porém utilizando a mesma árvore de componentes em vez de recriar uma nova árvore.

O problema real está na árvore de componentes “suja”. Nas soluções que eu havia dado mais acima eu comentei sobre utilizar a navegação do faces, mas sem retornar null.[/quote]

Rafael,

Bom concluindo o assunto, entendi. Você deu uma boa clareada neste caso.
Agora vamos ver uma das soluções postada no inicio por você.
O @DaniloMagrini deu uma ideia de propor na lista oficial do Mojarra a criação de algo como:

FacesContext.getCurrentInstance().getViewRoot().invalidate()

O que acha ? Acho que quando cair nessa situação, pelo menos teria uma forma mais simples e objetiva de contornar.

[quote=rponte]
Na verdade eu gostaria de garantir isso para você, mas já li artigos que afirmam que o local value é setado após a validação e outros dizem que é setado logo após a conversão, na qual a validação só cuidaria de invalidar o componente. Mas no geral, para nós “usuários” do framework que não criamos componentes não faz tanta diferença assim. No mais, o local value também é utilizado para eventos que ocorrem imediatamente após a conversão e validação dentro da própria fase de validação, como por exemplo valueChangeListener’s.[/quote]

O ideal é procurar isso na especificação. Assim que eu tiver um tempo vou vasculhar entre as JSRs 314, 127 e 252 e ver se encontro algo. Porém se fosse para apostar eu apostaria que ele é setado depois da conversão porque pra mim setar o local value depois da validação se tornaria desnecessário uma vez que ele seria setado e logo em seguida receberia null já que o modelo seria atualizado.

[quote=danilo.magrini]
O ideal é procurar isso na especificação. Assim que eu tiver um tempo vou vasculhar entre as JSRs 314, 127 e 252 e ver se encontro algo. Porém se fosse para apostar eu apostaria que ele é setado depois da conversão porque pra mim setar o local value depois da validação se tornaria desnecessário uma vez que ele seria setado e logo em seguida receberia null já que o modelo seria atualizado.[/quote]

Mas é como eu te disse, Danilo, após a validação, dentro da PROCESS VALIDATION, ainda podem ocorrer diversos eventos antes da atualização do modelo. Nesse curto intervalo o modelo precisa estar integro para que alguns eventos funcionem corretamente, como o valueChangeEvent, por exemplo.

[quote=nei.junior]Rafael,

Bom concluindo o assunto, entendi. Você deu uma boa clareada neste caso.
Agora vamos ver uma das soluções postada no inicio por você.
O @DaniloMagrini deu uma ideia de propor na lista oficial do Mojarra a criação de algo como:

FacesContext.getCurrentInstance().getViewRoot().invalidate()

O que acha ? Acho que quando cair nessa situação, pelo menos teria uma forma mais simples e objetiva de contornar.

[/quote]

Nei,

Antes de mais nada, é importante definir bem o que “invalidar” significa. Significa recriar toda a árvore de componentes? Limpar o estado interno de todos os inputs ou todos os componentes?

O pessoal do Trinidad criou algo semelhante, mas acho que somente para inputs, não me recordo. Tanto que foi incorporado ao JSF 2.x. Se eu pesquisar talvez até ache o artigo.

No mais, eu acredito que você não queira invalidar toda a árvore de componentes, mas sim alguns componentes ou grupo de componentes. Então utilizar navegação é a solução mais simples e o próprio faces já te fornecesse isso.

Enfim, provavelmente há uma forte razão para o ciclo de vida e os componentes trabalharem assim! Se você souber de algo no forum não deixe de me informar.

[quote=rponte]
Nei,

Antes de mais nada, é importante definir bem o que “invalidar” significa. Significa recriar toda a árvore de componentes? Limpar o estado interno de todos os inputs ou todos os componentes?

O pessoal do Trinidad criou algo semelhante, mas acho que somente para inputs, não me recordo. Tanto que foi incorporado ao JSF 2.x. Se eu pesquisar talvez até ache o artigo.

No mais, eu acredito que você não queira invalidar toda a árvore de componentes, mas sim alguns componentes ou grupo de componentes. Então utilizar navegação é a solução mais simples e o próprio faces já te fornecesse isso.

Enfim, provavelmente há uma forte razão para o ciclo de vida e os componentes trabalharem assim! Se você souber de algo no forum não deixe de me informar.[/quote]
Rafael,

O invalidar seria limpar o estado interno de todos os inputs, praticamente o que você fez nessa solução.

Quanto a navegação do faces, no caso desse exemplo se ao entrar na grid eu apertasse um botão filtrar e carregar a grid, ai quando entrasse na página de cadastro e voltasse ele perderia os dados da grid pois recriou toda a arvore novamente. Isso é um exemplo simples de como colocar a navegação do faces atrapalharia (de uma certa forma) se quero manter a grid sem ter que filtrar novamente.
Portanto deixando o voltar como NULL ele não recria e não perde. Sendo assim, usando a sua solução ele funcionaria perfeitamente.

Enfim, agora que ficou claro vamos elaborar a melhor forma de trabalhar para resolver isso.
Quero agradecer pela atenção e pelas dúvidas esclarecidas e se eu souber de algo ou achar alguma outra solução, informo todos aqui.

Muito obrigado

Abraço !

Normalmente eu limpo o formulário de maneira explicita antes de navegar para alguma página (exibir o formulário), algo como,

Enfim, é um reset forçado da árvore de componentes :slight_smile:

Um abraço.

Só para constar: escrevi um post sobre o assunto,
http://www.rponte.com.br/2011/06/07/limpando-a-arvore-de-componentes/

:slight_smile: