Customizando os Componentes
Vamos dizer que existe o seguinte código, para alterar o método da forma para “get”.
Form form = new Form("form");
//default é post
form.setMethod("get");
Para evitar que isto seja um trabalho repetitivo, pode-se criar um componente customizado, utilizando o conceito de herança. Isto perimite utilizar Orientação a Objetos na camada de visualização:
public class GetForm extends Form {
public GetForm() {
super("form");
setMethod("GET");
}
}
Agora, quando for necessário criar um objeto form, basta escrever a seguinte linha de código:
Form form = new GetForm();
Este foi um exemplo realmente simples, mas se aplicado da forma correta, pode ser muito útil e produtivo.
Customizando o HTML
Todos os componentes possuem o método toString(), responsável por gerar o HTML correspondente:
Abaixo um exemplo de componente customizado, onde o HTML gerado é utilizado para implementar um simples botão de voltar:
public class BackButton extends Button {
public BackButton() {
super("back");
setTitle("Retornar para a tela anterior");
}
public String toString() {
return "<a href='#' onClick='history.back();'> << back
</a>";
}
}
Ainda é possível escrever o HTML utilizando templates do Velocity, conforme visualizado abaixo:
public class BackButton extends Button {
public BackButton() {
super("back");
setTitle("Return to previous page");
}
public String toString() {
Map model = new HashMap();
model.put("model", this);
return getContext().renderTemplate
("/click/templates/voltar.htm", model);
}
}
Para isto basta criar um template “voltar.htm” na pasta /click/templates, no diretório Web da aplicação.
Layout Padrão para páginas HTML
Provavelmente você já ouviu falar do framework Tiles da Jakarta ou Sitemesh da Opensymphony. O Click oferece uma simples alternativa a estes frameworks, para reutilizar um template HTML em todas as páginas da aplicação.
O primeiro passo é escrever uma classe que sobrescreve o método getTemplate() e retorna o html que possui o layout que precisa ser reutilizado.
public class BorderPage extends Page {
public String getTemplate() {
return "border.htm";
}
}
A página do layout “border.htm”, pode ser visualizada abaixo:
<html>
<head>
<title>Click</title>
<link rel="stylesheet" type="text/css" href="style.css" title="Style"/>
</head>
<body>
<h2 class="title">$title</h2>
#parse($path)</body>
</html>
Note que o código #parse($path), vai executar o template da página atual. Agora todas as “Pages” devem herdar de BorderPage, por exemplo:
public class CadastroUsuarioPage extends BorderPage { ...
Além da simplicidade desta solução, aind é possível alterar o template “border.htm” em tempo de execução, uma vez que tudo é controlado no código Java. Isto é possível no Sitemesh através de “Decorators”, mas não no Tiles.
Integrando o Spring
Para integrar o Spring ao Click, e utilizar alguns recursos como Injeção de Dependências, é necessário utilizar o SpringClickServlet, ao invés do ClickServlet, no web.xml.
<servlet>
<servlet-name>click-servlet</servlet-name>
<servlet-class>net.sf.click.extras.spring.SpringClickServlet</servlet-class>
<init-param>
<param-name>spring-path</param-name>
<param-value>/applicationContext.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
O arquivo applicationContext.xml é bem conhecido pelos usuários do Spring, ele contém os beans do Spring, e deve ficar na pasta WEB-INF/classes.
O último passo é criar uma nova Page, chamada SpringPage, para recuperar os serviços declarados no arquivo applicationContext.xml.
public class SpringPage extends BorderPage implements ApplicationContextAware { protected ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
} public Object getBean(String beanName) {
return applicationContext.getBean(beanName);
} public UsuarioService getUsuarioService() {
return (UsuarioService) getBean("usuarioService ");
}
}
As Pages que precisam usufruir dos recursos oferecidos pelo Spring, precisam herdar de SpringPage:
public class CadastroUsuarioPage extends SpringPage { ...
A qualquer momento o método getUsuarioService(), ou outro, pode ser chamado para recuperar a classe de negócios (e suas dependências) adequada para a página atual.
Também é possível utilizar o Spring para criar as Pages, mas isto está fora do escopo deste artigo.
Exemplo, layout HTML + Componente + Listener
Vamos criar um exemplo de página que possui uma Tabela de Pessoas, utilizando um layout manual apenas com Velocity, porém usufruindo de um componente ActionButton no lado do servidor, o qual possui um listener que responde aos eventos da tela.
Crie uma classe Pessoa:
Crie uma classe Pessoa:
public class Pessoa {
private int id;
private String nome;
// TODO: get / sets
}
Þ pessoa.htm
<table id="table">
<tr>
<td>Id</td>
<td>Nome</td>
</tr>
#foreach($p in $pessoas)
<tr>
<td>$p.id</td>
<td>$p.nome</td>
<td><a href="$edit.getOnClick($p.id)" >Editar</a></td>
</tr>
#end
</table>
$!msg
Þ PessoaPage.java
public class PessoaPage extends Page {
public ActionButton edit = new ActionButton(this,"onEdit");
public void onGet() {
Collection list = new ArrayList();
list.add(new Pessoa(1,"Pessoa 1"));
list.add(new Pessoa(2,"Pessoa 2"));
list.add(new Pessoa(3,"Pessoa 3"));
addModel("pessoas", list);
}
public boolean onEdit(){
addModel("msg","Vc clicou na pessoa: " + edit.getValueInteger());
return true;
}
}
Note que o código é muito simples. O código na classe PessoaPage apenas define um ActionButton e o método “onEdit” que será o listener.
No html, nao existe nenhuma novidade, apenas Velocity e um loop para mostrar as pessoas. O método “getOnClick” é chamado passando como argumento o “id” da Pessoa atual. O “id” pode ser recuperado facilmente na classe Page, apenas utilizando o próprio componente ActionButton.
Este exemplo mostra como um modelo orientado a componentes e eventos
é produtivo, mesmo quando o layout HTML é manual.
Exemplo de aplicação CRUD
Neste exemplo, vamos construir uma página de cadastro de Pessoas, que possui um formulário e uma tabela para visualizar todos os registros, conforme a imagem abaixo.
Vamos criar a classe "PessoaService”, para armazenar a lista de Pessoas, e simular o banco de dados. Um variável estática é utilizada para manter a lista em memória.
Þ PessoaService
public class PessoaService {
private static Map pessoas = new LinkedHashMap();
public static void addPessoa(Pessoa p){
pessoas.put(new Integer(p.getId()),p);
}
public static void remove(Integer id){
pessoas.remove(id);
}
public static List getPessoas(){
return new ArrayList(pessoas.values());
}
public static Pessoa get(Integer id) {
return (Pessoa) pessoas.get(id);
}
}
A página “pessoas.htm”, pode ser definida com apenas 2 linhas (onde Form e Table são componentes do Click):
Þ pessoas.htm
$form
$table
A classe PessoaPage que possui os componentes Form e Table do Click, assim como a lógica de negócio, pode ser visualizada abaixo:
public class PessoaPage extends Page {
public Form form = new Form();
public Table table = new Table();
public ActionLink edit = new ActionLink(this,"onEdit");
public ActionLink delete = new ActionLink(this,"onDelete");
public PessoaPage(){
//Form
form.add(new IntegerField("id",true));
form.add(new TextField("nome",true));
form.add(new Submit("salvar","Salvar Pessoa"));
//Table
table.addColumn(new Column("id"));
table.addColumn(new Column("nome"));
//Links Editar e Deletar
Column column = new Column("Action");
ActionLink[] links = new ActionLink[]{edit, delete};
column.setDecorator(new LinkDecorator(table, links, "id"));
table.addColumn(column);
}
//listener: ao clicar no link "edit"
public boolean onEdit(){
Pessoa p = PessoaService.get(edit.getValueInteger());
form.copyFrom(p);
return true;
}
//listener: ao clicar no link "delete"
public boolean onDelete(){
PessoaService.remove(delete.getValueInteger());
return true;
}
//ao clicar em Salvar
public void onPost() {
if(form.isValid()){
Pessoa p = new Pessoa();
form.copyTo(p);
PessoaService.addPessoa(p);
}
}
//atualiza a tabela após
public void onRender() {
table.setRowList(PessoaService.getPessoas());
}
}
Note que o código é extremamente simples. Na classe são definidos os componentes Form e Table. Para o Table, são definidos as colunas e também os links “editar” e “deletar”.
A classe LinkDecorator é um Decorator (pattern) que cria os links para os métodos (listeners) “onEdit” e “onDelete”, os quais são automaticamente chamados pelo Click ao clicar nos links.
O método “onPost” é chamado automaticamente ao clicar em “Salvar Pessoa”, uma vez que isto gera uma request do tipo “post”. Note como um objeto Pessoa é criado e populado automaticamente com o método “copyTo”. Isto copia as informações da Form para o objeto Pessoa. O inverso também existe, copiar as informações da Pessoa para a Form, como é feito no método “onEdit”, utilizando o método “copyFrom”.
O método “onRender” é utilizado para atualizar a tabela. Este método é executado depois de qualquer listener (onEdit,onDelete), e dos métodos (onGet/onPost).