Click - Programação Web orientada a Componentes e Eventos – Parte III

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:

 

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.

 

clickfig07.JPG

 

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