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.

10 thoughts on “URLs amigáveis no JSF 2.0

  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. 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. 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. 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. 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?

  6. @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!

  7. Pingback: Gilliard Cordeiro » Como trabalhar com ViewScope e Page

  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!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>