Conteúdo


JSF 2 fu

Assistentes JSF

Implemente um assistente com JSF 2 e CDI

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.

Java™ Enterprise Edition (Java EE) 6 inclui muitas tecnologias poderosas, incluindo JSF 2. Uma dessas tecnologias, Contexts and Dependency Injection (CDI), padroniza de muitas formas os conceitos cultivados por anos em outras estruturas.

Neste artigo, mostrarei como combinar JSF 2 e CDI para implementar um assistente para todos os tipos de quiz realizados on-line. CDI me fornece injeção de dependência, métodos de produtor e um escopo de conversação. Eu usarei os três para implementar um assistente que possa ser usado facilmente por qualquer quiz on-line de múltipla escolha.

Entretanto, este artigo não se trata apenas de CDI. Mostrarei como:

  • Usar modelos de facelets para minimizar o código e maximizar a reutilização
  • Transformar assistentes em Ajax para uma experiência mais tranquila do usuário
  • Usar a injeção de dependência de CDI para simplificar o código
  • Implementar e usar métodos do produtor de CDI para usar beans com perfeição em suas visualizações
  • Aproveitar as vantagens do escopo de conversação de CDI para implementar casos de uso de solicitação múltipla

O código de origem completo para os exemplos do artigo estão disponíveis para download. Consulte a barra lateral Running the sample code para um link de download e um ponteiro a fim de obter instruções de implementação.

O assistente de quiz

A Figura 1 mostra o assistente de quiz em ação:

Figura 1. O assistente de quiz
O assistente de quiz
O assistente de quiz

Inicialmente, o aplicativo contém um link isolado que inicia o assistente: <h:commandLink value="#{msgs.startWizard}" action="#{wizard.start}"/>. O texto do link (Start the wizard) vem com um arquivo de propriedades e é representado pela expressão msgs.startWizard no valor do link. A internacionalização é JSF 101 de cerca de 2004, portanto, não se preocupe com esses detalhes aqui. Observar que todo o aplicativo está localizado é o suficiente, portanto, todas as cadeias de caractere são obtidas do arquivo messages.properties.

O link Start the wizard leva os usuários à página do assistente de quiz, aos quais são apresentadas perguntas, uma por vez, como mostrado nas duas imagens na parte inferior em Figura 1. Eu controlo o estado ativo dos botões do assistente por meio de Ajax simples e um bean de servidor, como lhe mostrarei na seção O Ajax deste artigo.

A Figura 2 mostra a última pergunta, seguida por um resumo das respostas do usuário. O botão Finish ficará ativo apenas quando o usuário estiver na última pergunta; clicar nesse botão leva o usuário à página de resumo.

Figura 2. A página de resumo
A página de resumo
A página de resumo

Agora que você sabe como o assistente de quiz funciona, mostrarei como ele é implementado.

O aplicativo de quiz

Os arquivos do aplicativo de quiz são mostrados na Figura 3:

Figura 3. Os arquivos do aplicativo
Os arquivos do aplicativo

Implementei um assistente de quiz com um modelo JSF 2 (/templates/wizardTemplate.xhtml) usado pela visualização do assistente (/quizWizard/wizard.xhtml).

Além do modelo e da visualização, eu tenho facelets — tudo no diretório quizWizard — para cada uma das partes do assistente:

  • O cabeçalho (/quizWizard/heading.xhtml)
  • A pergunta (/quizWizard/question.xhtml)
  • Os botões de opções (quizWizard/choices.xhtml)
  • Os botões Next, Previous e Finish (quizWizard/controls.xhtml)

O facelet index.xhtml inicia o aplicativo com o link Start the wizard, e o facelet done.xhtml exibe um resumo das perguntas e das respostas.

É isso para o cliente. No servidor, o aplicativo tem três beans, dois dos quais vou abordar na sequência.

Os beans de pergunta do aplicativo

O bean Question, mostrado na Listagem 1, é na verdade uma pergunta, um conjunto de opções de resposta e uma resposta:

Listagem 1. O bean Question
package com.clarity;

import java.io.Serializable;

public class Question implements Serializable {
  private static final long serialVersionUID = 1284490087332362658L;

  private String question, answer;
  private String[] choices;
  private boolean answered = false; // next button is enabled when answered is true
  
  public Question(String question, String[] choices) {
    this.question = question;
    this.choices = choices;
  }

  public void setAnswer(String answer) {
    this.answer = answer;
    answered = true;
  }

  public String getAnswer()    { return answer;   }
  public String getQuestion()  { return question; }
  public String[] getChoices() { return choices;  }
  public boolean isAnswered()  { return answered; }

  public void setAnswered(boolean answered) { this.answered = answered; }  
}

O aplicativo também mantém um array de perguntas na classe Questions, o que é mostrado na Listagem 2:

Listagem 2. O bean Questions
package com.clarity;

import java.io.Serializable;

import com.corejsf.util.Messages;

public class Questions implements Serializable {
  private static final long serialVersionUID = -7148843668107920897L;

  private String question;
  private Question[] questions = {      
    new Question(
       Messages.getString("com.clarity.messages", "expandQuestion", null),
       new String[] { 
         Messages.getString("com.clarity.messages", "hydrogen", null),
         Messages.getString("com.clarity.messages", "helium", null),
         Messages.getString("com.clarity.messages", "water", null),
         Messages.getString("com.clarity.messages", "asphalt", null)
       }),
       
   new Question(
       Messages.getString("com.clarity.messages", "waterSGQuestion", null),
       new String[] { 
         Messages.getString("com.clarity.messages", "onedotoh", null),
         Messages.getString("com.clarity.messages", "twodotoh", null),
         Messages.getString("com.clarity.messages", "onehundred", null),
         Messages.getString("com.clarity.messages", "onethousand", null)
       }),
       
   new Question(
       Messages.getString("com.clarity.messages", "numThermoLawsQuestion", null),
       new String[] { 
         Messages.getString("com.clarity.messages", "one", null),
         Messages.getString("com.clarity.messages", "three", null),
         Messages.getString("com.clarity.messages", "five", null),
         Messages.getString("com.clarity.messages", "ten", null)
       }),
       
   new Question(
       Messages.getString("com.clarity.messages", "closestSunQuestion", null),
       new String[] { 
         Messages.getString("com.clarity.messages", "venus", null),
         Messages.getString("com.clarity.messages", "mercury", null),
         Messages.getString("com.clarity.messages", "mars", null),
         Messages.getString("com.clarity.messages", "earth", null)
       })         
  };
  
  public int size()                        { return questions.length; }
  public String getQuestion()              { return question; }
  public void setQuestion(String question) { this.question = question; }
  public Question[] getQuestions()         { return questions; }
}

Listagem 1 e Listagem 2 são imperceptíveis — elas simplesmente me fornecem uma lista de perguntas no servidor — exceto para o fato de que eu programaticamente obtenho cadeias de caracteres de um pacote configurável de recursos com um método auxiliar. É possível ver como o método funciona fazendo o download do código para este artigo, e você poderá ler sobre isso em Core JavaServer Faces (consulte Recursos).

É isso para beans de aplicativo, exceto para o bean Wizard, que atua como um controlador para o assistente. É onde o único código Java realmente interessante reside nesse aplicativo. Eu abordo o bean Wizard na seção CDI: injeção de dependência e conversações.

Agora que você está mais por dentro dos arquivos no aplicativo e os beans de pergunta, mostrarei como implementar a visualização do assistente.

O modelo e a visualização

Para a maioria dos assistentes, é possível generalizar com segurança a anatomia do assistente, como detalhado na Figura 4:

Figura 4. Anatomia do assistente
Anatomia do assistente
Anatomia do assistente

A Listagem 3 mostra o modelo que encapsula essa anatomia:

Listagem 3. O modelo do assistente (templates/wizardTemplate.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:ui="http://java.sun.com/jsf/facelets">

  <h:head>
    <title>
      <ui:insert name="windowTitle">
        #{msgs.windowTitle}
      </ui:insert>
    </title>
  </h:head>
  
  <h:body>  
    <h:outputStylesheet library="css" name="styles.css" target="head"/>       
    
    <ui:insert name="heading"/>
          
    <div class="wizardPanel">
    
      <div class="subheading">
        <ui:insert name="subheading"/>
      </div>
      
       <div class="work">
         <ui:insert name="work"/>
       </div>
       
      <div class="controls">
        <ui:insert name="controls"/>
      </div>
      
    </div>      
        
  </h:body>
</html>

A implementação específica do assistente de quiz é mostrada na Listagem 4:

Listagem 4. O facelet do assistente (quizWizard/wizard.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
     xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    template="/templates/wizardTemplate.xhtml">

  <ui:define name="heading">
    <ui:include src="heading.xhtml"/>
  </ui:define> 
  
  <ui:define name="subheading">
    <ui:include src="question.xhtml"/>
  </ui:define>
  
  <ui:define name="work">
    <ui:include src="choices.xhtml"/>
  </ui:define>
   
  <ui:define name="controls">
    <ui:include src="controls.xhtml"/>
  </ui:define> 

</ui:composition>

Os modelos são muito simples. Eles inserem seções de uma página definidas por uma visualização. Nesse caso, o modelo na Listagem 3 insere as seções heading, subheading, work e controls definidas pela exibição na Listagem 4. O encapsulamento de recursos comuns de visualizações em modelos torna mais fácil criar novas visualizações — nesse caso, novos tipos de assistentes.

A Listagem 5 mostra a seção heading do assistente de quiz:

Listagem 5. O heading (quizWizard/heading.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
     xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">

      <div class="heading">
        #{msgs.quizTitle}
      </div>

</ui:composition>

A Listagem 6 mostra a seção subheading:

Listagem 6. O subheading (quizWizard/question.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.or g/1999/xhtml"
     xmlns:f="http://java.sun.com/jsf/core"
     xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">

  <h:panelGrid columns="1" id="question">
    #{wizard.cursor+1}. #{questions[wizard.cursor].question}?
  </h:panelGrid>
    
</ui:composition>

O heading na Listagem 5 mostra o título do quiz, neste caso, Science Quiz, e o subheading na Listagem 6 mostra a pergunta. O wizard.cursor mencionado na Listagem 6 é um cursor (ou índice, se preferir) que aponta para a pergunta atual. Esse cursor é baseado em zero, portanto, #{wizard.cursor+1} exibe o número da pergunta, e #{questions[wizard.cursor].question} mostra a pergunta.

Agora que cuidamos das introduções, como geração de modelos e beans de servidor, eu lhe mostrarei o que é realmente interessante: como o Ajax do assistente é implementado e como o assistente usa CDI. Primeiro, o Ajax.

O Ajax

Toda a interação do usuário no assistente de quiz resulta em chamadas Ajax, e somente as seções apropriadas da página são renderizadas quando essas chamadas retornam. Uma coisa que as chamadas Ajax fazem é controlar o estado de ativação dos botões do assistente. A Figura 5 mostra o estado de ativação dos botões do assistente durante a primeira pergunta e a segunda pergunta:

Figura 5. Os botões do assistente de quiz
Os botões do assistente de quiz
Os botões do assistente de quiz

O Ajax do assistente está nitidamente encapsulado em dois arquivos de facelet. A Listagem 7 mostra choices.xhtml:

Listagem 7. As opções
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
     xmlns:f="http://java.sun.com/jsf/core"
     xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
       
  <h:form id="choices">
    <h:panelGrid columns="2">  
        <h:selectOneRadio value="#{questions[wizard.cursor].answer}"
                         layout="pageDirection">
          <f:selectItems value="#{questions[wizard.cursor].choices}"/>
          <f:ajax render=":buttons"/>
        </h:selectOneRadio>
     </h:panelGrid>
  </h:form> 
    
</ui:composition>

Quando o usuário seleciona um botão de opções, JSF faz uma chamada Ajax ao servidor e registra a seleção do botão de opções (a resposta da pergunta) em uma propriedade backing-bean. Quando a chamada retorna, JSF atualiza os botões do assistente.

A Listagem 8 mostra controls.xhtml:

Listagem 8. Os controles
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
     xmlns:f="http://java.sun.com/jsf/core"
     xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">

    <h:form id="buttons">
   
        <h:panelGrid columns="4" styleClass="wizardControls">
        <f:ajax render=":question :choices buttons">

            <h:commandButton id="next" 
                        styleClass="wizardButton"
                             value="#{msgs.nextButtonText}" 
                          disabled="#{not wizard.nextButtonEnabled}"/> 
                    actionListener="#{wizard.nextButtonClicked}"/>
      
            <h:commandButton id="previous"
                        styleClass="wizardButton"
                             value="#{msgs.previousButtonText}" 
                          disabled="#{not wizard.previousButtonEnabled}"
                    actionListener="#{wizard.previousButtonClicked}"/>
        </f:ajax>
                                                              
        <h:commandButton id="finish"
                    styleClass="wizardButton"
                         value="#{msgs.finishButtonText}" 
                      disabled="#{not wizard.finishButtonEnabled}"
                        action="#{wizard.end}"/>
                                                                      
        </h:panelGrid>
        
    </h:form>
</ui:composition>

Quando o usuário clica nos botões Next ou Previous, JSF faz uma chamada Ajax ao servidor e, quando a chamada Ajax retorna, JSF atualiza a pergunta, as opções da pergunta (o botão de opções) e os próprios botões.

O botão Finish não é um botão Ajax porque clicar nele leva para a página done.

Observe as inúmeras referências nas Listagens 7 e 8 ao bean wizard. Esse bean é de fato um controlador para o assistente de quiz. Vou concluir este artigo dando uma olhada nesse bean.

CDI: injeção de dependência e conversações

CDI pode ser descrito como beans gerenciados por JSF anabolizados. Como um componente de Java EE 6, CDI é de muitas formas uma padronização de conceitos cultivados em Spring há bastante tempo, como injeção de dependência e interceptores. De fato, CDI e Spring 3 compartilham muitos recursos similares.

CDI permite dividir as preocupações no que ele se refere como loose coupling e strong typing. Fazendo isso, ele proporciona um escape praticamente de libertação das banalidades da programação diária em Java, como a instanciação de objetos e o controle dos seus tempos de vida.

A partir de uma perspectiva de JSF, um recurso especialmente atraente do CDI é o escopo de conversão. Desenvolvido por Seam, o escopo de conversão é um escopo com um tempo de vida controlado programaticamente, que permite escapar da opção sem saída entre solicitação e sessão.

Todo o uso de CDI pelo assistente está no bean Wizard, mostrado na Listagem 9:

Listagem 9. O bean Wizard
package com.clarity;

import java.io.Serializable;

import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.inject.Produces;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;

@Named()
@ConversationScoped()
public class Wizard implements Serializable {
  private static final long serialVersionUID = 1L;
  private Questions questions = new Questions();
  private int cursor = 0;
  
  @Inject
  private Conversation conversation;@Produces @Named
  public Question[] getQuestions() {
    return questions.getQuestions();
  }
  
  public void nextButtonClicked(ActionEvent e) {
    incrementCursor();
  }

  public void previousButtonClicked(ActionEvent e) {
    decrementCursor();
  }
    
  public void incrementCursor() { ++cursor; }
  public void decrementCursor() { --cursor; }
  public int  getCursor()       { return cursor; }
  public void resetCursor()     { cursor = 0; }

  public boolean getNextButtonEnabled() {
    return cursor != questions.size() - 1 &&
    (questions.getQuestions())[cursor].isAnswered();
  }
  
  public boolean getPreviousButtonEnabled() {
    return cursor > 0;
  }
  
  public boolean getFinishButtonEnabled() {
    return cursor == questions.size() - 1 &&
    (questions.getQuestions())[cursor].isAnswered();
  }
  
  public String start() {
    conversation.begin();
    return "quizWizard/wizard";
  }
  
  public String end() {
    conversation.end();
    return "/done";
  }
  
  private void setCurrentQuestionUnanswered() {
    Question currentQuestion = (questions.getQuestions())[cursor];
    currentQuestion.setAnswered(false);    
  }
}

Praticamente, todo o código interessante do aplicativo do assistente de quiz reside na Listagem 9. Primeiro, o bean Wizard tem métodos que controlam o estado de ativação dos botões do assistente, como eu mencionei na seção anterior. Ele também tem os métodos chamados por JSF quando o usuário clica nos botões Next ou Previous. Esses métodos avançam para a próxima pergunta ou voltam para a anterior, respectivamente.

Mas, aparentemente, a coisa mais interessante sobre o bean Wizard é o seu uso do CDI. Primeiro, como tenho feito em toda esta série, estou usando a implementação de CDI da anotação @Named (que é na verdade definida por JSR 330, injeção de dependência para Java) no lugar de @ManagedBean. As duas anotações criam um bean com escopo que pode ser acessado pela linguagem de expressão JSF. Mas o recurso de bean gerenciado de CDI é muito mais sofisticado, portanto, se estiver usando um servidor compatível com Java EE 6, escolha @Named em vez de @ManagedBean.

Se observar com mais atenção a Listagem 6 e a Listagem 7, verá que estou acessando um bean denominado questions na linguagem de expressão JSF. Você poderá também se lembrar de que implementei uma classe Questions na Listagem 2. Entretanto, não será possível ver uma anotação @Named na Listagem 2. Em circunstâncias normais, essa falta de anotação produzirá um erro, mas, neste caso, o bean questions vem de outro lugar — ele é produzido pelo método Wizard.getQuestions(). Esse método é anotado com uma anotação @Produces, o que significa que JSF chama esse método para obter o bean Questions quando for feita referência ao bean na linguagem de expressão.

Em seguida, há o uso do escopo de conversação do bean Wizard. O link Start the wizard na página de boas-vindas do aplicativo é vinculado ao método start() do bean Wizard, que inicia uma conversação chamando o método begin() de conversation. Esse método promove a solicitação atual (que é na verdade uma conversação que dura por uma única solicitação) para uma conversação de longa duração que não será finalizada até o seu tempo limite ser atingido ou alguém chamar o método end() de conversation. Como eu especifiquei o escopo de Conversation para Wizard, seu tempo de vida terminará quando a conversação terminar.

Você poderá, claro, fugir do escopo de conversação e implementar seu próprio escopo de pseudoconversação na sessão do usuário. De fato, é exatamente o que muitos desenvolvedores fizeram antes do escopo de conversação para manter o estado dos casos de uso com várias solicitações em seus aplicativos. CDI tira essa tarefa burocrática manual de suas mãos.

Para terminar, observe que estou usando a injeção de CDI para injetar uma conversação no bean gerenciado, portanto, posso iniciar programaticamente o início e o término de uma conversação. A injeção de recurso permite que eu me concentre em fazer coisas com objetos, em vez de me preocupar com detalhes banais de criá-los e controlar seus tempos de vida.

Conclusão

Neste artigo, falei de bastante coisa — assistentes Ajax, modelos, injeção de dependência, escopo de conversação — surpreendentemente com pouco código. Com a ajuda de JSF 2 e CDI, é possível implementar aplicativos da Web robustos e reutilizáveis, com um mínimo de confusão e um máximo de flexibilidade e capacidade de reutilização.

A série JSF 2 fu fará uma pausa agora no terceiro trimestre. Volto em outubro com muito mais novidades para ajudá-lo a aprimorar ainda mais suas habilidades em JSF.


Recursos para download


Temas relacionados


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=502382
ArticleTitle=JSF 2 fu: Assistentes JSF
publish-date=09292010