selectOneMenu dependente em JSF

24 respostas
M

Senhores,

preciso criar dois selectOneMenu, sendo que um é dependente de outro. Por exemplo, um selectOneMenu em que é selecionado ESTADO, e dinamicamente, o outro selectOneMenu de MUNICIPIO é carregado.

Pois bem, consigo fazer com que ambos sejam sincronizados, logo, a alteração em um implica na alteração do outro. Contudo, tenho problemas no momento da submissão em que me deparo com erros no último selectOneMenu.

Alguém tem algum código pronto, ou sugestão de como contornar este problema?

24 Respostas

Leozin

qual o problema que acontece quando tu altera o segundo?

andre_a_s

Dá uma olhada na tag <t:saveState/> do MyFaces Tomahawk.
Ele salva o estados dos objetos, tenta colocas 2 tags que salvam os estados das 2 collections q populam a suas combos.
Eu estava com exatamente o mesmo cenario e o mesmo problema, essa tag resolveu meus problemas =)

M

Este é o erro!

WARNING: executePhase(PROCESS_VALIDATIONS 3,com.sun.faces.context.FacesContextImpl@cc0e01) threw exception
javax.faces.FacesException
	at com.sun.faces.lifecycle.ProcessValidationsPhase.execute(ProcessValidationsPhase.java:108 )
	at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:248 )
	at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:117)
	at javax.faces.webapp.FacesServlet.service(FacesServlet.java:244)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at sso.DominoLoginFilter.doFilter(DominoLoginFilter.java:84)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:228 )
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128 )
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:212)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:818 )
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:624)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:445)
	at java.lang.Thread.run(Unknown Source)
Caused by: java.util.NoSuchElementException
	at javax.faces.component.SelectItemsIterator.next(SelectItemsIterator.java:117)
	at javax.faces.component.SelectItemsIterator.next(SelectItemsIterator.java:141)
	at javax.faces.component.SelectItemsIterator.next(SelectItemsIterator.java:49)
	at javax.faces.component.UISelectOne.matchValue(UISelectOne.java:165)
	at javax.faces.component.UISelectOne.validateValue(UISelectOne.java:137)
	at javax.faces.component.UIInput.validate(UIInput.java:868 )
	at javax.faces.component.UIInput.executeValidate(UIInput.java:1071)
	at javax.faces.component.UIInput.processValidators(UIInput.java:663)
	at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1021)
	at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1021)
	at javax.faces.component.UIForm.processValidators(UIForm.java:229)
	at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1021)
	at javax.faces.component.UIViewRoot.processValidators(UIViewRoot.java:662)
	at com.sun.faces.lifecycle.ProcessValidationsPhase.execute(ProcessValidationsPhase.java:100)

Agora veja os códigos que uso, primeiro o JSF:

<h:outputText 
value="Unidade Administrativa"/>

<h:selectOneMenu 
id="unidade" 
immediate="true" 
required="true" 
value="#{DitecConteqCadastrar.unidade}" 
valueChangeListener="#{DitecConteqCadastrar.selecionaUnidade}" 
onchange="submit()">

<f:selectItems 
value="#{DitecConteqCadastrar.unidades}"/>

</h:selectOneMenu>
				
<h:outputText 
value="Seção/Divisão"/>

<h:selectOneMenu 
id="divisao" 
required="true" 
value="#{DitecConteqCadastrar.divisao}">

<f:selectItems 
value="#{DitecConteqCadastrar.divisoes}"/>

</h:selectOneMenu>

<h:commandButton 
value="Cadastrar" 
action="#{DitecConteqCadastrar.cadastrarComputador}"/>

Agora o backing bean:

public class Cadastrar{
	private String unidade;
	private String divisao;
	
	public Cadastrar(){}
	
	public SelectItem[] getUnidades(){
		BDConteq conteq = new BDConteq();
		Hashtable hashtable = conteq.getUnidades();
		Enumeration enumeration = hashtable.keys();
		SelectItem[] unidades = new SelectItem[hashtable.size()];
		int i = 0;
					
		while(enumeration.hasMoreElements()){
			String codigo = (String) enumeration.nextElement();
			String nome = (String) hashtable.get(codigo);				
			unidades[i] = new SelectItem(nome, codigo);
			i++;
		}
			
		return unidades;			
	}
	
	public SelectItem[] getDivisoes(){
		if (unidade != null){
			BDConteq conteq = new BDConteq();
			Hashtable hashtable = conteq.getDivisoes(unidade);
			Enumeration enumeration = hashtable.keys();
			SelectItem[] divisoes = new SelectItem[hashtable.size()];
			int i = 0;
				
			while(enumeration.hasMoreElements()){
				String sigla = (String) enumeration.nextElement();
				String nome = (String) hashtable.get(sigla);
				divisoes[i] = new SelectItem(nome, sigla);
				i++;
			}
				
			return divisoes;		
		}
		return new SelectItem[0];
	}
	
	public String getUnidade(){return unidade;}
	public String getDivisao(){return divisao;}
	public void setUnidade(String unidade){this.unidade = unidade;}
	public void setDivisao(String divisao){this.divisao = divisao;}
	
	public void selecionaUnidade(ValueChangeEvent event){
		if (event.getNewValue() != null){
			unidade = (String) event.getNewValue();
			FacesContext.getCurrentInstance().renderResponse();
		}
	}
	
	public String cadastrarComputador(){
		System.out.println(unidade);
		System.out.println(divisao);
	}
}
gilliard_santos

cara, como o André falou, a solução para o teu problema pode ser o uso da tag t:saveState. Só para não duplicar a explicação do porque isso deve estar acontecendo, dá uma olhada nesse post http://www.guj.com.br/posts/list/48462.java
Espero que ajude.
falow.

Anderson_Schmidt

Olá,

Só para constar nos autos, é possível usar o atributo actionListener do primeiro selectOneMenu, sem nenhum componente ou controle adicional na jsp para tanto.

Basta um método no seu bean (void e que recebe como argumento um ValueChangeEvent) para filtrar o segundo selectOneMenu de acordo com a opção selecionada no primeiro.

Schmidt

M

Agradeço a ajuda de todos, mas o uso dessa tag só ocorrerá em último caso, porque aqui no trabalho temos um ambiente fechado muito burocrático, e atualmente não temos muita flexibilidade na escolha das ferramentas a serem usadas.

Enfim, é algo estranho que no momento que eu clico no botão de Submit ocorre o estranho erro que já mandei em anexo. Não existe nenhuma forma de fazer isso usando apenas JSF?

Anderson_Schmidt

Usando o meu post anterior! :shock:

Flws!

Leozin

caralho, esse selectOneMenu tá dando muito stress, até aqui

por exemplo, o meu funciona adicionando uma list associada ao f:selectItems

o foda é que em outra página o código tá IGUALZINHO e funciona certinho, mas quando a página é renderizada, joga uma excessão (na outra página)

sério, minha paciencia tá quase esgotando aqui…

gilliard_santos

Bom, se nao tem como usar outras coisas então complica. Pela minha esperiência eu indico você dar uma conversada com o pessoal da tua empresa e considerar fortemente o uso daquela tag (t:saveState).
Apesar deu nao ter olhado certinho como você está atualizando o segundo combo :oops: , pelo erro já imagino que seja aquilo que eu comentei no post que mandei o link. Pelo fato do JSF facilitar algumas coisas, a gente acaba esquecendo que por baixo dos panos é tudo request e response, e então a gente estressa mesmo. A solução seria jogar tudo na session :shock: , ou então usar esse componente que é bem inteligente e resolve esse problema.
Para não usar nenhuma dessas alternativas realmente você vai ter dorzinha de cabeça :twisted:
bom, essa é apenas a MINHA opinião, baseada na esperiência que eu tenho.

Anderson_Schmidt

Wow!

Relaxem, se esse for o problema de vocês, fiquem contentes!  :wink: 
Como  foi comentado, existem algumas formas diferentes de fazer essa implementação. 

Gilliard citou uma que utiliza um componente do tomahawk. Não sei como funciona pois nunca usei, mas deve resolver com certeza. Temos um detalhe da dependência que isso agrega, conforme comentado pelo criador deste tópico.

 Uma segunda forma de resolver, usando apenas faces, é a que eu sugerí. Para tanto, usa-se normalmente os dois selectOneMenu. Estes selects encapsulam sim, f:selectItems que apontam pra listas do seu bean. O primeiro selectOneMenu terá 3 particularidades: os atributos "onchange", valueChangeListener (peço desculpas pelo post que fiz antes, havia dito actionListener, está errado) e por fim, o "immediate".

 Esse valueChangeListener é o mais importante, integra a renderização faces com a sua lógica de negócios. Ou seja, o MethodBinding usado nesse atributo precisa ser capaz de buscar a sua lista de Municípios de acordo com o Estado selecionado. O "onchange" é "requisito não funcional", precisa ser usado para que a jsp seja renderizada novamente com os valores das listas atualizadas. E pra terminar, o immediante precisa ser igual a "true", pois é possível que vc tenha outros campos na sua jsp e vc não deseja validá-los (ou preenchê-los caso sejam obrigatórios) para capturar Estados/Municípios.

Se ainda restarem dúvidas, mandem uma MP.

Abraços,
Schmidt

Leozin

ufa finalmente consegui resolver rsrs! quase explodi por causa do stress haha! :-o :-o

esse método submit() é javascript?

andre_a_s

Se vc colocar o seu ManagedBean com o escopo = “session” vai funcionar, mas tem q ver se isso não trará problemas pra vc.

Anderson_Schmidt

Sim, infelizmente. =)

Pode-se usar onchange="this.form.submit();" mesmo.

 Quem usa algum framework auxiliar como Ajax, pode resolver isso de outras formas.

Schmidt

gilliard_santos

Bom, só justificando a minha resposta… é que eu tenho o costume de deixar para os gets resolverem os valores, pois uso ajax e assim, no meu caso, da forma como uso, acredito que fica mais fácil. Mas com certeza da pra fazer de outro jeito. E sobre usar ou não o tomahawk, a dependencia dele é muito pequena (voce pode usar com RI, e é esse o meu caso). Uma das vantagens do JSF é esse “mundaréu” de coisas/componentes que você encontra na net, então podar isso é deixar de usar muita coisa. Mas como é algo da sua organização eu não me meto, foi só um comentário.
Como pra tudo tem várias formas de fazer, escolha uma e bom trabalho :wink:

andre_a_s

Só complementando… pra essa situação, eu uso a tag saveState e populo as combox com ajax usando Ajax4JSF.
Mto bom viu, rápido, simples e sem refresh na página :smiley:
Se conseguir implementar o Ajajax4JSF vai te poupar um teabalho tbm :slight_smile:

Anderson_Schmidt

Concordo com Gilliard,

Cada caso é diferente, projetos com requisitos ou necessidades específicas.

  Eu fiz um mega comentário sobre como fazer o que na prática se traduz em poucas linhas, simples mesmo. Entretanto, usar uma biblioteca com a do tomahawk é muito interessante, as funcionalidades suportadas são muitas e as vezes, em situações delicadas são quase "obrigatórias".

  Eu por exemplo brigo até hoje com dateTables do faces quando preciso renderizar galerias em colunas e linhas.  apelo!. =)

flws!

luisandro

Gostaria de saber como fazer isso, pode me ajudar?

luisandro

Gostaria de saber como fazer isso, pode me ajudar?

thiago.limma

O esquema é simples… vc faz dois array’s. Um com os estados e outro com as UF’s.Ai tu joga num for e manda os ifs pra comparar…
depois tu cria outra function pra chamar isso tudo aew…
vlw

austre

Aproveitando este Tópico que fala de SelectOneMenu Dependentes, vou tentar explicar meu problema atual.

Consegui fazer com que ao selecionar uma opção no primeiro SelectOneMenu o segundo seja carregado com as informações pertinentes a escolha do primeiro. Quando eu escolho pela primeira vez a opção no primeiro SelectOneMenu, a escolha seguinte não funciona como esperado e assim começa um ciclo de alternâncias de funcionamento e não-funcionamento do elemento.

Veja o vídeo em anexo (Flash), creio que com ele dê para entender bem o problema na prática. Prestem atenção nos valores que aparecem em Contrato e o logo abaixo do botão Cadastrar. O primeiro é setado na primeira linha do método getTodosSistemas e o segundo no final da página, ambos listados abaixo.

Alguem pode me esclarecer o erro que estou cometendo?

Obrigado!

:arrow: Anexos

Código da Página
.
.
.
&lt;h:selectOneMenu id="regional" value="#{ConsumidorBean.idRegional}" onchange="submit()" immediate="false"&gt;
   &lt;f:selectItems value="#{RegionalBean.todosParaComboBox}" /&gt;
&lt;/h:selectOneMenu&gt;
 
&lt;h:selectOneMenu id="sistema" value="#{ConsumidorBean.consumidor.sistema.id}"&gt;
   &lt;f:selectItems value="#{ConsumidorBean.todosSistemas}" /&gt;
&lt;/h:selectOneMenu&gt;
.
.
.
&lt;h:outputText value="#{ConsumidorBean.idRegional}" /&gt;
Código do Bean
public class ConsumidorBean {

    private Consumidor consumidor = new Consumidor();
    private GerenciadorConsumidor gerenciador;
    private int idRegional;
    private DataModel model;
    private String mensagem;
    
    public ConsumidorBean() throws Exception{
        gerenciador = new GerenciadorConsumidor();
        consumidor.setCentroCusto(new CentroCusto());
        consumidor.setTipoConsumidor(new TipoConsumidor());
        consumidor.setSistema(new Sistema());
    }
.
.
.   
    public List getTodosSistemas() throws Exception{
        consumidor.setNumContrato(Integer.toString(idRegional));
        List sistemasJsf = new ArrayList();
        List&lt;Sistema&gt; sistemas = new GerenciadorSistema().retornarSistema(idRegional);
        for (Sistema elem : sistemas) {
            String id = Integer.toString(elem.getId());
            sistemasJsf.add(new SelectItem(id, elem.getDescricao()));
        }
        return sistemasJsf;
    }
.
.
.
}
austre

Aleluia consegui fazer o que queria. Segue uma breve explicação de como fiz para ter meus SelectOneMenu dependetes.

Eu tenho a página JSP com o seguinte código.

&lt;h:selectOneMenu id="regional" valueChangeListener="#{ConsumidorBean.regionalChanged}" onchange="submit()"&gt;
   &lt;f:selectItems value="#{RegionalBean.todosParaComboBox}" /&gt;
&lt;/h:selectOneMenu&gt;

&lt;h:selectOneMenu id="sistema" value="#{ConsumidorBean.consumidor.sistema.id}"&gt;
   &lt;f:selectItems value="#{ConsumidorBean.todosSistemas}" /&gt;
&lt;/h:selectOneMenu&gt;

No atributo valueChangeListener da Tag SelectOneMenu eu vinculei o método regionalChanged do meu Bean Consumidor. Isso quer dizer que este método vai ser chamado sempre que o SelectOneMenu tiver o seu valor modificado.

No atributo onchange eu coloquei a instrução submit() para que o formulário seja enviado e ele seja reprocessado. Acho que esta parte é bem clara para todos.

Agora vamos para o Bean

public void regionalChanged(ValueChangeEvent event){
   idRegional = Integer.parseInt((String) event.getNewValue());
}

A assinatura deste método deve ser como está acima: do tipo void e com um parâmetro do tipo ValueChangeEvent. O método getNewValue() retorna um Object ai você tem que fazer os casts como precisar. No meu caso eu preciso de um Inteiro.

:?: Mas porque você não colocou diretamente o cast para Inteiro: idRegional = (Integer) event.getNewValue() :?:

Pelos meus testes eu entendi que mesmo o retorno do método getNewValue() sendo um Object, verdadeiramente o valor é do tipo String. Então eu precisei fazer como fiz. Na verdade eu percebi isso com a mensagem de erro quando tentei usar apenas o cast pra Integer, não sei explicar perfeitamente este detalhe. Se alguém que tenha conhecimento puder contribuir, será de grande valia para todos.

Bem, está ai o mínimo que se precisa para capturar o valor de um SelectOneMenu para popular outro dependente deste.

Para deixar mais claro o funcionamento vou postar o código do método que popula o SelectOneMenu de id sistema.

public List getTodosSistemas() throws Exception{
   List sistemasJsf = new ArrayList();
   List&lt;Sistema&gt; sistemas = new GerenciadorSistema().retornarSistema(idRegional);
   for (Sistema elem : sistemas) {
      String id = Integer.toString(elem.getId());
      sistemasJsf.add(new SelectItem(id, elem.getDescricao()));
   }
   return sistemasJsf;
}

Neste método getTodosSistemas faço uso da propriedade idRegional na terceira linha, cuja recebeu o valor do SelectOneMenu que disparou o método regionalChanged.

Acho que é isso. Nada de avançado demais, mas funcional e tudo isso foi dito, não de forma detalhada, na primeira página deste tópico pelo schmidt.
Comentários são bem vindos, principalmente aqueles que mencionarem cuidados, alertas e possíveis problemas com essa implementação.

Obrigado!

austre

Bem,

Ainda não me livrei dos benditos SelectOneMenu. Estou com um problema quando tento salvar o formulário. Nele eu tenho um SelectOneMenu chamado sistema, que é dependente do SelectOneMenu chamado regional.

Quando eu tento salvar o form recebo a mensagem Erro de validação “sistema”: O valor não é uma opção válida. E neste mesmo momento percebo que o SelectOneMenu sistema não ficou populado como deveria.

Coloquei um vídeo em anexo para esclarecer mais a situação.
Será que alguém sabe como me ajudar?

rponte

Dá uma olhada aqui, isso pode te ajudar,
http://www.rponte.com.br/2008/02/01/selectonemenu-converter-erro-de-validacao/

Abraços e boa sorte.

austre

Cara,

Eu acho que o meu problema não é o mesmo do link que tu passou.

A grosso modo, meu problema é que quando eu submeto o form com o SelectOneMenu sistema tendo uma das opções selecionadas, este objeto não lista novamente todos os itens de acordo com a opção de Regional selecionada.

A princípio eu achei que ia funcionar já que tenho o método getTodosSistemas (Bean em anexo).

Criado 17 de janeiro de 2007
Ultima resposta 4 de jul. de 2008
Respostas 24
Participantes 9