Back-End

28 nov, 2012

JSF 2 fu: Componentes compostos Ajax de última hora

Publicidade

Os autores de páginas usam o componente em um facelet e o componente cuida de todos os detalhes do Ajax. O Ajax integrado possui seu lugar, mas também é conveniente permitir aos autores de páginas a inclusão de Ajax no componente composto depois (talvez bem depois) de o desenvolvedor ter implementado o componente. Este artigo mostra como os seus componentes compostos podem acomodar o Ajax de última hora.

Vale lembrar que a tag <f:ajax> do JSF 2 permite aos autores de páginas a inclusão de Ajax de última hora aos componentes integrados do JSF 2. Por exemplo: com a <f:ajax> é possível facilmente transformar um botão de envio em um botão do Ajax:

<h:commandButton value="Click me">
<f:ajax>
</h:commandButton>

No entanto, a tag <f:ajax> do JSF 2 não funciona – sem uma pequena ajuda – com componentes compostos, pois os compostos são simplesmente contêineres de componentes.

Quando o usuário clique no ícone, o ícone envia o formulário circundante, que aciona um listener de ação do lado do servidor associado ao link do ícone. O uso de ícones é simples:

<util:icon image="...">
  <f:actionListener for="link" type="...">
</util:icon>

Como é possível transformar botões de envio em botões de AjaComx com a tag <f:ajax>, você pode pensar que é possível fazer a mesma coisa para ícones:

<util:icon image="...">
<f:ajax>
  <f:actionListener for="link" type="...">
</util:icon>

O fragmento de código anterior não funciona, pois estou anexando a tag <f:ajax> no componente de ícone, quando o que realmente desejo fazer é anexá-la ao link dentro do ícone.

O necessário neste exemplo é um mecanismo que permita a conexão do comportamento do Ajax ao link dentro do ícone, ou de forma mais geral, que permita a conexão do componente do Ajax aos componentes que residem nos componentes compostos. Esse mecanismo – implementados no Mojarra e Apache MyFaces e que não possuem documentação no JSF 2.0 – é o foco deste artigo.

Observação: O suporte ao MyFaces foi incluído enquanto este artigo estava sendo escrito. Antes de ver como o mecanismo funciona, irei criar um novo componente composto de ícone para uso com o mecanismo.

Executando o código de amostra

O código desta série é baseado no JSF 2 em execução em um contêiner corporativo, como o GlassFish ou Resin. Para obter o passo a passo de instalação e execução do código desta série com o GlassFish, faça o download para obter o código de amostra.

Um componente de ícone reutilizável

Imagine que você tem a profissão mais legal do mundo. Talvez você esteja implementado o mecanismo de gráfico World of Warcraft de última geração. Mas infelizmente não hoje. Hoje você implementará o seletor de fonte mostrado na Figura 1:

Figura 1. Selecionando uma fonte

Seus chefes perguntam quanto tempo vai demorar essa implementação. Eles querem os ícones de + e – que os usuários podem clicar para alterar o tamanho da fonte na visualização com dois caracteres. Obviamente eles querem o uso de Ajax para que a visualização e a exibição do tamanho próximas aos ícones atualizem dinamicamente sem interferir no resto da página.

Os seus chefes esperam um componente simples de seleção de fonte, mas você fará melhor. Você implementará um componente de ícone de uso geral que pode ser configurado no tempo de execução com uma imagem e uma ação, totalmente preparado para Ajax, e usará o componente de ícone na visualização da fonte. Dessa forma, você terá um componente de ícone útil que será muito conveniente no futuro.

Agora irei mostrar como implementar o componente de ícone em menos de 25 linhas de XML.

O exemplo do seletor de fonte

O exemplo de seletor de fonte consiste em quatro arquivos:

  • A página mostrada na Figura 1, definida no index.xhtml.
  • O componente de ícone, que reside em /resources/util/icon.xhtml.
  • Um listener (com.clarity.FontSelectionListener.java).
  • Um bean (com.clarity.FontSettings).

A Figura 2 mostra a estrutura de diretório:

Figura 2. Os arquivos do exemplo de seletor de fonte

A Listagem 1 é o facelet (index.xhtml) da página mostrada na Figura 1:

Listagem 1. O facelet (/web/index.xhtml)

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:corejsf="http://corejsf.com"
xmlns:util="http://java.sun.com/jsf/composite/util">

   <h:head>
      <h:outputStylesheet library="css" name="styles.css"/>
      <title>#{msgs.windowTitle}</title>
   </h:head>

   <h:body> 
      <h:outputStylesheet library="css" name="styles.css"/>

      <h:outputText value="#{msgs.fontSizeHeading}"
        style="padding-left: 30px; font-size: 2em;"/>

      <h:panelGrid columns="3" style="padding-left: 80px;">
        <util:icon id="minus" image="#{resource['images:minus.gif']}">
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>

        <util:icon id="plus" image="#{resource['images:plus.gif']}">
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>

        </util:icon>

        <h:outputText id="readout" value="#{fontSettings.size}em"/>
      </h:panelGrid>

      <h:outputText id="fontPreview" value="Aa" 
                 style="font-size: #{fontSettings.size}em; font-style: italic"/>

   </h:body>
</html>

O facelet na Listagem 1 declara um namespace para o componente de ícone e usa o componente na página. Esse é o uso de componentes compostos 101 do JSF 2.0.

Observe que os dois ícones são equipados com um listener de ação para o componente do link do ícone. Quando um usuário clica no link do ícone, o JSF invoca esse listener, mostrado na Listagem 2, no servidor.

Listagem 2. O listener (com/clarity/FontSelectionListener.java)

package com.clarity;

import javax.el.ELResolver;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class FontSelectionListener implements ActionListener {
   @Override
   public void processAction(ActionEvent event)
         throws AbortProcessingException {
    FacesContext c = FacesContext.getCurrentInstance();
    ELResolver elResolver = c.getApplication().getELResolver();
    FontSettings fs = (FontSettings) 
elResolver.getValue(c.getELContext(), null, "fontSettings");

    if (((UIComponent)event.getSource()).getClientId().startsWith("minus"))
fs.decrement();
    else
fs.increment();
   }
}

Na Listagem 2, olhei para ver se o identificador de cliente do componente que acionou o evento é minus. Se for, sei que o usuário clicou no ícone do sinal de menos, e eu
reduzo o tamanho da fonte. Caso contrário, eu aumento o tamanho da fonte.

Observe que o listener obtém uma referência para o bean gerenciado fontSettings. Ele faz isso obtendo uma referência para o resolvedor de idioma de expressão, que sabe como localizar beans gerenciados de acordo com seus nomes. O bean fontSettings é mostrado na Listagem 3:

Listagem 3. O bean de configuração de fonte (com/clarity/FontSettings.java)

package com.clarity;

import java.io.Serializable;

import javax.inject.Named; 
import javax.enterprise.context.SessionScoped; 

@Named
@SessionScoped
public class FontSettings implements Serializable {
   private static int INCREMENT = 1;
   private int size = 1;

   public int getSize() { return size; }
   public void setSize(int newValue) { size = newValue; }

   public void increment() { size += INCREMENT; }
   public void decrement() { size -= INCREMENT; }
}

As três listagens anteriores mostram todo o código no aplicativo, com exceção do componente composto de ícone. Ele é o item seguinte.

Implementando o componente composto de ícone

Os ícones possuem três requisitos:

  • A imagem deve ser configurável.
  • A ação quando o usuário clica na imagem deve ser configurável.
  • O ícone deve oferecer suporte para Ajax.

Na Listagem 4, eu cumpro os dois primeiros requerimentos:

Listagem 4. O componente composto <util:icon> – parte 1 (/resources/util/icon.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="image" required="true"/>
      <composite:actionSource name="link"  targets="#{cc.clientId}:iconForm:link"/>
    </composite:interface>

    <composite:implementation>
      <div id="#{cc.clientId}">
         <h:form id="iconForm">
            <h:commandLink id="link" immediate="true">
              <h:graphicImage value="#{cc.attrs.image}"/>
            </h:commandLink>
         </h:form>
       </div>
    </composite:implementation>    
</html>

O componente de ícone na Listagem 4 declara um atributo de imagem e um actionSource chamado link. Esse actionSource é usado na Listagem 1 como o valor do atributo for do <f:actionListener>.

A implementação do componente de ícone na Listagem 4 permite ao autor de páginas a configuração da aparência e do comportamento dos ícones, mas não permite ao autor a conexão de comportamentos de Ajax ao componente.

De acordo com a implementação atual, se o usuário clicar em uma imagem, o JSF envia a página completa e redesenha completamente a página ao retornar. Agora você verá como permitir aos autores de páginas a inclusão de Ajax ao componente de ícone.

Incluindo suporte para Ajax com <composite:clientBehavior>

Para permitir aos autores de páginas a inclusão de Ajax no link presente no componente <util:icon>, eu uso a tag <composite:clientBehavior>, conforme mostrado na Listagem 5:

Listagem 5. O componente composto <util:icon> – parte 2

<!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="image" required="true"/>
      <composite:actionSource   name="link"  targets="#{cc.clientId}:iconForm:link"/>
 <composite:clientBehavior name="click" 
 event="action"
 targets="#{cc.clientId}:iconForm:link"/>
    </composite:interface>

    <composite:implementation>
      <div id="#{cc.clientId}">
         <h:form id="iconForm">
            <h:commandLink id="link" immediate="true">
              <h:graphicImage value="#{cc.attrs.image}"/>
            </h:commandLink>
         </h:form>
       </div>
    </composite:implementation>    
</html>

A tag <composite:clientBehavior> expõe um evento de Ajax para um componente contido em um componente composto. Na Listagem 5, eu declaro um comportamento de cliente com o nome lógico click associado ao evento real – o evento action disparado pelo link do ícone.

Aqui está um resumo dos atributos válidos da tag <composite:clientBehavior>:

  • name: O nome do evento usado pelos autores de páginas.
  • default: Pode ser true ou false. Se for true, o evento especificado no atributo name será o evento padrão.
  • event: O nome real do evento.
  • targets: Uma lista separada por espaço dos IDs de componentes para os quais o JSF redireciona o comportamento.

Agora os autores de páginas podem conectar os comportamentos de Ajax ao componente de ícone, conforme mostrado na Listagem 6:

Listagem 6. O facelet – parte 2

<?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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:corejsf="http://corejsf.com"
      xmlns:util="http://java.sun.com/jsf/composite/util">

   <h:head>
      <h:outputStylesheet library="css" name="styles.css"/>
      <title>#{msgs.windowTitle}</title>
   </h:head>

   <h:body> 
      <h:outputStylesheet library="css" name="styles.css"/>

      <h:outputText value="#{msgs.fontSizeHeading}"
        style="padding-left: 30px; font-size: 2em;"/>

      <h:panelGrid columns="3" style="padding-left: 80px;">
        <util:icon id="minus" image="#{resource['images:minus.gif']}">
<f:ajax event="click" render=":readout :fontPreview"/>
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>

        <util:icon id="plus" image="#{resource['images:plus.gif']}">
<f:ajax event="click" render=":readout :fontPreview"/>
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>

        <h:outputText id="readout" value="#{fontSettings.size}em"/>
      </h:panelGrid>

      <h:outputText id="fontPreview" value="Aa" 
                 style="font-size: #{fontSettings.size}em; font-style: italic"/>

   </h:body>
</html>

Na Listagem 6, eu incluí uma tag <f:ajax> nos ícones. Quando o usuário clica em um dos ícones, o JSF cria uma chamada de Ajax para o servidor, e quando a chamada retorna, o JSF renderiza os componentes readout e fontPreview.

Se preferir, será possível incluir um atributo default=”true” na tag <composite:clientBehavior> na interface do ícone. Isso elimina a necessidade de especificação do evento click pelo autor de página, reduzindo as tags <f:ajax> na Listagem 6 para <f:ajax render=”:readout :fontPreview”>.

Não documentado: <composite:clientBehavior>

A tag <composite:clientBehavior> foi detalhada por um subgrupo do Grupo de Especialistas em JSF 2.0. Ela foi implementada na implementação de referência do JSF (Mojarra), mas não foi documentada para o JSF 2.0. Por isso, você não encontrará nenhuma menção a ela no Javadocs e na especificação do JSF.

A documentação ausente é conhecida como um erro de especificação. Consulte a seção Recursos para obter um link para mais informações. O Grupo de Especialistas em JSF garantiu que o erro será corrigido com a inclusão da documentação na especificação e no Javadocs.

Renderizando componentes fora de um componente composto

Na Listagem 6, eu especifico os componentes readout e fontPreview para o atributorender da tag <f:ajax>. Observe que eu prefaciei os identificadores do componente com dois pontos.

Os dois pontos forçam o JSF a procurar os componentes a partir da parte superior da hierarquia do componente, caso contrário o JSF procuraria no contêiner de nomenclatura (normalmente um formulário) próximo ao componente no qual o Ajax está conectado.

No caso do ícone, o componente é o link do ícone, e o formulário é o formulário do ícone que contém o link. Se você remover os dois pontos, o JSF iniciará a procura dos componentes readout e fontPreview no formulário do ícone. Eles não serão encontrados, o que gerará um erro.

Conclusão

Neste artigo, mostrei como o JSF 2 facilita, mesmo com uma tag não documentada, a inclusão de funcionalidades do Ajax nos componentes compostos pelos autores de páginas.

Realmente, conforme visto, é possível fornecer aos autores de páginas a capacidade de transformação de componentes compostos que fazem solicitação de HTTP completas em componentes que fazem solicitações Ajax. E tudo isso é o resultado de uma única linha de XML em uma interface do componente composto.

No próximo artigo do JSF 2 fu, vou deixar os componentes compostos um pouco de lado para abordar o uso dos Contextos e Injeção de Dependência (CDI) com o JSF 2.

Download

Descrição Nome Tamanho Método de download
Sample code for this article j-jsf2fu-0610-src.zip 11KB HTTP

Recursos

Aprender

Obter produtos e tecnologias

  • JSF: Faça o download do JSF 2.0.

Discutir

***

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.