quinta-feira, 13 de maio de 2010

User Interface Composition (UI composition)

O reaproveitamento de código com facelets é bem intuitivo para quem já trabalha com gerenciadores de conteúdo. Como sempre, é viável, mas tem boas e más notícias.
Como exemplo, podemos criar um dataPaginator que pode ser reaproveitado em outras páginas. Esse é um bom exemplo, pois o código do dataPaginator é bem extenso (ver o artigo http://rfavero.blogspot.com/2010/04/criar-datatable-e-colocar-um-paginador.html).
Para começar, criamos uma pasta dentro de Páginas Web chamada templates, por exemplo. Nela, colocamos um novo arquivo JSF chamado aqui de paginator.

O conteúdo do arquivo paginator.xhtml deve ser modificado desta maneira:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:ice="http://www.icesoft.com/icefaces/component" xmlns:f="http://java.sun.com/jsf/core">
    <body>
        <ui:composition>
        </ui:composition>
    </body>
</html>

Todo o código colocado entre as tags composition será reaproveitado em outra página. Qualquer código fora delas será ignorado.
No arquivo pessoas.xhtml, devemos colocar (se não houver), um id para o formulário:
01  <body>
02      <ice:form id="edit_form">
03          <div align="center">

O código entre as tags <ice:dataPaginator> e </ice:dataPaginator>  (incluindo as mesmas) do arquivo pessoas.xhtml deve ser transferido para o arquivo paginator.xhtml, dentro das tags composition. O início do dataPaginator deve ser modificado para que fique assim:

01 <ui:composition>
02     <ice:dataPaginator for="#{tabela}"
03                        paginator="true"
04                        fastStep="5"
05                        paginatorMaxPages="#{paginas}">
06         <f:facet name="first">
07             <ice:graphicImage

Nas linhas 02 e 05 podemos observar os parâmetros que deverão ser passados pela página que vai utilizar este template. O dataPaginator já foi retirado da página pessoas.xhtml, e no lugar dele vamos inserir o código, destacado em negrito:

01  <ice:panelTab id="panelTab1" label="Lista">
02   <ui:include src="templates/paginator.xhtml">
03       <ui:param name="tabela" value="edit_form:painel:0:tabelaPessoas" /> 
04       <ui:param name="paginas" value="5" /> 
05   </ui:include>
06      <ice:dataTable id="tabelaPessoas"

Na linha 02 está o caminho para o arquivo que vai ser reutilizado, com a tag ui:include. Nas linhas 03 e 04, as declarações dos parâmetros e seus valores. Boas notícias: o código diminui drasticamente, fica mais claro e podemos reaproveitar a pasta templates em outros projetos. A má notícia: podemos observar que na linha 03, o parâmetro que se refere ao componente dataTable deve indicar o id que será gerado pelo iceFaces! Esse id pode ser conseguido olhando o código fonte da página pessoas.xhtml, antes de retirar o dataPaginator.

quinta-feira, 6 de maio de 2010

Validação de formulários

Uma maneira elegante de controlar erros nos formulários construídos com IceFaces é criar uma classe no modelo, onde são criados atributos do tipo String que servirão para controlar a classe CSS utilizada para formatar o erro e a mensagem de erro que será mostrada.
No caso da entidade Person, supondo que apenas os atributos name e lastUpdated serão validados, a classe Erro poderia ser construída como segue: 

01 package modelo;
02 
03 public class Erro {
04 
05     private String name;
06     private String lastUpdated;
07 
08     public Erro() {
09     }
10 
11     public String getLastUpdated() {
12         return lastUpdated;
13     }
14 
15     public void setLastUpdated(String lastUpdated) {
16         this.lastUpdated = lastUpdated;
17     }
18 
19     public String getName() {
20         return name;
21     }
22 
23     public void setName(String name) {
24         this.name = name;
25     }
26 }
No controlador, esse modelo é utilizado para declarar dois novos objetos: 
 
private Erro erros = new Erro();
   private Erro classeErros = new Erro();

É necessário criar os getters e setters para os dois novos objetos. Em seguida, esses objetos serão utilizados ao validar uma instância da classe pessoa:


   private boolean pessoaValida() {
        erros = new Erro();
        classeErros = new Erro();
        boolean naoOcorreuErro = true;
        if (pessoaSelecionada.getName().isEmpty()) {
            erros.setName("Nome é obrigatório.");
            classeErros.setName("classeErro");
            naoOcorreuErro = false;
        }
        if (pessoaSelecionada.getLastupdated() == null) {
            erros.setLastUpdated("Data de atualização é obrigatória.");
            classeErros.setLastUpdated("classeErro");
            naoOcorreuErro = false;
        }
        return naoOcorreuErro;
    }

Um exemplo de validação pode ser implementado no momento de gravar uma alteração:
public void atualizar() {
        if (pessoaValida()) {
            boolean gravou = dao.crud(pessoaSelecionada, "3");
            abaSelecionada = 0;
        }
    }

Também é necessário um arquivo CSS com a classe de estilo do erro que sobrescreve, com a cláusula !important, os atributos CSS utilizados pelo componente panelGroup (pode ser criada uma pasta resources e um arquivo estilos.css, como sugestão) ...

.classeErro {
    background-color: #ff9933 !important;
    padding-right: 2px !important;
    padding-left: 2px !important;
    padding-bottom: 2px !important;
    padding-top: 2px !important;
}

...será necessário referenciar o arquivo CSS criado (no meu caso, em pessoas.xhtml)...


<head>
      <ice:outputstyle href="./xmlhttp/css/rime/rime.css" />
      <ice:outputstyle href="/resources/estilos.css" />
</head>

...e modificar os componentes utilizados para a inserção dos dados:

<ice:outputtext value="Nome:" />
<ice:panelgroup styleclass="#{controlador.classeErros.name}">
   <ice:panelgroup>
      <ice:outputtext value="#{controlador.erros.name}" />
   </ice:panelgroup>
   <ice:inputtext value="#{controlador.pessoaSelecionada.name}" />
</ice:panelgroup>




<ice:outputText value="Atualizado em:" />
<ice:panelGroup  styleClass="#{controlador.classeErros.lastUpdated}">
   <ice:panelGroup>
      <ice:outputText value="#{controlador.erros.lastUpdated}" />
   </ice:panelGroup>
   <ice:inputText value="#{controlador.pessoaSelecionada.lastupdated}" >
      <f:convertDateTime pattern="dd/MM/yyyy" />
   </ice:inputText>
</ice:panelGroup>
 
E o resultado final, ao ocorrer uma tentativa de gravação com algum dos campos requeridos em branco, ficará assim:


Esta técnica para mostrar o campo que está impedindo a validação foi desenvolvida com base na técnica utilizada pelo gerenciador de conteúdos Plone, versão 2.5. Não é uma técnica elegante?