JSF é uma estrutura baseada em componente, o que significa que fornece a infraestrutura necessária para implementar seus próprios componentes. JSF 2 fornece uma maneira simples para implementar componentes customizados com compostos.
Mostrei a implementação de diversos componentes compostos em artigos anteriores (consulte "Uso de modelos e componentes compostos," "Componentes Ajax" e "Componentes compostos Ajax de última hora"). Neste artigo, vou abordar esse tópico — e a série JSF 2 fu :— apresentando cinco boas práticas para implementar componentes compostos com JSF 2:
-
Agrupe seus componentes em um
DIV. - Incorpore JavaScript e Ajax.
- Use encerramentos de JavaScript para suportar diversos componentes em uma página.
- Deixe autores de páginas customizarem seus componentes.
- Internacionalize seus componentes.
Para ilustrar essas boas práticas, vou discutir como elas se aplicam à implementação de um componente composto simples.
O componente composto de entrada editável
O componente de exemplo deste artigo é um componente composto de entrada editável. O aplicativo mostrado na Figura 1 usa duas entradas editáveis, uma para nome e uma para sobrenome:
Figura 1. Componentes de Texto Editáveis
De cima para baixo, as três capturas de tela na Figura 1 mostram a sequência de edição para o nome:
- A captura de tela superior mostra a aparência inicial do aplicativo, com botões edit... à direita dos rótulos First name: e Last name: .
- A captura de tela intermediária mostra como o aplicativo fica
imediatamente após o usuário clicar no botão
edit... ao lado de First
name: e inserir
Rogerem uma área de entrada de texto. Um botão done aparece à direita da área de entrada de texto. - A captura de tela inferior mostra como fica o aplicativo após o usuário ter clicado no botão done . Agora First name: Roger é exibido, com um botão edit... à sua direita.
Em seguida, vou discutir como usar o componente de entrada editável e então mostrar como ele é implementado. Depois disso, vou discutir cada uma das cinco boas práticas em termos da implementação do componente.
O componente de entrada editável é usado da mesma forma que qualquer componente composto JSF: declare o namespace apropriado e use a tag que JSF gera para o componente composto. Lista 1 ilustra essas duas etapas com a marcação para a página mostrada na Figura 1:
Lista 1. Usando
<util:inputEditable>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:util="http://java.sun.com/jsf/composite/util">
<h:head> <title>Implementing custom
components</title> </h:head>
<h:body> <h:form> <h:panelGrid
columns="2"> First name: <util:inputEditable
id="firstName" value="#{user.firstName}"/>
Last name: <util:inputEditable id="lastName"
value="#{user.lastName}"/>
</h:panelGrid> </h:form>
</h:body> </html> |
Por questão de integridade, a Lista 2 mostra a implementação do bean user referido na Lista 1:
Lista 2. O
User bean
package com.corejsf; import java.io.Serializable; import
javax.inject.Named; import javax.enterprise.context.SessionScoped;
@Named("user") @SessionScoped public class UserBean implements
Serializable { private String firstName; private String lastName;
public String getFirstName() { return firstName; } public void
setFirstName(String newValue) { firstName = newValue; } public
String getLastName() { return lastName; } public void
setLastName(String newValue) { lastName = newValue; } } |
Agora que você viu como usar o componente de entrada editável, mostrarei como ele é implementado.
O componente de entrada editável é implementado nos arquivos inputEditable.js, inputEditable.properties e inputEditable.xhtml no diretório resources/util, conforme mostrado na hierarquia do sistema de arquivos na Figura 2:
Figura 2. Os arquivos do componente
Lista 3 mostra inputEditable.xhtml:
Lista 3. A marcação do componente
inputEditable
(inputEditable.xhtml)
<!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:composite="http://java.sun.com/jsf/composite">
<composite:interface> <composite:attribute
name="text"/> <composite:editableValueHolder
name="text" targets="editableText" />
<composite:actionSource name="editButton"
targets="editButton" /> <composite:actionSource
name="doneButton" targets="doneButton" />
<composite:clientBehavior name="edit" event="action"
targets="editButton"/> <composite:clientBehavior
name="done" event="action" targets="doneButton"/>
<composite:facet name="textMessage"/>
</composite:interface>
<composite:implementation> <h:outputScript
library="javascript" name="prototype.js" target="head"/>
<h:outputScript library="javascript" name="scriptaculous.js"
target="head"/> <h:outputScript library="javascript"
name="effects.js" target="head"/> <h:outputScript
library="util" name="inputEditable.js" target="head"/>
<div id="#{cc.clientId}"> <h:outputText
id="text" value="#{cc.attrs.text}"/>
<h:commandButton id="editButton"
type="button" value="#{cc.resourceBundleMap.editButtonText}"
onclick="this.startEditing()"/>
<h:inputText id="editableText"
value="#{cc.attrs.text}" style="display: none"/>
<h:commandButton id="doneButton"
value="#{cc.resourceBundleMap.doneButtonText}" style="display:
none"> <f:ajax render="text textMessage"
execute="editableText" onevent="ajaxExecuting"/>
</h:commandButton> <h:panelGroup
id="textMessage"> <composite:renderFacet
name="textMessage"/> </h:panelGroup>
</div> <script>
com.clarity.init('#{cc.clientId}'); </script>
</composite:implementation>
</html> |
A marcação cria quatro componentes, sendo que somente dois —
o texto e o botão edit — estão inicialmente visíveis. Quando
o usuário clica no botão edit, o aplicativo chama a função de
JavaScript com.clarity.startEditing() ,
que é implementada na Lista 4:
Lista 4. O JavaScript do componente
inputEditable
(inputEditable.js)
package com.corejsf; var com = {}; if
(!com.clarity) { com.clarity = { init: function
(ccid) { var mydiv = document.getElementById(ccid); mydiv.editButton
= $(mydiv.id + ':editButton'); mydiv.text = $(mydiv.id + ':text');
mydiv.editableText = $(mydiv.id + ':editableText'); mydiv.doneButton
= $(mydiv.id + ':doneButton'); mydiv.doneButton.offsetLeft =
mydiv.editButton.offsetLeft;
mydiv.editButton.startEditing = function() {
mydiv.text.fade( { duration: 0.25 } ); mydiv.editButton.fade( {
duration: 0.25 } ); window.setTimeout( function() {
mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } ); window.setTimeout(
function() { mydiv.editableText.focus(); }, 300); }, 300); }; },
toggleDisplay: function(element) {
element.style.display = element.style.display == "none" ? "" :
"none"; }, ajaxExecuting: function(data) { var
mydiv = $(data.source.parentNode); if (data.status == 'complete') {
toggleDisplay(mydiv.editableText); toggleDisplay(mydiv.doneButton);
toggleDisplay(mydiv.text); toggleDisplay(mydiv.editButton); } } } } |
A função startEditing() usa os métodos
fade() e appear() da estrutura Scriptaculous (consulte Recursos). Ela também usa dois cronômetros
para assegurar que as ações de esmaecer e aparecer ocorram na ordem
correta. Observe que a função startEditing() , no fim das contas, também foca a entrada
de texto.
Lista 5 mostra inputEditable.properties:
Lista 5. O arquivo de propriedades do componente
inputEditable
(inputEditable.properties)
editButtonText=edit...
doneButtonText=done |
Em seguida, discutirei a implementação de entrada editável a partir da perspectiva das cinco boas práticas.
Quando JSF cria um componente composto, cria o que a estrutura se refere como um contêiner de nomenclatura, que contém todos os componentes dentro do composto. O contêiner de nomenclatura, no entanto, não gera marcação. Em vez disso, JSF gera marcação para cada um dos componentes dentro do composto. Como resultado, a marcação de página não pode fazer referência ao componente como um todo por seu ID de componente, pois, por padrão, não há nenhum componente com esse ID.
É possível dar aos autores de páginas a capacidade de fazer
referência ao componente composto agrupando o componente em um DIV ao implementá-lo. Suponhamos, por
exemplo, que você queira que a marcação de página faça referência a
um componente de entrada editável como parte de uma chamada Ajax. Na
marcação a seguir, incluo um botão Ajax que processa a entrada do
nome. Clicar no botão faz uma chamada Ajax ao servidor, onde a
entrada do nome é processada. Quando a chamada do Ajax retorna, JSF
renderiza a entrada:
<h:form>
<h:panelGrid columns="2"> First name:
<util:inputEditable id="firstName"
value="#{user.firstName}"/> Last name:
<util:inputEditable id="lastName"
value="#{user.lastName}"/> <h:commandButton
value="Update first name"> <f:ajax
execute="firstName" render="firstName">
</h:commandButton>
</h:panelGrid> </h:form> |
o botão Ajax na marcação anterior funciona, porque na Lista 3 eu agrupei o
componente em um DIV:
<div
id="#{cc.clientId}"> ... </div> |
O identificador usado para o DIV é o
identificador de cliente do composto em si. Portanto, o autor da
página pode fazer referência ao componente composto como um todo,
como o botão Ajax que acabei de discutir.
Uma coisa que não é possível ver nas capturas de tela estáticas
neste artigo é a animação de esmaecer que o componente de entrada
editável executa quando o usuário clica no botão
edit... . Essa ação de esmaecer é por fim feita
pela estrutura Scriptaculous. Na Lista 3, eu uso a tag <h:outputScript> para emitir saída do
JavaScript necessária do Scriptaculous e, na Lista 4 , eu uso os métodos fade() e appear()
da estrutura para obter a animação desejada:
mydiv.editButton.startEditing =
function() { mydiv.text.fade( { duration: 0.25 } );
mydiv.editButton.fade( { duration: 0.25 } ); window.setTimeout(
function() { mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } ); window.setTimeout(
function() { mydiv.editableText.focus(); }, 300); }, 300); };
|
Os cronômetros aninhados no JavaScript anterior certificam que tudo
na animação ocorra na hora certa. Por exemplo, eu adio colocar o
foco no texto de entrada até ter certeza que a entrada tenha
aparecido na tela; caso contrário, se eu chamar focus() antes de a entrada aparecer, a chamada não será
atendida.
É simples usar estruturas de JavaScript de terceiros, como Scriptaculous ou JQuery, com JSF. O JavaScript apropriado tem a saída emitida na página e, em seguida, a estrutura é usada em seu código JavaScript.
O componente de entrada editável também usa Ajax para fazer uma chamada ao servidor quando o usuário clica no botão done :
<h:commandButton
id="doneButton" value="#{cc.resourceBundleMap.doneButtonText}"
style="display: none"> <f:ajax render="text
textMessage" execute="editableText"
onevent="ajaxExecuting"/> </h:commandButton>
|
Na marcação anterior, uso a tag <f:ajax> de JSF para fazer uma chamada Ajax
quando o usuário clica no botão done . Essa chamada
Ajax executa a entrada de texto no servidor e atualiza o texto e a
mensagem de texto quando a chamada Ajax retorna.
Usar Encerramentos de JavaScript
Ao implementar componentes compostos, você deve levar em consideração diversos componentes em uma página. Quando todas as instâncias de um componente compartilham o mesmo JavaScript, deve tomar cuidado para manipular somente o componente com o qual o usuário está interagindo atualmente.
É possível suportar diversos componentes em uma página de várias maneiras. Uma maneira, discutida pelo engenheiro de Oracle Jim Driscoll em uma entrada de blog sobre um componente de entrada editável semelhante, é manter um namespace de IDs de componentes (consulte Recursos). Outra maneira é usar encerramentos JavaScript:
com.clarity = { init: function
(ccid) { var mydiv = document.getElementById(ccid); mydiv.editButton
= $(mydiv.id + ':editButton'); mydiv.text = $(mydiv.id + ':text');
mydiv.editableText = $(mydiv.id + ':editableText'); mydiv.doneButton
= $(mydiv.id + ':doneButton'); mydiv.doneButton.offsetLeft =
mydiv.editButton.offsetLeft; mydiv.editButton.startEditing =
function() { mydiv.text.fade( { duration: 0.25 } );
mydiv.editButton.fade( { duration: 0.25 } ); window.setTimeout(
function() { mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } ); window.setTimeout(
function() { mydiv.editableText.focus(); }, 300); }, 300); }; }, |
Esse é o JavaScript para o componente de entrada editável que é
mostrado integralmente na Lista 4. A função init() é chamada para cada componente de entrada
editável, como pode-se ver na parte inferior da Lista 3. Dado o
identificador de cliente do agrupamento do componente DIV, obtenho uma referência para esse DIV. E como JavaScript é uma linguagem
dinâmica que permite incluir propriedades e métodos em objetos no
tempo de execução, incluo referências em todos os elementos que
preciso posteriormente em minhas funções de retorno de chamada ao
DIV propriamente dito.
Também incluo um método startEditing()
no botão edit do componente. Quando o usuário clica em
edit..., chamo esse método:
<h:commandButton
id="editButton" type="button"
value="#{cc.resourceBundleMap.editButtonText}"
onclick="this.startEditing()"/> |
Quando a função startEditing() é
chamada, a variável mydiv retém o valor
original que tinha quando o método init(), foi chamado. Esse é o ponto interessante dos
encerramentos JavaScript (e, até certo ponto, classes Java
internas). Não importa quanto tempo decorre entre init(), e startEditing() e não importa se init(), foi chamado diversas vezes no meio —
quando startEditing() é chamado, seu
valor mydiv é sua própria cópia que tinha
quando o método init(), foi chamado para
esse componente específico.
Como os encerramentos JavaScript retêm valores das variáveis de uma
função circundante, você pode ter certeza de que cada função startEditing() acessa o DIV apropriado para seu componente.
Deixar que os autores de páginas customizem
Geralmente, com apenas uma ou duas linhas de XML em sua definição de componente, é possível deixar os autores de páginas customizarem seus componentes. As três principais maneiras para customizar componentes compostos são:
- Incluir validadores, conversores e listeners
- Aspectos
- Ajax
Validadores, Conversores e Listeners
É possível deixar autores de páginas anexarem validadores, conversores e listeners a componentes dentro de seus componentes compostos, desde que você exponha esses componentes internos. Por exemplo, Figura 3 mostra validação incluída em um componente de entrada editável:
Figura 3. Validando o campo first-name
Figura 3 exibe uma mensagem de erro afirmando que o campo requer pelo menos 10 caracteres. O autor da página incluiu um validador no texto do componente de entrada editável do nome, como este:
<util:inputEditable
id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
</util:inputEditable> |
Observe o atributo for da tag <f:validateLength> . Isso
informa ao JSF que o validador é para o texto dentro do
componente de entrada editável. JSF sabe sobre esse texto, pois eu o
expus na implementação do componente:
<composite:interface>
... <composite:editableValueHolder name="text"
targets="editableText" /> ...
</composite:interface>
<composite:implementation> ... <h:inputText
id="editableText" value="#{cc.attrs.text}" style="display:
none"/> ... </composite:implementation> |
Na Figura 3, a mensagem de erro é exibida na parte inferior da página. Por isso, estou usando o estágio do projeto de Desenvolvimento e JSF inclui erros de validação automaticamente na parte inferior. Não é possível dizer a qual entrada editável o erro está associado, então seria melhor colocar a mensagem de erro ao lado do componente ofensivo, conforme mostrado na Figura 4:
Figura 4. Usando Aspectos
O autor da página pode fazer isso, incluindo um aspecto no componente:
<util:inputEditable
id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
<f:facet name="textMessage"> <h:message
for="editableText" style="color: red"/>
</f:facet> </util:inputEditable>
|
Esse aspecto é suportado pelo componente:
<composite:interface>
<composite:facet name="textMessage"/>
</composite:interface> <div
id="#{cc.clientId}"> <h:panelGroup
id="textMessage"> <composite:renderFacet
name="textMessage"/> </h:panelGroup> ...
</div> |
Conforme discutido no artigo "Componentes compostos Ajax de última hora", é possível usar
a tag <composite:clientBehavior> para permitir
que autores de páginas incluam recursos Ajax em seus componentes
compostos. Figura 5 mostra uma
caixa de diálogo que monitora chamadas Ajax que ocorrem quando o
usuário clica no botão edit... :
Figura 5. Monitorando solicitações Ajax
O autor da página simplesmente inclui uma tag <f:ajax> no componente e especifica uma
função Ajax cujo atributo onevent é uma
função JavaScript (a função que exibe a caixa de diálogo) a ser
chamada enquanto a chamada Ajax está progredindo:
<util:inputEditable
id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
<f:facet name="textMessage"> <h:message
for="editableText" style="color: red"/>
</f:facet> <f:ajax event="edit"
onevent="monitorAjax"/>
</util:inputEditable> |
O autor da página pode incluir uma tag <f:ajax> no componente, pois eu expus esse
comportamento do cliente na implementação do componente:
<composite:interface>
<composite:clientBehavior name="edit" event="action"
targets="editButton"/> </composite:interface> |
O componente de entrada editável exibe o mesmo texto (edit... e done) em seus dois botões. Para que o componente seja usado com diversos códigos de idioma, o texto deve ser internacionalizado e localizado.
Para localizar o texto de um componente, simplesmente inclua um
arquivo de propriedades no mesmo diretório que o componente e, em
seguida, teclas de acesso no arquivo de propriedades por meio desta
expressão: #{cc.resourceBundleMap.KEY}, em que
KEY
é a tecla no arquivo de propriedades. Como é possível ver na
Lista 3 e na
Lista 5, é
assim que eu localizo o texto para os botões do componente de
entrada editável.
JSF 1 dificultou a implementação de componentes, portanto a maioria dos desenvolvedores de JSF optou por não. Com o JSF 2, componentes customizados não são mais o domínio exclusivo dos desenvolvedores de estrutura de componente customizado. Neste artigo, mostrei algumas das boas práticas para implementar componentes compostos. Com um pouco de trabalho, é possível tornar seus componentes facilmente extensíveis para autores de páginas.
Aprender
- O Web site JSF:
Localize mais recursos sobre desenvolvimento com o JSF.
-
Scriptaculous: É possível
integrar essa e outras estruturas JavaScript de terceiros com JSF.
- "Another JSF 2.0 Ajax Component: Editable Text" (Jim
Driscoll, java.net, novembro de 2008): Driscoll usa uma abordagem
alternativa para implementar um componente composto de entrada
editável.
-
Zona tecnologia
Java do developerWorks: Encontre centenas de artigos sobre
cada aspecto da programação Java.
Obter produtos e tecnologias
- JSF: Download JSF 2.0.
Discutir
- Participe da comunidade do My
developerWorks . Conecte-se com outros usuários do
developerWorks, ao mesmo tempo em que explora os blogs, fóruns,
grupos e wikis direcionados a desenvolvedores.

O autor, palestrante e consultor David Geary é o presidente da Clarity Training, Inc. onde ensina desenvolvedores a implementarem aplicativos da Web usando JSF e Google Web Toolkit (GWT). Ele fez parte dos Grupos de Especialistas do JSTL 1.0 e do JSF 1.0/2.0, foi coautor do Exame de Certificação de Desenvolvedor da Web da Sun e contribuiu para projetos de software livre, incluindo o Apache Struts e o Apache Shale. Graphic Java Swing de David foi um dos livros de Java mais vendidos de todos os tempos e Core JSF (coescrito com Cay Horstman) é o livro de JSF mais vendido. David fala frequentemente em conferências e para grupos de usuários. É frequentador assíduo do tour de NFJS desde 2003, deu cursos na Java University e foi duas vezes votado como JavaOne rock star.