Home > JavaEE, JSF > URLs amigáveis no JSF 2.0

URLs amigáveis no JSF 2.0

Hoje vou falar de mais uma novidade do JSF 2, cuja falta era motivo de muita reclamação: URLs amigávies, bookmarking, método GET e outros nomes que podemos dar. O suporte a essa nova funcionalidade é dado por dois pares de componentes, de um lado h:button e h:link e do outro f:metadata e f:viewParam.

Os componentes h:button e h:link servem para originar as ações jsf assim como os componentes h:command{Button|Link}, porém usando GET em vez de POST. Esses componentes possuem um atributo chamado outcome, que representa a regra de navegação do JSF, assim como seria colocar uma String diretamente na action do h:command{Button|Link}. Para passar parâmetros colocamos a tag f:param dentro do h:link ou h:button.

Como exemplo vamos ver uma aplicaçãozinha que tem uma tela de listagem e outra de visualização de Pessoas. A seguir um trecho da página de listagem de pessoas, listarPessoas.xhtml:

<h:form>
	<h:dataTable value="#{pessoaController.pessoas}" var="pessoa">
		<h:column>
			#{pessoa.nome}
		</h:column>
		<h:column>
			<h:link outcome="/verPessoa" value="Editar">
				<f:param name="pessoa" value="#{pessoa.nome}"/>
			</h:link>
		</h:column>
	</h:dataTable>
</h:form>

Nesse exemplo, no outcome eu já estou usando o esquema novo de navegação comentado no post passado. O link acima vai gerar uma url parecida com http://localhost:8080/exemplojsf2/verPessoa.jsf?pessoa=fulano_0.

Agora do lado da página que recebe a requisição temos os componentes f:metadata e f:viewParam. A primeira é apenas uma tag que engloba as f:viewParam. Já as tags f:viewParam se comportam de forma muito parecida com um h:inputText, podemos dizer que praticamente a única diferença é no input a gente digita em um formulário, enquanto na f:viewParam escrevemos na URL.

A tag f:viewParam possue os seguintes atributos: converter, converterMessage, required, requiredMessage, validator, validatorMessage, value, valueChangeListener, maxlength e for (este último voltado para o novo esquema de component composition do Facelets). Como podemos ver, é praticamente um h:inputText.

Vamos ver então um trecho do código da página que recebe a requisição, verPessoa.xhtml:

<f:view>
	<f:metadata>
	    	<f:viewParam name="pessoa" value="#{pessoaController.pessoaSelecionada}" />
   	</f:metadata>
<h:head>
    <title>Ver Pessoa</title>
</h:head>
<h:body>
	<h:form>
		Nome: #{pessoaController.pessoaSelecionada.nome}
	</h:form>
</h:body>
</f:view>

Não precisei colocar um converter no f:viewParam pois configurei um converter “forClass” como veremos mais a frente.

Agora a nossa classe

public class Pessoa {
 
	private String nome;
 
	public Pessoa() {
	}
	public Pessoa(String nome) {
		this.nome = nome;
	}
 
	//getter e setter suprimido
}

Isso mesmo, a classe é complexa desse jeito  :D

Como o intuito é só um exemplo, eu nem me preocupei com banco de dados ou algum mecanismo mais interessante, apenas criei um conversor para mostrar que o novo esquema não permite apenas o uso de Strings. Segue o código do conversor:

@FacesConverter(forClass=Pessoa.class)
public class PessoaConverter implements Converter {
 
	@Override
	public Object getAsObject(FacesContext context, UIComponent component, String string) {
 
		return new Pessoa(string);
	}
 
	@Override
	public String getAsString(FacesContext context, UIComponent component, Object object) {
 
		return ((Pessoa)object).getNome();
	}
 
}

E agora nosso managed bean que recebe não uma String, e sim objetos do nosso domínio. Eu falo isso o tempo todo pois preciso deixar isso bem claro, senão eu fico doido de ver uma aplicação usando JSF passando String e Integer de um lado pro outro :(

Mas tudo bem, deixando o desabafo pra lá vamos ao código:

@ManagedBean(name="pessoaController")
@RequestScoped
public class PessoaController {
 
	private Pessoa pessoaSelecionada;
	private List<pessoa> pessoas;
 
	@PostConstruct
	public void init()
	{
		pessoas = new ArrayList<pessoa>();
		for (int i = 0; i < 10; i++) {
			pessoas.add(new Pessoa("fulano_" + i));
		}
	}
 
	//getters e setters suprimidos
}

Acredito que por hoje seja suficiente. Vou ver se em breve escrevo algo mais específico sobre facelets.

  1. Ezequiel de Witt
    May 29th, 2009 at 18:58 | #1

    Olá Gilliard, tenho acompanhando seu blog com frequência, é a principal referência em português de JSF2. Parabéns.
    Seguinte, estou estudando um pouco JSf 2.0 e me surgiu uma dúvida, com faço pra saber a linha que está sendo mostrada utilizando h:datatable ou o ui:repeat, como tem o varStatus.count no jstl c:forEach?

    grato,Ezequiel

  2. June 17th, 2009 at 16:43 | #2

    Desculpa pela demora na resposta, mas é que eu passei por uns probleminhas de manutenção no site. Realmente a falta desse atributo é algo negativo. No meu caso, como utilizo o RichFaces, no componente rich:dataTable tem um atributo chamado rowKeyVar que faz o mesmo papel.

  3. Emanuel
    December 25th, 2009 at 10:01 | #3

    Eu sou novo com JSF, mas no exemplo:
    Digamos que Pessoa tenha mais de um atributo String, como ficaria…?
    A classe PessoaConverter teria um método para cada um?

  4. January 7th, 2010 at 08:56 | #4

    Emanuel, desculpa a demora em responder, mas é que eu estava de férias (off-line).
    Eu dei um exemplo propositalmente simples, mas caso fosse um objeto “real”, você precisaria passar como referência algo que fosse suficiente para você recuperar o objeto. Na prática acaba sendo a chave primária ou alguma coisa que via conta te permita chegar na mesma (caso de segurança por exemplo).
    Dê uma olhada no mecanismo de conversão do JSF, o mesmo usado nos selects, pois é a mesma idéia.

  5. Léo
    March 17th, 2010 at 09:48 | #5

    Olá Gilliard,
    muito com seu blog cara!

    Sucesso pra vc.

  6. Paulo Pinheiro
    November 4th, 2010 at 19:38 | #6

    O que eu custo a entender é por que a especificação JSF faz tanta frescura pra gente passar um objeto serializado de uma view para outra.
    Funciona com o f:setPropertyActionListener, mas o managed bean de destino tem que ser RequestScoped, porque o parâmetro está no map de request.

    Afinal o padrão Java Bean não foi idealizado pra que objetos inteiros sejam passados pela rede?

  7. November 17th, 2010 at 12:18 | #7

    @Paulo Pinheiro, o managed bean de destino pode ser de qualquer escopo.

    Por acaso você está usando um bean ViewScoped? Se for isso teu problema é outro. O que acontece na verdade é que sempre que a view muda o bean é destruído (como o nome do escopo sugere). Mas o chato dessa história é que o JSF faz isso no momento em que a view muda, e não quando o request termina. Então em uma mesma requisição o jsf seta o valor, destrói o bean, e depois como provavelmente a próxima view usa o mesmo bean, uma outra instância dele é criado e como o valor não vai mais estar lá dá a impressão que o jsf não chamou o set.

    A solução pra isso é usar os parâmetros de view. É tosco, mas os caras quiseram fazer assim. Pois o Seam já faz isso há muito tempo com o escopo Page. Vou fazer um post explicando melhor isso, pois aqui fica uma resposta pobre. Tem uma extensão para cdi que a apache tá fazendo que é um escopo que funciona como o view, mas deixa o request terminar antes de eliminar o bean (na minha opinião e pelo jeito na tua também esse seria o comportamento ideal por padrão).

    Volta aqui no blog semana que vem que ja terei escrito um post com mais detalhes sobre isso :)

    Valeu!

  8. Erick Basilio
    February 10th, 2011 at 21:38 | #8

    Valeu eim cara, estava procurando resolver problemas com bookmarking no JSF 2 e ia implementar umas soluções complexas, aqui aprendi mais algumas coisas como as diferenças entre hlink e h:commandLink além do simples outcome, que é o que diferenciava pra mim. Vou sempre visitar seu blog, já está adicionado em meus favoritos. Parabéns pela iniciativa!

  9. February 11th, 2011 at 10:53 | #9

    Obrigado @Erick Basilio. Geralmente a solução mais simples é a mais certa mesmo. Ainda bem que hoje em dia são poucos os casos onde o “jeito certo” de fazer algo é complicado.

  1. November 23rd, 2010 at 16:06 | #1