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?

quinta-feira, 22 de abril de 2010

O componente rowSelector do IceFaces

A intenção de utilizar rowSelector é que quando o internauta selecionar uma linha do dataTable, o objeto correspondente seja editado automaticamente. Podemos fazer isso preparando o dataTable já existente para funcionar dentro de um panelTabSet. Essa estratégia permitirá que adicionemos um formulário de edição em uma das abas do panelTabSet e enviemos para ele os dados da linha que for selecionada.
No backing bean (ControladorBean.java) serão necessários dois novos atributos:

private int abaSelecionada = 0;
private Person pessoaSelecionada;

Sem esquecer que os gets e sets também serão necessários. Em seguida, é preciso um método que seja disparado quando uma linha for selecionada no dataTable:

    public void selecionouLinha(RowSelectorEvent linha) {
        int linhaSelecionada = linha.getRow();
        lista.setRowIndex(linhaSelecionada);
        pessoaSelecionada = (Person)lista.getRowData();
        abaSelecionada = 1;
    }

Para gravar as alterações, um método que se comunica com a camada de persistência:

    public void atualizar() {
        boolean gravou = dao.crud(pessoaSelecionada, "3");
        abaSelecionada = 0;
    }

E também um método para cancelar a alteração em curso e voltar para a lista de pessoas:

    public void cancelar() {
        pessoaSelecionada = new Person();
        abaSelecionada = 0;
    }

Finalmente, o código completo e atualizado da página (pessoas.xhtml)

001 <?xml version='1.0' encoding='UTF-8' ?>
002 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
003     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
004 <html xmlns="http://www.w3.org/1999/xhtml" 
005       xmlns:ui="http://java.sun.com/jsf/facelets"
006       xmlns:f="http://java.sun.com/jsf/core"
007       xmlns:h="http://java.sun.com/jsf/html"
008       xmlns:ice="http://www.icesoft.com/icefaces/component">
009     <head>
010         <ice:outputStyle href="./xmlhttp/css/rime/rime.css" />
011     </head>
012     <body>
013         <ice:form>
014             <div align="center">
015                 <h1><ice:outputText value="Pessoas" /></h1>
016                 <ice:panelTabSet id="painel" selectedIndex="#{controlador.abaSelecionada}">
017                     <ice:panelTab id="panelTab1" label="Lista">
018                         <ice:dataPaginator for="tabelaPessoas"
019                                            paginator="true"
020                                            fastStep="5"
021                                            paginatorMaxPages="3">
022                             <f:facet name="first">
023                                 <ice:graphicImage
024                                     url="./xmlhttp/css/xp/css-images/arrow-first.gif"
025                                     style="border:none;"
026                                     title="Início"/>
027                             </f:facet>
028                             <f:facet name="last">
029                                 <ice:graphicImage
030                                     url="./xmlhttp/css/xp/css-images/arrow-last.gif"
031                                     style="border:none;"
032                                     title="Final"/>
033                             </f:facet>
034                             <f:facet name="previous">
035                                 <ice:graphicImage
036                                     url="./xmlhttp/css/xp/css-images/arrow-previous.gif"
037                                     style="border:none;"
038                                     title="Anterior"/>
039                             </f:facet>
040                             <f:facet name="next">
041                                 <ice:graphicImage
042                                     url="./xmlhttp/css/xp/css-images/arrow-next.gif"
043                                     style="border:none;"
044                                     title="Próxima"/>
045                             </f:facet>
046                             <f:facet name="fastforward">
047                                 <ice:graphicImage url="./xmlhttp/css/xp/css-images/arrow-ff.gif"
048                                                   style="border:none;"
049                                                   title="Avanço rápido"/>
050                             </f:facet>
051                             <f:facet name="fastrewind">
052                                 <ice:graphicImage url="./xmlhttp/css/xp/css-images/arrow-fr.gif"
053                                                   style="border:none;"
054                                                   title="Retrocesso rápido"/>
055                             </f:facet>
056                         </ice:dataPaginator>
057                         <ice:dataTable id="tabelaPessoas"
058                                        value="#{controlador.lista}"
059                                        var="item" border="1"
060                                        rows="3"
061                                        sortColumn="#{controlador.colunaOrdenada}"
062                                        sortAscending="#{controlador.ordemInversa}">
063                             <ice:column>
064                                 <ice:rowSelector selectionListener="#{controlador.selecionouLinha}" />
065                                 <f:facet name="header">
066                                     <ice:commandSortHeader columnName="Nome" arrow="true" >
067                                         <ice:outputText value="Nome"/>
068                                     </ice:commandSortHeader>
069                                 </f:facet>
070                                 <ice:outputText value="#{item.name}" />
071                             </ice:column>
072                             <ice:column>
073                                 <f:facet name="header">
074                                     <ice:commandSortHeader columnName="Tratamento" arrow="true" >
075                                         <ice:outputText value="Tratamento"/>
076                                     </ice:commandSortHeader>
077                                 </f:facet>
078                                 <ice:outputText value="#{item.jobtitle}" />
079                             </ice:column>
080                             <ice:column>
081                                 <f:facet name="header">
082                                     <ice:commandSortHeader columnName="Atualizado em" arrow="true">
083                                         <ice:outputText value="Atualizado em"/>
084                                     </ice:commandSortHeader>
085                                 </f:facet>
086                                 <ice:outputText value="#{item.lastupdated}">
087                                     <f:convertDateTime pattern="dd/MM/yyyy" />
088                                 </ice:outputText>
089                             </ice:column>
090                         </ice:dataTable>
091                     </ice:panelTab>
092                     <ice:panelTab label="Editar">
093                         <ice:panelGrid columns="2">
094                             <ice:outputText value="Nome:" />
095                             <ice:inputText value="#{controlador.pessoaSelecionada.name}" />
096                             <ice:outputText value="Tratamento:" />
097                             <ice:inputText value="#{controlador.pessoaSelecionada.jobtitle}" />
098                             <ice:outputText value="Atualizado em:" />
099                             <ice:inputText value="#{controlador.pessoaSelecionada.lastupdated}" >
100                                 <f:convertDateTime pattern="dd/MM/yyyy" />
101                             </ice:inputText>
102                             <ice:commandButton value="Gravar" action="#{controlador.atualizar}" />
103                             <ice:commandButton value="Cancelar" action="#{controlador.cancelar}" />
104                         </ice:panelGrid>
105                     </ice:panelTab>
106                 </ice:panelTabSet>
107             </div>
108         </ice:form>
109     </body>
110 </html>
 
A linha 64 mostra a utilização do rowSelector, e as linhas 92-105 mostram o formulário de edição. Na linha 16, há a propriedade selectedIndex do panelTabSet que o backing bean utiliza para mudar a aba selecionada, por meio do atributo abaSelecionada do controlador. A seguir, duas imagens que mostram o comportamento do panelTabSet e o formulário de edição recebendo os dados.


Resta somente a perfumaria - mensagens de interação com o usuário, opções para excluir, inserir, filtrar, etc. , que poderão ser assunto de outro post.