Conteúdo


JSF 2 fu

Boas Práticas para Componentes Compostos

Implementar componentes customizados extensíveis

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: JSF 2 fu

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:JSF 2 fu

Fique ligado em conteúdos adicionais dessa série.

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


Temas relacionados

  • O Web site JSF: Localize mais recursos sobre desenvolvimento com o JSF.
  • JSF: Download JSF 2.0.
  • 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.

Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

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