Atividade Templates com FreeMarker (EstanteWeb)
Disciplina: Programação para Web 3
Assunto: Geração dinâmica de páginas, templates e FreeMarker
Professor: Prof. Me. Danilo Bueno
Grupos: duplas
Duração: 4 aulas de 50 minutos (em sala)
Importante
Antes de começar, leia o material de apoio sobre templates e FreeMarker. É imprescindível ler antes de codar: a atividade aplica exatamente os conceitos do material. Se tiver dúvidas, releia, pergunte ao professor ou aos colegas. Você também pode consultar os slides da aula.
Contextualização
Até agora, toda página HTML da EstanteWeb foi montada por concatenação de String dentro do controller, incluindo a listagem do acervo (/livros/html) e a listagem de sugestões (/sugestoes). Funciona, mas qualquer ajuste visual exige editar texto dentro de código Java, sem destaque de sintaxe e com alto risco de esquecer uma tag. Nesta etapa, a dupla vai substituir essas páginas por templates FreeMarker de verdade, e vai revisitar uma decisão tomada lá na Semana 2: por que os controllers eram @RestController, e o que muda ao usar @Controller.
O que já temos
O checkpoint de partida (semana-04-inicio) traz a aplicação Spring Boot das semanas anteriores, já rodando em http://localhost:8080, com:
PaginaController: rotas/,/sobre(GET, texto simples) e/contato(GET com formulário HTML manual, POST recebendo os dados).Livro(modelo):titulo,autor,ano,preco.LivroRepository: acervo em memória, comlistarTodos(),contarLivros()elistarAtePreco(double).LivroController: rotas/livros,/livros/total,/livros/html(HTML montado manualmente),/livros/busca(comResponseEntity) e/livros/quem-acessa. Todas as rotas de texto/HTML usam@RestController.SugestaoeSugestaoRepository: guardam sugestões de leitura em memória.SugestaoController: rotas/sugestoes/nova(formulário manual),POST /sugestoeseGET /sugestoes(listagem HTML manual). Também@RestController.
Como começar
git clonedo repositório do projeto (pule este passo se sua dupla já clonou nas semanas anteriores).git checkout semana-04-iniciopara partir exatamente do estado descrito acima, mesmo que sua dupla não tenha terminado a semana passada.- Rode a aplicação (
./mvnw spring-boot:runou o botão Run da IDE) e confirme quehttp://localhost:8080/livros/htmlresponde como antes (a lista de livros montada manualmente).
Como funciona
- A atividade é feita em dupla, mas as entregas no Moodle são individuais.
- As missões são incrementais: cada uma constrói sobre a anterior, siga na ordem.
- Ao final de cada missão, teste no navegador antes de seguir.
- Responda, no caminho, às perguntas em destaque (marcadas com
>). Elas voltam no questionário do Moodle.
Cronograma
| Aula | Foco | Missões |
|---|---|---|
| 1 | Ligando o FreeMarker | Missão 1 |
| 2 | Variáveis dinâmicas com Model | Missão 2 |
| 3 | Convertendo o acervo para um template | Missão 3 |
| 4 | Reaproveitando o padrão + desafio | Missão 4 |
Missão 1: Ligando o FreeMarker
Objetivo: adicionar a dependência do FreeMarker ao projeto e confirmar que um template estático é renderizado.
1.1. Abra o pom.xml do projeto e adicione, dentro de <dependencies>, a dependência do FreeMarker:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
Salve o arquivo. Se sua IDE perguntar se deseja recarregar o projeto Maven, aceite.
1.2. Crie a pasta src/main/resources/templates/ (se ainda não existir) e, dentro dela, o arquivo ola.ftlh, com um HTML estático simples (sem nenhuma variável), por exemplo um título de boas-vindas.
Dica de VS Code: por padrão, o editor abre
.ftlhsem destaque de sintaxe. Com o arquivo aberto, useCtrl+Shift+P(Cmd+Shift+Pno Mac) → Change Language Mode → HTML → Configure File Association for ‘.ftlh’… → HTML de novo. Faça isso agora, uma única vez; vale para todos os.ftlhdo projeto daqui em diante.
1.3. Crie a classe TesteController, anotada com @Controller (não @RestController), com uma rota @GetMapping("/ola") que apenas retorna "ola" (o nome do template, sem extensão e sem parênteses de método). Reinicie a aplicação e acesse http://localhost:8080/ola no navegador.
O método de
TesteControllerretornou a String"ola", mas o navegador não exibiu o texto “ola” na tela. Compare com o que acontecia nos controllers@RestControllerdas semanas anteriores: o que o Spring faz com o valor retornado quando o controller é@Controller, em vez de escrever esse valor direto no corpo da resposta?
Missão 2: Variáveis dinâmicas com Model
Objetivo: enviar dados do controller para o template através do Model.
2.1. Em ola.ftlh, acrescente uma variável no meio do texto, por exemplo ${nomeLoja}, dentro de uma frase de boas-vindas.
2.2. Altere o método de TesteController para receber um parâmetro Model model, chamar model.addAttribute("nomeLoja", "EstanteWeb") antes do return, e mantenha o retorno "ola". Reinicie e recarregue /ola: confirme que o nome aparece na página.
2.3. Adicione uma segunda variável ao template e ao Model, desta vez numérica (por exemplo, um ${anoFundacao} com o valor 1998). Recarregue a página e observe com atenção como o número aparece.
O número apareceu como
1,998, com uma vírgula que você não escreveu em lugar nenhum. Releia, no material de apoio, a explicação sobre formatação de números do FreeMarker (seção 4) e ajuste o template usando o built-in indicado lá, até o número aparecer como1998.
2.4. Confirme que, após o ajuste, tanto ${nomeLoja} quanto o ano aparecem corretamente na página, sem vírgula.
Remova temporariamente a linha
model.addAttribute("nomeLoja", ...)do controller, sem remover${nomeLoja}do template, e recarregue a página. O que acontece? Depois, devolva a linha removida.
Missão 3: Convertendo o acervo para um template
Objetivo: substituir a listagem manual de /livros/html por um template FreeMarker de verdade, ajustando o controller para conviver com as rotas que continuam devolvendo texto cru.
3.1. Crie o template livros.ftlh, com um título e uma lista <ul>, usando <#list livros as livro> para percorrer o acervo e exibir ${livro.titulo}, ${livro.autor} e ${livro.preco?c} em cada <li> (o ?c que você usou na Missão 2 evita que o preço ganhe separador de milhar, mantendo o mesmo formato da rota /livros).
3.2. Em LivroController, troque @RestController por @Controller. Isso muda o comportamento de todos os métodos da classe que retornam String: adicione @ResponseBody aos métodos listar(), total() e quemAcessa(), para que continuem devolvendo o texto cru como antes (o método buscar(), que já usa ResponseEntity, não precisa de nenhum ajuste).
3.3. Reescreva o método da rota /livros/html: remova o produces = "text/html" e a montagem manual da String, adicione um parâmetro Model model, preencha model.addAttribute("livros", repositorio.listarTodos()) e troque o retorno para "livros". Reinicie a aplicação e acesse /livros/html: confirme que a lista aparece igual a antes, agora gerada pelo template.
O acervo tem um livro cujo título contém a tag
<b>Grande</b>(adicionado na Semana 2, para mostrar a diferença de Content-Type). Como esse título aparece agora, renderizado por${livro.titulo}num arquivo.ftlh, comparado a como aparecia na versão antiga, montada por concatenação de String? O que explica a diferença?
Missão 4: Reaproveitando o padrão
Objetivo: tratar o caso de acervo vazio com <#if> e repetir a conversão em SugestaoController, consolidando o padrão aprendido.
4.1. Em livros.ftlh, envolva a lista num <#if livros?size == 0>, exibindo uma mensagem alternativa (por exemplo, “Nenhum livro no acervo no momento”) quando não houver livros, e a lista normal no <#else>. Não é preciso esvaziar o acervo de verdade para conferir a lógica; basta ler o código e garantir que ele cobre os dois casos.
4.2. Repita a conversão em SugestaoController: troque @RestController por @Controller; adicione @ResponseBody ao método de /sugestoes/nova (o formulário, que continua sendo montado manualmente por enquanto) para preservar seu comportamento (o método de POST /sugestoes, que usa ResponseEntity, não precisa de ajuste); crie o template sugestoes.ftlh com <#list> e converta o método de GET /sugestoes para usar Model e retornar o nome do template.
4.3. Confirme, testando as duas rotas convertidas (/livros/html e /sugestoes) no navegador, que o resultado visual é idêntico ao da versão anterior, mesmo com o HTML agora vindo de arquivos .ftlh separados.
4.4. Desafio (opcional): crie o template cabecalho.ftlh, com um cabeçalho simples (nome da loja e um link de volta para /), e insira-o com <#include "cabecalho.ftlh"> no início de livros.ftlh e de sugestoes.ftlh. Confirme que o cabeçalho aparece nas duas páginas.
Antes do
<#include>, se a dupla quisesse adicionar um link de navegação em todas as páginas HTML da EstanteWeb, seria preciso editar quantos arquivos? E depois de extrair o cabeçalho compartilhado?
Entrega individual
Cada integrante da dupla deve, individualmente, no Moodle:
- Responder o questionário da semana (perguntas de reflexão sobre a atividade).
- Enviar o projeto compactado em
.zip(a pasta do projeto sem a subpastatarget/, que é gerada na compilação e pesa à toa).