08 View e Ajax
Compartilhando objetos com a view
Para registrar objetos a serem acessados na view, usamos o método include:
@Resource
class ClientController {
private final Result result;
public ClientController(Result result) {
this.result = result;
}
public void busca(int id) {
result.include("mensagem", "Alguma mensagem");
result.include("cliente", new Cliente(id));
}
}
Agora as variáveis "mensagem" e "cliente" estão disponíveis para uso em seu template engine. É possível registrar o objeto por meio da invocação do método include com um único argumento:
@Resource
class ClientController {
private final Result result;
public ClientController(Result result) {
this.result = result;
}
public void busca(int id) {
result.include("Alguma mensagem").include(new Cliente(id));
}
}
Nesse caso, a primeira invocação registra a chave "string" e a segunda, a chave "cliente". Você pode alterar o comportamento de convenção de chaves no seu próprio TypeNameExtractor.
Custom PathResolver
Por padrão, para renderizar suas views, o VRaptor segue a convenção:
public class ClientsController {
public void list() {
//...
}
}
Este método acima renderizará a view /WEB-INF/jsp/clients/list.jsp. No entanto, nem sempre queremos esse comportamento, e precisamos usar algum template engine, como por exemplo, Freemarker ou Velocity, e precisamos mudar essa convenção. Um jeito fácil de mudar essa convenção é estendendo a classe DefaultPathResolver:
@Component
public class FreemarkerPathResolver extends DefaultPathResolver {
protected String getPrefix() {
return "/WEB-INF/freemarker/";
}
protected String getExtension() {
return "ftl";
}
}
Desse jeito, a lógica irá renderizar a view /WEB-INF/freemarker/clients/list.ftl. Se, ainda assim, isso não for o suficiente, você pode implementar a interface PathResolver e fazer qualquer convenção que você queira, não esquecendo de anotar a classe com @Component.
View
Se você quiser mudar a view de alguma lógica específica, você pode usar o objeto Result:
@Resource
public class ClientsController {
private final Result result;
public ClientsController(Result result) {
this.result = result;
}
public void list() {}
public void save(Client client) {
//...
this.result.use(Results.logic()).redirectTo(ClientsController.class).list();
}
}
Por padrão, existem estes tipos de views implementadas:
- Results.logic(), que vai redirecionar para uma outra lógica qualquer do sistema
- Results.page(), que vai redirecionar diretamente para uma página, podendo ser um JSP, um HTML, ou qualquer URI relativa ao web application dir, ou ao contexto da aplicação.
- Results.http(), que manda informações do protocolo HTTP como status codes e headers.
- Results.status(), manda status codes com mais informações.
- Results.referer(), que usa o header Referer para fazer redirects ou forwards.
- Results.nothing(), apenas retorna o código de sucesso (HTTP 200 OK).
- Results.xml(), serializa objetos em XML.
- Results.json(), serializa objetos em JSON.
- Results.representation(), serializa objetos em um formato determinado pela requisição (parâmetro _format ou header Accept)
Atalhos no Result
Alguns redirecionamentos são bastante utilizados, então foram criados atalhos para eles. Os atalhos disponíveis são:
- result.forwardTo("/some/uri") ==> result.use(page()).forward("/some/uri");
- result.redirectTo("/some/uri") ==> result.use(page()).redirect("/some/uri)
- result.permanentlyRedirectTo("/some/uri") ==> result.use(status()).movedPermanentlyTo("/some/uri");
- result.forwardTo(ClientController.class).list() ==> result.use(logic()).forwardTo(ClientController.class).list();
- result.redirectTo(ClientController.class).list() ==> result.use(logic()).redirectTo(ClientController.class).list();
- result.of(ClientController.class).list() ==> result.use(page()).of(ClientController.class).list();
- result.permanentlyRedirectTo(Controller.class) ==> use(status()).movedPermanentlyTo(Controller.class);
- result.notFound() ==> use(status()).notFound()
- result.nothing() ==> use(nothing());
Além disso, se o redirecionamento é para um método do mesmo Controller, podemos usar:
- result.forwardTo(this).list() ==> result.use(logic()).forwardTo(this.getClass()).list();
- result.redirectTo(this).list() ==> result.use(logic()).redirectTo(this.getClass()).list();
- result.of(this).list() ==> result.use(page()).of(this.getClass()).list();
- result.permanentlyRedirectTo(this) ==> use(status()).movedPermanentlyTo(this.getClass());
Redirecionamento e forward
No VRaptor3, podemos tanto realizar um redirect ou um forward do usuário para uma outra lógica ou um JSP. Apesar de serem conceitos da API de Servlets, vale a pena relembrar a diferença: o redirecionamento acontece no lado do cliente, através de códigos HTTP que farão o browser acessar uma nova URL; já o forward acontece no lado do servidor, totalmente transparente para o cliente/browser.
Um bom exemplo de uso do redirect é no chamado 'redirect-after-post'. Por exemplo: quando você adiciona um cliente e que, após o formulário submetido, o cliente seja retornado para a página de listagem de clientes. Fazendo isso com redirect, impedimos que o usuário atualize a página (F5) e reenvie toda a requisição, acarretando em dados duplicados.
No caso do forward, um exemplo de uso é quando você possui uma validação e essa validação falhou, geralmente você quer que o usuário continue na mesma tela do formulário com os dados da requisição preenchidos, mas internamente você vai fazer o forward para outra lógica de negócios (a que prepara os dados necessários para o formulário).
Escopo Flash automático
Se você adicionar objetos no Result e fizer um Redirect, esses objetos estarão disponíveis na próxima requisição.public void adiciona(Cliente cliente) {
dao.adiciona(cliente);
result.include("mensagem", "Cliente adicionado com sucesso");
result.redirectTo(ClientesController.class).lista();
}
...
<div id="mensagem">
<h3>${mensagem}</h3>
</div>
...
Accepts e o parâmetro _format
Muitas vezes, precisamos renderizar formatos diferentes para uma mesma lógica. Por exemplo queremos retornar um JSON, em vez de um HTML. Para fazer isso, podemos definir o Header Accepts da requisição para que aceite o tipo desejado, ou colocar um parâmetro _format na requisição.
Se o formato for JSON, a view renderizada por padrão será: /WEB-INF/jsp/{controller}/{logic}.json.jsp, ou seja, em geral será renderizada a view: /WEB-INF/jsp/{controller}/{logic}.{formato}.jsp. Se o formato for HTML você não precisa colocá-lo no nome do arquivo. O parâmetro _format tem prioridade sobre o header Accepts.
Ajax: construindo na view
Para devolver um JSON na sua view, basta que sua lógica disponibilize o objeto para a view, e dentro da view você forme o JSON como desejar. Como no exemplo, o seu /WEB-INF/jsp/clients/load.json.jsp:
{ nome: '${client.name}', id: '${client.id}' }
E na lógica:
@Resource
public class ClientsController {
private final Result result;
private final ClientDao dao;
public ClientsController(Result result, ClientDao dao) {
this.result = result;
this.dao = dao;
}
public void load(Client client) {
result.include("client", dao.load(client));
}
}
Ajax: Versão programática
Se você quiser que o VRaptor serialize automaticamente seus objetos para XML ou JSON, você pode escrever em sua lógica:
import static br.com.caelum.vraptor.view.Results.*;
@Resource
public class ClientsController {
private final Result result;
private final ClientDao dao;
public ClientsController(Result result, ClientDao dao) {
this.result = result;
this.dao = dao;
}
public void loadJson(Cliente cliente) {
result.use(json()).from(cliente).serialize();
}
public void loadXml(Cliente cliente) {
result.use(xml()).from(cliente).serialize();
}
}
Os resultados vão ser parecidos com:
{"cliente": {
"nome": "Joao"
}}
<cliente>
<nome>Joao</nome>
</cliente>
Por padrão, apenas campos de tipos primitivos serão serializados (String, números, enums, datas), se você quiser incluir um campo de tipo não primitivo você precisa incluí-lo explicitamente:
result.use(json()).from(cliente).include("endereco").serialize();
vai resultar em algo parecido com:
{"cliente": {
"nome": "Joao",
"endereco" {
"rua": "Vergueiro"
}
}}
Você pode também excluir os campos de tipo primitivo da serialização:
result.use(json()).from(usuario).exclude("senha").serialize();
vai resultar em algo parecido com:
{"usuario": {
"nome": "Joao",
"login": "joao"
}}
Ou ainda você pode serializar recursivamente (cuidado com ciclos):
result.use(json()).from(usuario).recursive().serialize();
result.use(xml()).from(usuario).recursive().serialize();
A implementação padrão é baseada no XStream, então é possível configurar a serialização por anotações ou configurações diretas ao XStream, bastando criar a classe:
@Component
public class CustomXMLSerialization extends XStreamXMLSerialization {
//or public class CustomJSONSerialization extends XStreamJSONSerialization {
//delegate constructor
@Override
protected XStream getXStream() {
XStream xStream = super.getXStream();
//suas configurações ao XStream aqui
return xStream;
}
}
Serializando Collections
Ao serializar coleções, o padrão é colocar a tag "list" em volta dos elementos:
List<Cliente> clientes = ...;
result.use(json()).from(clientes).serialize();
//ou
result.use(xml()).from(clientes).serialize();
vai resultar em algo como:
{"list": [
{
"nome": "Joao"
},
{
"nome": "Maria"
}
]}
ou
<list>
<cliente>
<nome>Joao</nome>
</cliente>
<cliente>
<nome>Maria</nome>
</cliente>
</list>
É possível personalizar o elemento de fora usando o método:
List<Cliente> clientes = ...;
result.use(json()).from(clientes, "clientes").serialize();
//ou
result.use(xml()).from(clientes, "clientes").serialize();
vai resultar em algo como:
{"clientes": [
{
"nome": "Joao"
},
{
"nome": "Maria"
}
]}
ou
<clientes>
<cliente>
<nome>Joao</nome>
</cliente>
<cliente>
<nome>Maria</nome>
</cliente>
</clientes>
Os includes e excludes funcionam como se você os estivesse aplicando num elemento de dentro da lista. Por exemplo se você quiser incluir o endereço no cliente:
List<Cliente> clientes = ...;
result.use(json()).from(clientes).include("endereco").serialize();
com resultado:
{"list": [
{
"nome": "Joao",
"endereco": {
"rua": "Vergueiro, 3185"
}
},
{
"nome": "Maria",
"endereco": {
"rua": "Vergueiro, 3185"
}
}
]}
Serializando JSON sem elemento raiz
Se você quiser serializar um objeto em JSON sem dar nomes a eles, pode fazer isso com o método withoutRoot:
result.use(json()).from(carro).serialize(); //=> {'carro': {'cor': 'azul'}}
result.use(json()).withoutRoot().from(carro).serialize(); //=> {'cor': 'azul'}