Componentes Facelets
Um dos recursos mais interessantes do JSF é a capacidade de criar componentes para o mesmo. Esta criação, as vezes, é um pouco complexa e pode não valer a pena quando o resultado é algo simples. Dessa forma, utilizando-se do Facelets, é possível criá-los utilizando-se dos componentes que já existem, como os inputText’s ou até mesmo um dataTable.
Acredito que o maior benefício é evitar a reescrita de código ou a famosa duplicação de código, que geralmente nos remete à código do backend, mas dessa vez na view.
Por exemplo, tenho que desenvolver um sistema que possui 5 cadastros do tipo CRUD para alimentar a base com informações. Pois bem, quais os componentes necessários para um CRUD? Um dataTable, para apresentar a lista dos registros do banco e um formulário para cadastrar e editar os dados. Partindo desse princípio, o que normalmente nós faríamos é criar um primeiro CRUD completo para servir de “base” para os outros e fazer o famoso copy/paste, trocar os valores dos formulários e ta pronto. Uma maravilha! Ou não…
Duas semanas depois, nós estámos já desenvolvendo alguns recursos necessários para a aplicação e uma mudança de layout foi realizada: agora as mensagens de validação de campos devem ser apresentadas abaixo de cada campo. Ferrou! Retrabalho editando todos os formulários que nós tínhamos.
É aqui que entra o nosso amigo Facelets. Com ele nós podemos criar componentes para diversos propósitos, como, por exemplo, formulários. Sei que já existe o h:inputText e a minha ideia não é escrever um novo inputText, mas sim, incrementá-lo. A maioria dos formulários possui a mesma estrutura: “Label:” [Campo]. Então, normalmente nós faríamos o seguinte:
<h:outputLabel for="nome" id="nomeLabel" value="Nome: " />
<h:inputText id="nome" value="#{mBean.user.name}" required="true" />
<h:message for="nome" />
Isso iria se repetir para cada campo do tipo texto do todos os formulários do sistema. No exemplo, eu já adicionei o h:message para o input, mas na nossa história isso não existia no CRUD “base” e tivemos que alterar tudo depois. Agora vamos criar um componente que apresente esse nosso formato usando apenas uma tag:
<asn:formInput id="nome" label="Nome: " value="#{userMBean.nome}" required="true" />
O resultado deverá ser o mesmo apresentado antes, porém temos o componente centralizado, nos possibilitando de alterar apenas um arquivo para que todo o sistema se encaixe no requisito.
Mas como a gente faz isso tio? Bueno, mãos à obra:
Criando o componente
A primeira coisa a fazer, é escrever o componente. Monte tudo que você precisar. O meu ficou assim:
<!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">
<ui:component>
<h:outputLabel for="#{id}" id="#{id}Label" value="#{label}" />
<h:inputText id="#{id}" value="#{value}" required="#{required}" />
<h:message for="#{id}" />
</ui:component>
</html>
Os valores id, value e required, serão os parâmetros do nosso componente.
Criando a TagLib
Feito isso, precisamos criar a nossa taglib:
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"https://facelets.dev.java.net/source/browse/*checkout*/facelets/src/etc/facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://asneto.eti.br/java/jsf</namespace>
<tag>
<tag-name>formInput</tag-name>
<source>formInput.xhtml</source>
</tag>
</facelet-taglib>
Observe que o “source” é o caminho para o arquivo que contém o nosso componente. Como eu deixei na mesma pasta da taglib, apenas coloquei o nome do arquivo. Agora vamos registrar a taglib na nossa aplicação. Abra o seu web.xml e coloque:
<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/components/asneto.taglib.xml</param-value>
</context-param>
Observe o caminho para o xml da taglib. Foi nesse caminho que salvei os arquivos. Você pode colocar em qualquer lugar, mas preferencialmente coloque fora das pastas públicas do projeto.
Testando
Bacana, vamos testar agora. Crie um formulário de exemplo utilizando o componente recém criado:
<?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:h="http://java.sun.com/jsf/html"
xmlns:asn="http://asneto.eti.br/java/jsf">
<head>
<title>Teste</title>
</head>
<body>
Teste de componentes
<h:form>
<asn:formInput id="nome" label="Nome: " value="#{userMBean.nome}" required="true" />
</h:form>
</body>
</html>
E o resultado no browser:
Legal, mas esse exemplo é bobinho né? Claro, é apenas pra exemplificar. Tive um caso real, onde os CRUD‘s tinham modais de confirmação, tipo o “alert()” do JavaScript, mas mais moderno. Para não ficar repetindo código em todos os CRUD‘s, criamos um componente que tinha um rich:modalPanel com botões “Sim” e “Não” e recebia uma mensagem como parâmetro (além de muitos outros como a action do botão sim e tal). Isso facilitou bastante, pois apenas mexendo no componente, todo o sistema estava alterado.
Incrementando o exemplo
Criando um TLD para o componente.
Os “TLD” (taglib descriptor) são a documentação do nosso componente. É desse arquivo que as IDE’s lêem para exibir as informações no autocomplete:
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib xmlns="http://java.sun.com/JSP/TagLibraryDescriptor">
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>Components</short-name>
<uri>http://asneto.eti.br/java/jsf</uri>
<display-name>ASNeto TagLibrary</display-name>
<tag>
<name>formInput</name>
<tag-class />
<body-content>empty</body-content>
<description>
Componente para campos de formulários. Exibe uma label, o campo do tipo input e a mensagem de validação.
</description>
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>
Id do componente.
</description>
</attribute>
<attribute>
<name>label</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>
Label do campo.
</description>
</attribute>
<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
<description>
Valor ao qual o input está associado.
</description>
</attribute>
<attribute>
<name>required</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.Boolean</type>
<description>
Informa se o campo é obrigatório ou não.
</description>
</attribute>
</tag>
</taglib>
Criando parâmetros não obrigatórios
Nossos componentes devem poder receber parâmetros não obrigatórios. Digamos que no nosso sistema, todas as labels são com cor vermelha e os inputs tem borda verde. Vamos setar um styleClass direto no componente? Pode ser, vai funcionar, mas estaremos engessando nosso componente. O ideal é possibilitarmos que a classe CSS para a label e para o input sejam passados por parâmetro.
Com as devidas alterações, nosso componente agora ficou assim:
<!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:c="http://java.sun.com/jsp/jstl/core">
<ui:component>
<c:if test="#{styleClass}">
<c:set value="false" var="styleClass" />
</c:if>
<c:if test="#{labelStyleClass}">
<c:set value="false" var="labelStyleClass" />
</c:if>
<h:outputLabel for="#{id}" id="#{id}Label" value="#{label}" styleClass="#{labelStyleClass}" />
<h:inputText id="#{id}" value="#{value}" required="#{required}" styleClass="#{styleClass}" />
<h:message for="#{id}" />
</ui:component>
</html>
Observem que eu agora é possível passar a classe CSS para a label e/ou para o nosso input. Eu usei JSTL pois ela é executada antes do parse do Facelets. Portanto, caso o parâmetro não tenha sido setado, o JSTL vai criar uma variável local (através do c:set) possibilitando o uso da mesma.
Usando máscara com jQuery
Um caso que sempre vejo na lista JavaSF, é referente ao uso de máscaras com jQuery (meio.mask, input.mask). O problema é que o reRender realizado acaba não chamando o JavaScript para aplicar a máscara denovo, fazendo com que os inputs percam a máscara setada. Uma boa maneira de resolver este problema, é usar um componente que, além de ter a máscara, tenha a chamada JavaScript para a aplicação da mesma. Dessa forma, ao dar um reRender no componente, a máscara é aplicada junto. Neste exemplo vou usar o plugin meio.mask que pode ser baixado do site oficial.
Alterando nosso componente para receber máscara:
<!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:rich="http://richfaces.org/rich"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<ui:component>
<c:if test="#{!styleClass}">
<c:set value="false" var="styleClass" />
</c:if>
<c:if test="#{!labelStyleClass}">
<c:set value="false" var="labelStyleClass" />
</c:if>
<c:if test="#{!mask}">
<c:set value="" var="mask" />
<c:set value="false" var="useMask" />
</c:if>
<c:if test="#{mask}">
<c:set value="true" var="useMask" />
</c:if>
<h:outputLabel for="#{id}" id="#{id}Label" value="#{label}" styleClass="#{labelStyleClass}" />
<h:inputText id="#{id}" value="#{value}" required="#{required}" styleClass="#{styleClass}" />
<h:message for="#{id}" />
<rich:jQuery selector="##{id}" query="setMask('#{mask}')" timing="immediate" />
</ui:component>
</html>
Agora temos o novo parâmetro “mask” que não é obrigatório. Para usarmos esse input, temos que carregar o script do meio.mask. Eu fiz isso na tela onde eu uso o input, assim caso não seja usado nenhuma máscara, não há necessidade de carregar o script.
Usando nosso componente alterado:
<?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:h="http://java.sun.com/jsf/html"
xmlns:asn="http://asneto.eti.br/java/jsf"
xmlns:a4j="http://richfaces.org/a4j">
<head>
<script type="text/javascript" src="js/jquery.meio.mask.js" />
<title>Teste</title>
</head>
<body>
Teste de componentes
<h:form id="form" prependId="false">
<h:panelGroup id="bloco">
<asn:formInput id="nome" label="Nome: " value="#{userMBean.nome}" required="true" mask="99:99" />
<br />
<a4j:commandLink value="reRenderizar Bloco">
<a4j:support event="onclick" reRender="bloco" />
</a4j:commandLink>
</h:panelGroup>
</h:form>
</body>
</html>
Adicionei um link para reRenderizar o bloco, mostrando como a máscar permanece, mesmo após o recarregamento.
Finalizando
O Facelets facilita a nossa vida, basta saber utilizá-lo. Lembrando, este recurso serve para evitar a reescrita de código na View, caso você precise criar um componente novo, o melhor é utilizar componentes nativos para JSF, usando código Java. Fiz esse post meio rápido, então qualquer erro encontrado, por favor me notifiquem para que eu corriga o post.
Desenvolvimento, Java, JSFTags: Componente, Facelets, jQuery, JSF, JSTL, meio.mask, RichFaces
Um Comentário para “Componentes Facelets”
Deixe um comentário
Excelente! Simples mas era o que eu precisava!