Spring MVC - Java para Desenvolvimento Web
Spring MVC - Java para Desenvolvimento Web
CAPÍTULO 11
APOSTILA JAVA PARA DESENVOLVIMENTO WEB
Spring MVC
"Um homem pinta com seu cérebro e não com suas mãos."
— Michelangelo
Logo se percebeu que o trabalho com Servlets e JSPs puros não era tão produtivo
e organizado. A própria Sun começou a fomentar o uso do padrão MVC e de
patterns como Front Controller. Era muito comum as empresas implementarem
esses padrões e criarem soluções baseadas em mini-frameworks caseiros.
Mas logo se percebeu que o retrabalho era muito grande de projeto para projeto,
de empresa para empresa. Usar MVC era bem interessante, mas reimplementar o
padrão todo a cada projeto começou a ser inviável.
Isso fez com que muitas pessoas o utilizassem para desenvolver suas aplicações,
tornando-o rapidamente a principal solução MVC no mercado Java. Uma das
consequências disso é que hoje em dia ele é um dos mais utilizados no mercado.
No entanto, hoje, ele é visto como um framework que demanda muito trabalho,
justamente por ter sido criado há muito tempo, quando muitas das facilidades da
linguagem Java ainda não existiam.
Struts 1
Struts 2
Apesar do nome famoso, o Struts 2 nunca foi tão utilizado quanto o Struts 1.
Enquanto o Struts 1 era pioneiro na época e se tornou o padrão no
desenvolvimento web Java, o Struts 2 era uma escolha entre vários
frameworks MVC que ofereciam as mesmas facilidades.
Para que possamos aprender o Spring MVC, vamos criar um sistema de lista de
tarefas. E o primeiro passo que precisamos dar é ter o Spring MVC para
adicionarmos em nossa aplicação. Spring MVC vem junto com as bibliotecas do
framework Spring que podemos encontrar no site http://springsource.org. Lá, é
possível encontrar diversas documentações e tutoriais, além dos JARs do projeto.
Uma vez que adicionamos os JARs do Spring MVC em nosso projeto dentro do
diretório WEB-INF/lib, precisamos declarar um Servlet, que fará o papel de Front
Controller da nossa aplicação, recebendo as requisições e as enviando às lógicas
corretas. Para declararmos a Servlet do Spring MVC, basta adicionarmos no
web.xmlda nossa aplicação:
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
O framework Spring possui sua própria configuração XML. O Spring, por ser
muito mais do que um controlador MVC, poderia ser utilizado em ambientes não
Web, ou seja nem sempre o Spring pode se basear no web.xml. Por este motivo,
mas não somente este, o Spring definiu o seu próprio XML com várias opções para
configurar a aplicação.
<mvc:annotation-driven />
<context:component-scan base-package="br.com.caelum.tarefas" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
Isso já é suficiente para começar com o Spring MVC. O arquivo completo, com
todos os cabeçalhos, fica então como:
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
Para criarmos nossa primeira lógica, vamos criar uma classe chamada
OlaMundoController. Nela vamos colocar métodos que são as ações (Action). O
sufixo Controllernão é obrigatório para o Spring MVC porém é uma convenção
do mercado. Como utilizaremos Spring MVC baseado em anotações, é obrigatório
que o seu Controlleresteja dentro do pacote br.com.caelum.tarefasou em um
subpacote. No nosso caso, criaremos a classe dentro do pacote:
br.com.caelum.tarefas.controller.
Dentro dessa nossa nova classe, vamos criar um método que imprimirá algo no
console e, em seguida, irá redirecionar para um JSP com a mensagem "Olá
mundo!". A classe deve ser anotada com @Controller, uma anotação do Spring
MVC. Ela indica ao Spring que os métodos dessa classe são ações(Action).
Podemos criar um método de qualquer nome dentro dessa classe, desde que ele
esteja com a anotação @RequestMapping. A anotação @RequestMappingrecebe um
atributo chamado valueque indica qual será a URL utilizada para invocar o
método, como esse atributo já é o padrão não precisamos definir. Portanto, se
colocarmos o valor olaMundoSpringacessaremos o método dentro do nosso
@Controllerpela URL http://localhost:8080/fj21-tarefas/olaMundoSpring.
Vamos chamar o método execute, mas novamente poderia ser qualquer nome.
Esse método deve retornar uma Stringque indica qual JSP deve ser executado
após a lógica. Por exemplo, podemos retornar "ok" para enviar o usuário para uma
página chamada ok.jsp. O método nao deve retornar o sufixo da página, já que
isso foi configurado no XML do Spring. Também lembrando que o Spring MVC
procura as páginas JSP dentro da pasta WEB-INF/views.
@Controller
public class OlaMundoController {
@RequestMapping("/olaMundoSpring")
public String execute() {
System.out.println("Executando a lógica com Spring MVC");
return "ok";
}
}
Por fim, só precisamos criar o JSP que mostrará a mensagem "Olá mundo!".
Basta criar o arquivo ok.jspdentro da pasta WEB-INF/views/, que mapeamos
anteriormente no XML do Spring.
<html>
<body>
<h2>Olá mundo com Spring MVC!</h2>
</body>
</html>
Caso você esteja em casa, faça o download do Spring Framework e use apenas os
seguintes JARs na sua aplicação:
commons-logging-1.x.x.jar
log4j-1.2.x.jar
mysql-connector-java-5.x.x.jar
slf4j-api-1.6.x.jar
slf4j-log4j12-1.6.x.jar
spring-aspects-3.x.x.RELEASE.jar
spring-aop-3.x.x.RELEASE.jar
spring-beans-3.x.x.RELEASE.jar
spring-context-3.x.x.RELEASE.jar
spring-core-3.x.x.RELEASE.jar
spring-expression-3.x.x.RELEASE.jar
spring-jdbc-3.x.x.RELEASE
spring-web-3.x.x.jar
spring-webmvc-3.x.x.RELEASE.jar
11.7 - EXERCÍCIOS: CONFIGURANDO O SPRING MVC E TESTANDO A
CONFIGURAÇÃO
a. Crie um novo projeto web: File -> New -> Project... -> Dynamic Web Project
chamado fj21-tarefas. Caso a versão do Dynamic web module esteja com 3.0
selecione 2.5.
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
@Controller
public class OlaMundoController {
@RequestMapping("/olaMundoSpring")
public String execute() {
System.out.println("Executando a lógica com Spring MVC");
return "ok";
}
}
d. Falta o JSP que será exibido após a execução da nossa lógica. Crie o JSP ok.jsp
no diretório WebContent/WEB-INF/viewsdo projeto com o conteúdo:
<html>
<body>
<h2>Olá mundo com Spring MVC!</h2>
</body>
</html>
//getters e setters
}
Vamos criar a funcionalidade de adição de novas tarefas. Para isso, teremos uma
tela contendo um formulário com campos para serem preenchidos. Queremos que,
ao criarmos uma nova tarefa, a mesma venha por padrão como não finalizada e,
consequentemente, sem a data de finalização definida. Dessa forma, nosso
formulário terá apenas o campo descricao. Podemos criar um JSP chamado
formulario.jspcontendo somente o campo para descrição:
<html>
<body>
<h3>Adicionar tarefas</h3>
<form action="adicionaTarefa" method="post">
Descrição: <br />
<textarea name="descricao" rows="5" cols="100"></textarea><br />
<input type="submit" value="Adicionar">
</form>
</body>
</html>
@Controller
public class TarefasController {
@RequestMapping("adicionaTarefa")
public String adiciona() {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.adiciona(tarefa);
return "tarefa-adicionada";
}
}
@Controller
public class TarefasController {
@RequestMapping("adicionaTarefa")
public String adiciona(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.adiciona(tarefa);
return "tarefa-adicionada";
}
}
<html>
<body>
Nova tarefa adicionada com sucesso!
</body>
</html>
A nossa aplicação terá várias páginas relacionadas com uma ou mais tarefas.
Queremos organizar a nossa aplicação desde início e separar os arquivos JSPs
relacionadas em subpastas. Ou seja, todas as páginas JSP relacionadas com o
modelo tarefa ficarão na pasta tarefa. Por isso, vamos criar uma nova pasta
tarefadentro da pasta WEB-INF/viewspara os JSPs que o TarefasControllervai
usar. Por padrão, o Spring MVC não procura em subpastas, procura apenas na
pasta views. Vamos mudar o retorno do método adicionae devolver o nome da
subpasta e o nome da página JSP. Nesse caso o retorno fica como
tarefa/adicionada.
@Controller
public class TarefasController {
@RequestMapping("adicionaTarefa")
public String adiciona(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.adiciona(tarefa);
return "tarefa/adicionada";
}
}
@Controller
public class TarefasController {
@RequestMapping("novaTarefa")
public String form() {
return "tarefa/formulario";
}
@RequestMapping("adicionaTarefa")
public String adiciona(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.adiciona(tarefa);
return "tarefa/adicionada";
}
}
Vamos criar o formulário e nossa ação para fazer a gravação das tarefas.
1. O primeiro passo é criar nosso formulário para adicionar uma tarefa. Para isso
crie uma pasta tarefa dentro da pasta WebContent/WEB-INF/viewsDentro da
pasta tarefa adicione um novo arquivo formulario.jsp.
<html>
<body>
<h3>Adicionar tarefas</h3>
<form action="adicionaTarefa" method="post">
Descrição: <br />
<textarea name="descricao" rows="5" cols="100"></textarea><br />
<input type="submit" value="Adicionar">
</form>
</body>
</html>
@Controller
public class TarefasController {
@RequestMapping("novaTarefa")
public String form() {
return "tarefa/formulario";
}
3. Ainda falta o método que realmente adiciona a tarefa no banco de dados. Esse
método é chamado pelo nosso formulário e recebe uma tarefa como parâmetro. Ele
novamente usa a anotação @RequestMappingpara definir a URL.
4. E, por fim, criamos o arquivo adicionada.jsp na pasta tarefa que mostrará uma
mensagem de confirmação de que a tarefa foi efetivamente adicionada.
<html>
<body>
Nova tarefa adicionada com sucesso!
</body>
</html>
5. Reinicie o Tomcat.
mysql -u root
use fj21;
create table tarefas (
id BIGINT NOT NULL AUTO_INCREMENT,
descricao VARCHAR(255),
finalizado BOOLEAN,
dataFinalizacao DATE,
primary key (id)
);
Validando programaticamente
A maneira mais fácil de validar a tarefa é usar vários ifs no método adicionada
classe TarefasControllerantes de chamar dao.adiciona(tarefa), executando
a validação programaticamente. O código seguinte mostra a ideia:
@RequestMapping("adicionaTarefa")
public String adiciona(Tarefa tarefa) {
O problema aqui é quanto mais atributos na tarefa mais ifs teremos. É provável
também que vamos repetir um ifou outro quando validarmos a tarefa em
métodos diferentes, por exemplo, para adicionar ou alterar a tarefa. Sabemos que
copiar e colar código não é uma boa maneira de reaproveitar código. O que
precisamos é de algum artifício que seja igual para qualquer método, algo que
ajude na validação dos dados.
@NotNull @Size(min=5)
private String descricao;
//...
}
Pronto! Com essas anotações, qualquer objeto do tipo Tarefapode ser validado
na camada de controller. Só falta avisar o Spring MVC que realmente queremos
executar a validação. Isso é feito pela anotação Validque devemos usar na antes
do parâmetro da ação:
@RequestMapping("adicionaTarefa")
public String adiciona(@Valid Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.adiciona(tarefa);
return "tarefa/adicionada";
}
Não queremos mostrar uma exceção para o usuário e sim apenas voltar para o
formulário para mostrar uma mensagem que a validação falhou. O Spring MVC
pode guardar o resultado (os erros de validação) em um objeto do tipo
BindingResult. Assim não será lançado um exceção. Este objeto BindingResult
se torna um parâmetro da ação. Então só é preciso perguntar para ele se existe um
erro de validação e se existir, voltar para o formulário. Veja o código:
@RequestMapping("adicionaTarefa")
public String adiciona(@Valid Tarefa tarefa, BindingResult result) {
if(result.hasFieldErrors("descricao")) {
return "tarefa/formulario";
}
@RequestMapping("adicionaTarefa")
public String adiciona(@Valid Tarefa tarefa, BindingResult result) {
if(result.hasErrors()) {
return "tarefa/formulario";
}
O atributo path indica com que atributo essa mensagem está relacionada.
Mensagens internacionalizadas
Na pasta WEB-INF do projeto podemos então criar este arquivo com o seguinte
conteúdo:
O Spring MVC pode carregar automaticamente este arquivo, desde que a linha
abaixo seja incluída no arquivo spring-context.xml:
<fmt:message key="tarefa.adicionada.com.sucesso"/>
Personalizando as mensagens de erros
O arquivo deve ficar dentro da pasta src. Depois podemos referenciar as chaves
das mensagens dentro das anotações também pelo atributo message:
public class Tarefa {
@NotNull(message="{tarefa.descricao.vazia}")
@Size(min=5, message="{tarefa.descricao.pequena}")
private String descricao;
2. Abra a classe Tarefa. Nela é preciso definir as regras de validação através das
anotações do framework Bean validation. A atributo descricao deve ter pelo
menos 5 caracteres:
@NotNull @Size(min=5)
private String descricao;
@RequestMapping("adicionaTarefa")
public String adiciona(@Valid Tarefa tarefa, BindingResult result) {
if(result.hasFieldErrors("descricao")) {
return "tarefa/formulario";
}
Dentro do HTML adicione a tag form:errors acima do tag form. Adicione apenas o
tag form:errors:
<form:errors path="tarefa.descricao"/>
<form action="adicionaTarefa" method="post">
@RequestMapping("listaTarefas")
public String lista() {
JdbcTarefaDao dao = new JdbcTarefaDao();
List<Tarefa> tarefas = dao.lista();
return "tarefa/lista";
}
Essa lista de tarefas deverá ser disponibilizada para o JSP fazer sua exibição.
Para que possamos disponibilizar um objeto para o JSP, temos que alterar o
retorno do método lista. A ideia é que o Spring MVC não só recebe o nome da
página JSP (tarefa/lista) quando chama o método lista, o Spring MVC também
recebe os dados para o JSP. Os dados para a exibição na tela e o nome da página JSP
foram encapsulados pelo Spring MVC em uma classe especial que se chama
ModelAndView. Vamos criar um objeto do tipo ModelAndViewe preencher esse
modelo com nossa lista de tarefas e definir o nome da página JSP. O método lista
deve retornar esse objeto, não mais apenas um String. Veja como fica o código:
@RequestMapping("listaTarefas")
public ModelAndView lista() {
JdbcTarefaDao dao = new JdbcTarefaDao();
List<Tarefa> tarefas = dao.lista();
ModelAndView mv = new ModelAndView("tarefa/lista");
mv.addObject("tarefas", tarefas);
return mv;
}
@RequestMapping("listaTarefas")
public String lista(Model model) {
JdbcTarefaDao dao = new JdbcTarefaDao();
List<Tarefa> tarefas = dao.lista();
model.addAttribute("tarefas", tarefas);
return "tarefa/lista";
}
1. Vamos criar a listagem das nossas tarefas mostrando se a mesma já foi finalizada
ou não.
@RequestMapping("listaTarefas")
public String lista(Model model) {
JdbcTarefaDao dao = new JdbcTarefaDao();
model.addAttribute("tarefas", dao.lista());
return "tarefa/lista";
}
f. Crie o JSP que fará a exibição das tarefas dentro da pasta WebContent/WEB-
INF/views/tarefa. Chame-o de lista.jspe adicione o seguinte conteúdo:
<table>
<tr>
<th>Id</th>
<th>Descrição</th>
<th>Finalizado?</th>
<th>Data de finalização</th>
</tr>
<c:forEach items="${tarefas}" var="tarefa">
<tr>
<td>${tarefa.id}</td>
<td>${tarefa.descricao}</td>
<c:if test="${tarefa.finalizado eq false}">
<td>Não finalizado</td>
</c:if>
<c:if test="${tarefa.finalizado eq true}">
<td>Finalizado</td>
</c:if>
<td>
<fmt:formatDate
value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy"/>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
Tarefas podem ser adicionadas por engano, ou pode ser que não precisemos
mais dela, portanto, queremos removê-las. Para fazer essa remoção, criaremos um
link na listagem que acabamos de desenvolver que, ao ser clicado, invocará a nova
ação para a remoção de tarefas passando o código da tarefa para ser removida.
O link pode ser feito com HTML na lista de tarefas da seguinte forma:
<td><a href="removeTarefa?id=${tarefa.id}">Remover</a></td>
@RequestMapping("removeTarefa")
public String remove(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.remove(tarefa);
return "para onde ir???";
}
Uma das formas que poderíamos fazer esse redirecionamento é enviar o usuário
diretamente para a página que lista as tarefas (tarefa/lista.jsp). Mas, essa não
é uma boa abordagem, porque precisaríamos, outra vez, disponibilizar a lista das
tarefas para o JSP, algo que já fazemos na ação de listar as tarefas, o método lista
na classe TarefasController.
@RequestMapping("removeTarefa")
public String remove(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.remove(tarefa);
return "forward:listaTarefas";
}
@RequestMapping("removeTarefa")
public String remove(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.remove(tarefa);
return "redirect:listaTarefas";
}
<td><a href="removeTarefa?id=${tarefa.id}">Remover</a></td>
2. Criaremos a tela para fazer a alteração das tarefas, como por exemplo, marcá-la
como finalizada e definirmos a data de finalização.
a. Primeiro vamos criar um novo link na nossa listagem que enviará o usuário
para a tela contendo os dados da tarefa selecionada:
<td><a href="mostraTarefa?id=${tarefa.id}">Alterar</a></td>
b. Vamos criar uma nova ação que dado um id, devolverá a Tarefa
correspondente para um JSP, que mostrará os dados para que a alteração possa
ser feita.Crie um novo método mostrana classe TarefasController:
@RequestMapping("mostraTarefa")
public String mostra(Long id, Model model) {
JdbcTarefaDao dao = new JdbcTarefaDao();
model.addAttribute("tarefa", dao.buscaPorId(id));
return "tarefa/mostra";
}
<html>
<body>
<h3>Alterar tarefa - ${tarefa.id}</h3>
<form action="alteraTarefa" method="post">
Descrição:<br />
<textarea name="descricao" cols="100" rows="5">
${tarefa.descricao}
</textarea>
<br />
@DateTimeFormat(pattern="dd/MM/yyyy")
private Calendar dataFinalizacao;
@RequestMapping("alteraTarefa")
public String altera(Tarefa tarefa) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.altera(tarefa);
return "redirect:listaTarefas";
}
A questão é que não queremos navegar para lugar algum ao clicarmos nesse
link. Queremos permanecer na mesma tela, sem que nada aconteça, nem seja
recarregado.
AJAX nada mais é do que uma técnica que nos permite enviar requisições
assíncronas, ou seja, manter a página que estava aberta intacta, e recuperar a
resposta dessa requisição para fazermos qualquer processamento com eles. Essas
respostas costumam ser XML, HTML ou um formato de transferência de dados
chamado JSON (Javascript Object Notation).
Para as funções basta passarmos o endereço que queremos invocar, como por
exemplo:
$.get("minhaPagina.jsp")
Nesse caso, estamos enviando uma requisição via GET para o endereço
minhaPagina.jsp.
Sabendo isso, vamos criar um link que invocará uma função Javascript e fará
requisição AJAX para uma ação que finalizará a tarefa:
<td><a href="#" onclick="finalizaAgora(${tarefa.id})">
Finalizar agora
</a></td>
<script type="text/javascript">
function finalizaAgora(id) {
$.get("finalizaTarefa?id=" + id);
}
</script>
Por fim, basta criarmos a nossa ação que receberá o parâmetro e invocará um
método do JdbcTarefaDaopara fazer a finalização da tarefa. No entanto, a
requisição que estamos fazendo não gerará resposta nenhuma e nós sempre
retornamos uma String o resultado que determina qual JSP será exibido. Dessa vez,
não exibiremos nem um JSP e nem invocaremos outra Action. O protocolo HTTP
sempre retorna um código indicando qual é o estado dessa resposta, ou seja, se foi
executado com sucesso, se a página não foi encontrada, se algum erro aconteceu e
assim por diante.
O protocolo HTTP define que o código 200 indica que a execução ocorreu com
sucesso, portanto, vamos apenas indicar na nossa resposta o código, sem devolver
nada no corpo da nossa resposta. Para setar o código da resposta
programaticamente precisamos do objeto HttpServletResponse. Podemos
receber a resposta HTTP como parâmetro de qualquer método que é uma ação. Com
a resposta na mão podemos chamar o método setStatus(200).
@RequestMapping("finalizaTarefa")
public void finaliza(Long id, HttpServletResponse response) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.finaliza(id);
response.setStatus(200);
}
<script type="text/javascript">
function finalizaAgora(id) {
$.get("finalizaTarefa?id=" + id, function(dadosDeResposta) {
alert("Tarefa Finalizada!");
});
}
</script>
Repare que $.getrecebe mais uma função como parâmetro (também chamado
callback de sucesso). Nela, definimos apenas um simples alert para mostrar uma
mensagem ao usuário. Também é possível manipular o HTML da página
dinamicamente. O jQuery oferece recursos poderosos para alterar qualquer
elemento HTML dentro do navegador.
$("#tarefa_"+id).html("Tarefa finalizada");
http://api.jquery.com/jQuery.get/http://api.jquery.com/id-selector/
<mvc:default-servlet-handler/>
Agora é a melhor hora de aprender algo novo
Conheça a Alura.
<mvc:default-servlet-handler/>
Na pasta WebContentcrie uma nova pasta resources, vamos colocar nela tudo
relativo a conteúdo estático do nosso sistema.
2. Vamos adicionar AJAX na nossa aplicação. Para isso, utilizaremos o jQuery que
precisamos importar para nosso projeto e em nossas páginas.
a. Vá ao Desktop, e entre em Caelum/21;
<head>
<script type="text/javascript" src="resources/js/jquery.js"></script>
</head>
3. Caso a tarefa não esteja finalizada, queremos que ela possua uma nova coluna
que se chamará "Finalizar agora". Ao clicar, chamaremos via AJAX uma Action
que marcará a tarefa como finalizada e a data de hoje será marcada como a data de
finalização da mesma.
a. Altere a coluna que mostra a tarefa como não finalizada no arquivo lista.jsp.
Adicione um link que ao ser clicada, chamará uma função Javascript passando o
idda tarefa para finalizar. Também adicione uma id para cada elemento <td>.No
arquivo procure o c:if para tarefas não finalizadas, altere o elemento tddentro
c:if:
<table>
<!-- Resto da página com a tabela -->
4. Vamos criar o método para finalizar a tarefa. Após o mesmo ser executado, ele
não deverá nos redirecionar para lugar nenhum, apenas indicar que a execução
ocorreu com sucesso.
@RequestMapping("finalizaTarefa")
public void finaliza(Long id, HttpServletResponse response) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.finaliza(id);
response.setStatus(200);
}
$(elementoHtml).closest("tr").hide();
Para resolver esse problema, de alguma forma o nosso Controller deveria passar
uma data para nosso jQuery. Mas como? Uma solução possível seria escrevê-la
direto no response. Algo parecido com isso:
@RequestMapping("finalizaTarefa")
public void finaliza(Long id, HttpServletResponse response) throws
IOException {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.finaliza(id);
Date dataDeFinalizacao =
dao.buscaPorId(id).getDataFinalizacao().getTime();
String data = new
SimpleDateFormat("dd/MM/yyyy").format(dataDeFinalizacao);
response.getWriter().write(data);
response.setStatus(200);
}
Resolve nosso problema mas ainda assim teríamos que ficar trabalhando direto
em um camada mais baixa que são as classes de HTTPServletRequeste
HTTPServletResponse. Agora além de finalizar uma tarefa nossa action tem as
responsabilidades de buscar pela data finalizada, formatar a data, escrever no
response e mudar o status para 200. Responsabilidade de mais, não é mesmo?
Retornar uma JSP já nos traz o benefício do status 200, necessário para nossa
função de callback no jQuery. Sabendo disso usaremos uma JSP para renderizar a
data formatada. Mas antes é preciso passar essa data a nossa JSP, ou simplesmente
passar uma Tarefapara ela e depois fazer com que a Action retorne a String
referente a JSP.
@RequestMapping("finalizaTarefa")
public void finaliza(Long id, Model model) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.finaliza(id);
model.addAttribute("tarefa", dao.buscaPorId(id));
return "tarefa/dataFinalizada";
}
<fmt:formatDate value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" />
<script type="text/javascript">
function finalizaAgora(id) {
$.post("finalizaTarefa", {'id' : id}, function(resposta) {
$("#tarefa_"+id).html("Finalizado");
alert(resposta);
});
}
</script>
<td id="tarefa_data_${tarefa.id}">
<fmt:formatDate value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" />
</td>
<script type="text/javascript">
function finalizaAgora(id) {
$.post("finalizaTarefa", {'id' : id}, function(resposta) {
$("#tarefa_"+id).html("Finalizado");
$("#tarefa_data_"+id).html(resposta);
});
}
</script>
O nosso desafio foi cumprido de forma elegante, mas ainda assim poderíamos
melhorar o nosso código. A função de callback do AJAX tem que modificar dois
<td>'s e isso tornaria repetitivo para modelos com mais alterações que a nossa
Tarefa. Sabendo que a função recebe apenas um parâmetro de resposta, teríamos
mais problemas ao ter que passar mais de um parâmetro a ela. Como resolver esta
questão? Uma solução viável é passar a própria <tr>, completa, com as alteração
necessárias. Para isso uma alteração na JSP se faz necessária.
<td>${tarefa.id}</td>
<td>${tarefa.descricao}</td>
<td>Finalizada</td>
<td>
<fmt:formatDate value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" />
</td>
<td><a href="removeTarefa?id=${tarefa.id}">Remover</a></td>
<td><a href="mostraTarefa?id=${tarefa.id}">Alterar</a></td>
@RequestMapping("finalizaTarefa")
public String finaliza(Long id, Model model) {
...
return "tarefa/finalizada";
}
<td>
<fmt:formatDate
value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" />
</td>
<td><a href="removeTarefa?id=${tarefa.id}">Remover</a></td>
<td><a href="mostraTarefa?id=${tarefa.id}">Alterar</a></td>
</tr>
</c:forEach>
....
</table>
E por último, falta mudar a função de callback para que esta modifique o
conteúdo do <tr>.
...
$.post("finalizaTarefa", {'id' : id}, function(resposta) {
$("#tarefa_"+id).html(resposta);
});
...
Agora sim, temos um código simples e fácil de manter. Tudo o que um bom
programador gostaria de encontrar.
1. Vamos buscar uma tarefa e passá-la para nossa JSP através do Model. Abra o
TarefasController.javae modifique a action que finaliza uma tarefa para o que
segue:
@RequestMapping("finalizaTarefa")
public String finaliza(Long id, Model model) {
JdbcTarefaDao dao = new JdbcTarefaDao();
dao.finaliza(id);
model.addAttribute("tarefa", dao.buscaPorId(id));
return "tarefa/finalizada";
}
<td>${tarefa.id}</td>
<td>${tarefa.descricao}</td>
<td>Finalizada</td>
<td><fmt:formatDate value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" /></td>
<td><a href="removeTarefa?id=${tarefa.id}">Remover</a></td>
<td><a href="mostraTarefa?id=${tarefa.id}">Alterar</a></td>
....
<c:forEach items="${tarefas}" var="tarefa">
<tr id="tarefa_${tarefa.id}">
<td>${tarefa.id}</td>
<td>${tarefa.descricao}</td>
<td>
<fmt:formatDate
value="${tarefa.dataFinalizacao.time}"
pattern="dd/MM/yyyy" />
</td>
....
</tr>
.....
4. E agora para fazer uso do conteúdo renderizado pela JSP, é necessário que a
função de callback do AJAX receba como parâmetro esse conteúdo. Vamos alterar a
função do finalizaAgorano mesmo arquivo lista.jsp, para o que segue:
<script type="text/javascript">
function finalizaAgora(id) {
$.post("finalizaTarefa", {'id' : id}, function(resposta) {
$("#tarefa_"+id).html(resposta);
});
}
</script>
Facebook Newsletter