JSF 2 fu: Boas Práticas para Componentes Compostos

Implementar componentes customizados extensíveis

Nesta parte do JSF 2 fu, você aprenderá cinco boas práticas para implementar componentes compostos de Java™Server Faces. Seguindo estas diretrizes, você tornará mais fácil a atividade de estender seus componentes customizados realizada pelos autores de páginas.

David Geary, President, Clarity Training, Inc.

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



27/Jan/2011

Sobre esta série

A série JSF 2 fu , uma sequência da introdução de David Geary introdução com três artigos de David Geary com o mesmo nome, ajudará a desenvolver e aperfeiçoar as suas qualificações na estrutura JSF 2 como um mestre de kung fu. A série atual abrange em detalhes a estrutura e os ecossistemas circundantes. E também espia fora da caixa mostrando como algumas tecnologias Java EE, como os Contextos e a Injeção de Dependência, se integram com o JSF.

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:

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


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

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

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

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.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=620503
ArticleTitle=JSF 2 fu: Boas Práticas para Componentes Compostos
publish-date=01272011