Back-End

15 fev, 2011

JSF 2 fu: boas práticas para componentes compostos

Publicidade

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. Neste artigo,
vou abordar esse tópico apresentando cinco boas práticas para implementar
componentes compostos com JSF 2:

  1. Agrupe seus componentes em um DIV.
  2. Incorpore JavaScript e Ajax.
  3. Use encerramentos de JavaScript para suportar
    diversos componentes em uma página.
  4. Deixe autores de páginas customizarem seus
    componentes.
  5. 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 Roger em
    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.

Usando o 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. A 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.

A implementação do componente

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

A 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. 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. A 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.

Agrupar Componentes em um DIV

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. 

Incorporar JavaScript e Ajax

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. 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

A 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>

Aspectos

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>

Ajax

É 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>

Internacionalizar

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.

Conclusão

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.

Recursos

Aprender

Obter produtos e tecnologias

  • JSF: Download JSF 2.0.


***

artigo publicado originalmente no developerWorks Brasil, por David Geary

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.