Atualmente produtividade é algo que se fala muito e estará sempre em alta. Cada vez mais as ferramentas tendem a facilitar a vida do desenvolvedor, deixando que o mesmo preocupe-se com que é realmente importante: as regras de negócio.
Há tempos atrás para fazer uma tabulação de dados com paginação e pesquisa era praticamente um “sub-projeto” devido ao nível de complexidade e trabalho exigido para tal tarefa. Foram bons tempos e aprendemos muito, mas hoje, felizmente, temos diversos frameworks que nos auxiliam na construção de requisitos não funcionais como, por exemplo, o Bootstrap, Primefaces, JSF, IceFaces, RichFaces, Zend Framework e etc.
Neste artigo vamos demonstrar funções muito interessantes do JSF em conjunto com o Primefaces: a criação de componentes customizados através do Composite.
Composite com Primefaces
O Primefaces é um framework que possui um aglomerado de componentes que adicionam maior funcionalidade aos componentes padrões do JSF. Por exemplo, no JSF temos o DataTable e no Primefaces também, porém o dataTable possui um maior número de funcionalidades que proporcionam facilidade e agilidade na construção de uma tabulação completa com pesquisa, paginação, ordenação, seleção e etc.
Este foi desenvolvido pela empresa PrimeTek e teve seu início na versão 0.8.1 em 23/02/2009, e hoje já está na versão 5.1 e possui muitos componentes que, com certeza, trazem uma grande produtividade para quem os usa.
Para adicionar o Primefaces com o tema do Bootstrap ao seu projeto, caso esteja usando Maven, basta colocar as seguintes linhas da Listagem 1 no seu pom.xml.
<dependency>
<groupId>org.primefaces.extensions</groupId>
<artifactId>primefaces-extensions</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.1</version>
</dependency>
<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>bootstrap</artifactId>
<version>1.0.9</version>
</dependency>
<!-- dentro de <repositories> -->
<repository>
<id>prime-repo</id>
<name>PrimeFaces Maven Repository</name>
<url>http://repository.primefaces.org</url>
<layout>default</layout>
</repository>
É necessário adicionar o repositório do Primefaces ao pom.xml para que seja possível o Maven fazer o download dos jars necessários.
O Primefaces-extensions é uma biblioteca com mais componentes que o Primefaces padrão não possui, dando um maior nível de complexidade (funcionalmente falando) ao Primefaces. Estamos usando a última versão do Primefaces e optamos por colocar o tema 'bootstrap', mas você pode alterá-lo ou até desabilitá-lo usando a palavra-chave 'none', que esta fará com que o Primefaces renderize seus componentes sem nenhum estilo.
Vejamos agora o objetivo principal do nosso artigo, mas para isso vamos entender um pouco sobre o dataTable do Primefaces.
Muita das vezes você se deparará com o preenchimento de atributos em componentes de forma padrão, como mostra a Listagem 2.
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
</h:head>
<h:body>
<p:dataTable rowKey="#{cliente.id}" var="cliente"
value="#{clienteMB.beans}" paginator="true"
emptyMessage="Não foi encontrado nenhum registro" rows="10"
id="dataTableClientes" selection="#{clienteMB.bean}"
selectionMode="single" sortBy="#{cliente.pessoa.nome}">
<p:ajax event="rowSelect"
update="@form" />
<p:ajax event="rowUnselect"
update="@form" />
<p:column headerText="COD" sortBy="#{cliente.id}" width="10">
<h:outputText
value="#{cliente.id}" />
</p:column>
<p:column headerText="Nome" sortBy="#{cliente.pessoa.nome}">
<h:outputText
value="#{cliente.nome}" />
</p:column>
<p:column headerText="CPF/CNPJ" width="50"
sortBy="#{cliente.pessoaFisica != null ? cliente.pessoaFisica.cpf :
cliente.pessoaJuridica.cnpj}">
<h:outputText
value="#{cliente.pessoaFisica != null ? cliente.pessoaFisica.cpf :
cliente.pessoaJuridica.cnpj}" />
</p:column>
<p:column headerText="Situação" width="100"
sortBy="#{cliente.pessoa.situacao.descricao}">
<h:outputText value="#{cliente.pessoa.situacao.descricao}" />
</p:column>
</p:dataTable>
</h:body>
</html>
O DataTable possui alguns atributos que você perceberá que são preenchidos quase sempre da mesma forma, vejamos:
- Em um projeto onde será padronizada a paginação da tabela: quase sempre o atributo 'paginator' será true;
- No mesmo projeto a mensagem padrão que será mostrada quando não houver registro será “Não foi encontrado nenhum registro".
Perceba que no nosso exemplo estes dois atributos terão sempre o mesmo valor para todas as tabelas do nosso projeto. Pode parecer simples para apenas esses dois atributos, mas se forem quatro ou sete? Como podemos evitar o risco de preencher um dataTable fora do padrão? Como garantir a reusabilidade de componentes? Surge então o componente composite do JSF.
O composite nos dá o “poder” de criar um componente com base em outros componentes e isso é muito útil em diversos casos e não apenas quando desejamos parar de repetir atributos. Criando o componente AB com o composite, esse pode conter diversos outros dentro. Imagine a seguinte situação: podemos criar um componente chamado crudComponent que contém em seu contexto um dataTable, um botão de cadastro, um botão de alteração e um botão de remoção. Ao usar nosso crudComponent na página XHTML ele já saberá criar tudo isso sem precisar fazer mais nada, e caso desejássemos mudar o nome do botão de cadastro de “Novo” para “Cadastrar”, todas as páginas que usem o crudComponent também serão atualizadas.
O primeiro passo para criar um composite é saber que o mesmo é definido em uma página XHTML, com a seguinte estrutura presente na Listagem 3.
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface></composite:interface>
<composite:implementation></composite:implementation>
</html>
A única diferença que podemos destacar de uma página XHTML comum com JSF e Primefaces é a adição da linha “xmlns:composite”. Assim, podemos chamar a tag “” dentro do nosso XHTML, igualmente fazemos com a tag “p” do Primefaces e todas as outras tags definidas no header do nosso HTML.
Assim como um método que possui uma assinatura, argumentos e corpo, o composite também possui a mesma estrutura:
- composite:interface: Usado para definir os atributos/argumentos que poderão ser utilizados na sua implementação. Por exemplo, digamos que queremos usar um atributo chamado 'idade' com valor igual a 10, e este atributo idade poderá ser usado na implementação do composite, assim como usamos um argumento no corpo de um método.
- composite:implementation: Se o interface serve para definir os atributos, o implementation serve para criar a lógica necessária para trabalhar estes atributos.
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="idDataTable" default="dataTableListagem"
shortDescription="ID do DataTable. Usado para
referencia em outras partes do código" />
<composite:attribute name="managedBeanName"
type="br.com.meuprojeto.mb.BasicCrudMBImpl" required="true"
shortDescription="Nome do managedBean do componente" />
</composite:interface>
<composite:implementation>
<p:dataTable rowKey="#{bean.id}" var="bean"
value="#{cc.attrs.managedBeanName.beans}" paginator="true"
emptyMessage="Não foi encontrado nenhum registro" rows="10"
id="#{cc.attrs.idDataTable}"
selection="#{cc.attrs.managedBeanName.bean}" selectionMode="single">
<p:ajax event="rowSelect" update="@form" />
<p:ajax event="rowUnselect" update="@form" />
<composite:insertChildren />
</p:dataTable>
</composite:implementation>
</html>
O objetivo do nosso composite da Listagem 4 é criar uma dataTable customizado, com tudo que precisamos, sem precisar ficar reescrevendo código toda vez. Vamos por partes:
- O composite:attribute serve para passar o atributo com seus devidos valores ao composite desejado. Em nosso caso temos dois atributos: idDataTable e managedBeanName. O primeiro atributo servirá para colocar o id no nosso datatable, assim o identificamos como único na página. Além disso, este atributo possui o 'default', que representa qual será o valor padrão caso este não seja definido e o shortDescription ajudará a identificar qual o objetivo deste atributo, funcionando como um javadoc.No segundo atributo já temos o required='true' e isto significa que ele é obrigatório, por isso não precisamos do 'default', pois o 'type' identifica qual será o tipo do atributo, ou seja, neste caso obrigatoriamente ele deve ser do tipo 'br.com.meuprojeto.mb.BasicCrudMBImpl'.Já dá para ter uma ideia da quantidade de informações que podemos passar para o composite, das mais variadas formas para serem utilizadas na implementação.
- No composite:implementation fazemos uso dos atributos definidos no composite:interface. O que temos aqui é o uso do componente dataTable do Primefaces na sua forma normal, porém com alguns valores definidos através dos atributos. Por exemplo: no campo 'id' devemos definir qual será o identificador do nosso componente na página. Para isso fazemos uso do atributo 'idDataTable' da seguinte forma:
jcc.attrs.idDataTable
Lembre-se que nosso managedBeanName deve ser do tipo BasicCrudMBImpl e a classe tem um método chamado getBeans(). Só assim podemos fazer uso do 'cc.attrs.managedBeanName.beans', que automaticamente será chamado o getBeans() da classe.
Como vamos permitir que seja colocado um número indeterminado de “p:column” dentro do nosso dataTable customizado? A resposta é simples, pois utilizando o composite:insertChildren, pois, logo após as definições do p:ajax nós colocamos o insertChildren, permitindo que seja colocado não só o p:column, mas qualquer outro conteúdo necessário. Isto abre um leque enorme de possibilidade.
Nosso componente está criado, porém precisamos aprender a utilizá-lo em nossa página. O primeiro passo é salvar a página XHTML onde definimos o nosso componente, que terá o nome do componente. Por exemplo: appDataTable.xhml = nome do componente 'appDataTable'.
O JSF faz referência a um diretório escolhido por você, onde todos os composites estão. Podemos chamar este diretório de 'application' e obrigatoriamente devemos colocá-lo dentro de webapp/resources. Então, se for criar 10 componentes, estes devem estar dentro de 'webapp/resources/application'.
Após a criação do diretório e da página XHTML nós definimos uma nova URL no cabeçalho do nosso XHTML onde queremos usar o composite, como mostra a Listagem 5.
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:app="http://java.sun.com/jsf/composite/application">
Veja na última linha a definição do 'xmlns:app="http://java.sun.com/jsf/composite/application"', onde application é o nome do diretório e 'app' é a tag de referência que usaremos dentro da página.
<app:appDataTable managedBeanName="#{clienteMB}">
<p:column headerText="COD" sortBy="#{cliente.id}" width="10">
<h:outputText
value="#{cliente.id}" />
</p:column>
<p:column headerText="Nome" sortBy="#{cliente.pessoa.nome}">
<h:outputText
value="#{cliente.pessoa.nome}" />
</p:column>
<p:column headerText="CPF/CNPJ" width="50"
sortBy="#{cliente.pessoaFisica != null ? cliente.pessoaFisica.cpf :
cliente.pessoaJuridica.cnpj}">
<h:outputText
value="#{cliente.pessoaFisica != null ? cliente.pessoaFisica.cpf :
cliente.pessoaJuridica.cnpj}" />
</p:column>
<p:column headerText="Situação" width="100"
sortBy="#{cliente.pessoa.situacao.descricao}">
<h:outputText value="#{cliente.pessoa.situacao.descricao}" />
</p:column>
</app:appDataTable>
Para utilizar nosso componente, basta chamar “” e o único atributo obrigatório é o managedBeanName, como já havíamos definido.
Se você comparar a Listagem 2 com a Listagem 6, verá a diferença de detalhes em ambas, pois a Listagem 6 abstrai todos os detalhes que já deveriam ser padrões para nossa aplicação, tornando tudo mais simples e produtivo.
Vejamos outro exemplo de criação de um composite para mostrar um dialog do Primefaces, de forma padronizada para todo projeto. Observe a Listagem 7.
<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="titulo"
shortDescription="Título do dialog" />
<composite:attribute name="rodape"
shortDescription="Rodapé do dialog" />
<composite:attribute name="id"
shortDescription="ID do componente" />
</composite:interface>
<composite:implementation>
<p:dialog id="#{cc.attrs.id}" header="#{cc.attrs.titulo}" maximizable="true"
footer="#{cc.attrs.rodape}" widgetVar="var#{cc.attrs.id}" modal="true"
showEffect="fade" hideEffect="fade" closeOnEscape="true">
<composite:insertChildren />
</p:dialog>
</composite:implementation>
</html>
Seguindo a mesma linha de raciocínio das listagens anteriores, o nosso composite da Listagem 7 possui os atributos: titulo, rodape e id. Nenhum deles é obrigatório.
Na implementação nós chamamos o componente dialog (p:dialog) com os parâmetros padronizados e usando o composite:insertChildren para permitir a inserção de qualquer tipo de conteúdo dentro do dialog.
Vejamos como se da seu uso na Listagem 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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions"
xmlns:application="http://java.sun.com/jsf/composite/application">
<h:head>
</h:head>
<h:body>
<ui:composition>
<application:simpleDialog id="dialogTeste"
titulo="Teste de Dialog">
<div>Meu conteudo aqui</div>
</application:simpleDialog>
</ui:composition>
</h:body>
</html>
É importante salientar que o uso do composite não se aplica apenas ao Primefaces, mas para qualquer outra necessidade, usamos o Primefaces apenas para demonstrar o caso real de uso.
Neste artigo utilizamos o composite para tornar o conceito de reusabilidade e aumento de produtividade possível em projetos com JSF. Para projetos complexos e com diversos componentes espalhados por diversos módulos o uso do composite pode ser uma grande ferramenta visto que as alteração são todas centralizadas. Imagine, por exemplo, que em seu projeto há aproximadamente 50 dataTables espalhados dentre diversos formulários e foi requisitado a você remover a “seleção” de todos os dataTables temporariamente por alguns dias e depois habilitar novamente. Normalmente você teria que ir página em página e remover o selectionMode do Primefaces datatabel e isso com certeza seria um trabalho árduo, isso porque estamos falando apenas de 50 dataTables mas há projetos com muito mais que isso. Com um simples composite você consegue centralizar os dataTables e mudar o selectionMode direto no composite, isso é reusabilidade e produtividade ao mesmo tempo.
Veja que falamos muito de dataTAble por se tratar de algo muito comum em quase todos os projetos, dos mais simples aos mais complexos, mas mostramos também o uso do composite com um dialog para demonstrar que sua utilidade vai além.