2. Java Server Faces
Apresentamos agora o framework Java Server Faces. Será utilizada a versão 2, mas os exemplos apresentam principalmente características da versão 1. Apresentaremos, da versão 2, apenas as características necessárias para a aplicação de exemplo que se seguirá.
2.1. O papel do JSF numa aplicação web
Em primeiro lugar, vamos situar o JSF no contexto do desenvolvimento de uma aplicação web. Na maioria das vezes, esta será construída com base numa arquitetura multicamadas, tal como a seguinte:
![]() |
- a camada [web] é a camada em contacto com o utilizador da aplicação web. Este interage com a aplicação web através de páginas web visualizadas por um navegador. É nesta camada que se situa JSF e apenas nesta camada,
- a camada [métier] implementa as regras de gestão da aplicação, tais como o cálculo de um salário ou de uma fatura. Esta camada utiliza dados provenientes do utilizador através da camada [web] e do SGBD através da camada [DAO],
- a camada [DAO] (Data Access Objects), a camada [jpa] (Java Persistence API) e o controlador JDBC gerem o acesso aos dados do SGBD. A camada [jpa] funciona como um ORM (Mapeador Objeto-Relacional). Estabelece uma ponte entre os objetos manipulados pela camada [DAO] e as linhas e colunas dos dados de uma base de dados relacional,
- a integração das camadas pode ser realizada por um contentor Spring ou por um EJB3 (Enterprise Java Bean).
Os exemplos apresentados a seguir para ilustrar o JSF utilizarão apenas uma camada, a camada [web]:
![]() |
Depois de adquiridos os conceitos básicos do JSF, iremos construir aplicações Java EE multicamadas.
2.2. O modelo de desenvolvimento MVC de JSF
JSF implementa o modelo de arquitetura denominado MVC (Modelo – Vista – Controlador) da seguinte forma:
![]() |
Esta arquitetura implementa o Padrão de Design MVC (Modelo, Vista, Controlador). O processamento de um pedido de um cliente decorre de acordo com as quatro etapas seguintes:
- pedido – o navegador do cliente envia um pedido ao controlador [Faces Servlet]. Este recebe todos os pedidos dos clientes. É a porta de entrada da aplicação. É o C de MVC,
- processamento — o controlador C processa esta solicitação. Para tal, conta com a ajuda de gestores de eventos específicos da aplicação [2a]. Estes gestores podem necessitar da ajuda da camada de negócio [2b]. Depois de processada a solicitação do cliente, esta pode gerar várias respostas. Um exemplo clássico é:
- uma página de erros, caso a solicitação não tenha podido ser processada corretamente;
- uma página de confirmação, caso contrário,
- navegação — o controlador escolhe a resposta (= vista) a enviar ao cliente. Escolher a resposta a enviar ao cliente requer várias etapas:
- escolher o Facelet que irá gerar a resposta. É o que se denomina a vista V, o V de MVC. Esta escolha depende, em geral, do resultado da execução da ação solicitada pelo utilizador;
- fornecer a essa Facelet os dados de que necessita para gerar essa resposta. Com efeito, esta contém, na maioria das vezes, informações calculadas pelo controlador. Estas informações constituem o chamado modelo M da vista, o M de MVC,
A etapa 3 consiste, portanto, na escolha de uma vista V e na construção do modelo M necessário para a mesma.
- Resposta — o controlador C solicita que a Facelet escolhida seja apresentada. Esta utiliza o modelo M preparado pelo controlador C para inicializar as partes dinâmicas da resposta que deve enviar ao cliente. A forma exata desta resposta pode variar: pode ser um fluxo HTML, PDF, Excel, ...
Num projeto JSF:
- o controlador C é o servlet [javax.faces.webapp.FacesServlet]. Este encontra-se na biblioteca [javaee.jar],
- as vistas V são implementadas por páginas que utilizam a tecnologia Facelets,
- os modelos M e os gestores de eventos são implementados por classes Java frequentemente designadas por «backing beans» ou, mais simplesmente, «beans».
Agora, vamos esclarecer a relação entre a arquitetura web MVC e a arquitetura em camadas. Trata-se de dois conceitos diferentes que, por vezes, são confundidos. Consideremos uma aplicação web JSF de uma camada:
![]() |
Se implementarmos a camada [web] com JSF, teremos, de facto, uma arquitetura web MVC, mas não uma arquitetura multicamadas. Neste caso, a camada [web] encarregar-se-á de tudo: apresentação, lógica de negócio e acesso aos dados. Com a JSF, serão os beans a realizar esse trabalho.
Agora, consideremos uma arquitetura web multicamadas:
![]() |
A camada [web] pode ser implementada sem framework e sem seguir o modelo MVC. Temos, então, uma arquitetura multicamadas, mas a camada web não implementa o modelo MVC.
Em MVC, referimos que o modelo M era o da vista V, c.a.d, ou seja, o conjunto de dados apresentados pela vista V. É frequentemente apresentada outra definição do modelo M de MVC:
![]() |
Muitos autores consideram que o que se encontra à direita da camada [web] constitui o modelo M do MVC. Para evitar ambiguidades, referir-se-á:
- do modelo do domínio quando nos referirmos a tudo o que está à direita da camada [web],
- de «modelo da vista» quando nos referirmos aos dados apresentados por uma vista V.
Daqui em diante, o termo «modelo M» designará exclusivamente o modelo de uma vista V.
2.3. Exemplo mv-jsf2-01: os elementos de um projeto JSF
Os primeiros exemplos limitar-se-ão à camada web implementada com JSF 2:
![]() |
Quando os conceitos básicos estiverem assimilados, iremos estudar exemplos mais complexos com arquiteturas multicamadas.
2.3.1. Criação do projeto
Geramos o nosso primeiro projeto JSF2 com o NetBeans 7.
![]() |
- em [1], criamos um novo projeto,
- em [2], selecionar a categoria [Maven] e o tipo de projeto [Web Application],
![]() |
- em [3], indicar a pasta pai da pasta do novo projeto,
- em [4], atribuir um nome ao projeto,
- em [5], escolher um servidor. Com o NetBeans 7, pode-se escolher entre os servidores Apache Tomcat e GlassFish. A diferença entre os dois é que o GlassFish suporta os EJB (Enterprise Java Bean) e o Tomcat não. Nos nossos exemplos, não vamos utilizar JSF. Por isso, aqui podemos escolher qualquer servidor,
- no [6], escolhe-se a versão Java EE 6 Web,
- em [7], o projeto gerado.
Vamos analisar os elementos do projeto e explicar a função de cada um.
![]() |
- em [1]: os diferentes ramos do projeto:
- [Web Pages]: conterá as páginas web (.xhtml, .jsp, .html), os recursos (imagens, documentos diversos), a configuração da camada web, bem como a do framework JSF;
- [Source packages]: as classes Java do projeto;
- [Dependencies]: os arquivos .jar necessários ao projeto e geridos pelo framework Maven;
- [Java Dependencies]: os ficheiros .jar necessários ao projeto e não geridos pelo framework Maven;
- [Project Files]: ficheiro de configuração do Maven e do NetBeans,
![]() |
- em [2]: o ramo [Web Pages],
Contém a seguinte página [index.jsp]:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/HTML4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Trata-se de uma página web que exibe a sequência de caracteres «Hello World» em letras grandes.
O ficheiro [META-INF/context.xml] é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/mv-jsf2-01"/>
A linha 2 indica que o contexto da aplicação (ou o seu nome) é /mv-jsf2-01. Isto significa que as páginas web do projeto serão solicitadas através de um URL com o formato http://machine:port/mv-jsf2-01/page. O contexto é, por predefinição, o nome do projeto. Não será necessário alterar este ficheiro.
![]() |
- em [3], o ramo [Source Packages],
Este ramo contém os códigos-fonte das classes Java do projeto. Aqui não temos nenhuma classe. O NetBeans gerou um pacote por predefinição que pode ser eliminado: [4].
![]() |
- em [5], o ramo [Dependencies],
Este ramo apresenta todas as bibliotecas necessárias ao projeto e geridas pelo Maven. Todas as bibliotecas aqui listadas serão descarregadas automaticamente pelo Maven. É por isso que um projeto Maven necessita de acesso à Internet. As bibliotecas descarregadas serão armazenadas localmente. Se outro projeto necessitar de uma biblioteca já presente localmente, esta não será descarregada. Veremos que esta lista de bibliotecas, bem como os repositórios onde podem ser encontradas, estão definidos no ficheiro de configuração do projeto Maven.
![]() |
- em [6], as bibliotecas necessárias ao projeto e que não são geridas pelo Maven,
![]() |
- em [7], os ficheiros de configuração do projeto Maven:
- [nb-configuration.xml] é o ficheiro de configuração do NetBeans. Não nos vamos debruçar sobre este.
- [pom.xml]: o ficheiro de configuração do Maven. POM significa Project Object Model. Por vezes, teremos de intervir diretamente neste ficheiro.
O ficheiro [pom.xml] gerado é o seguinte:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${endorsed.dir}</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- as linhas 5 a 8 definem o objeto (artefacto) Java que será criado pelo projeto Maven. Estas informações provêm do assistente utilizado durante a criação do projeto:
![]() |
Um objeto Maven é definido por quatro propriedades:
- [groupId]: uma informação que se assemelha a um nome de pacote. Assim, as bibliotecas do framework Spring têm groupId=org.springframework, as do framework JSF têm groupId=javax.faces,
- [artifactId]: o nome do objeto Maven. No grupo [org.springframework] encontram-se, assim, os seguintes artifactId: spring-context, spring-core, spring-beans, ... No grupo [javax.faces], encontram-se o artifactId e o jsf-api,
- [version]: número de versão do artefacto Maven. Assim, o artefacto org.springframework.spring-core tem as seguintes versões: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
- [packaging]: o formato do artefacto, na maioria das vezes war ou jar.
O nosso projeto Maven irá, portanto, gerar um [war] (linha 8) no grupo [istia.st] (linha 5), denominado [mv-jsf2-01] (linha 6) e com a versão [1.0-SNAPSHOT] (linha 7). Estas quatro informações devem definir de forma única um artefacto Maven.
As linhas 17-24 enumeram as dependências do projeto Maven, ou seja, a lista das bibliotecas necessárias ao projeto. Cada biblioteca é definida pelas quatro informações (groupId, artifactId, versão, packaging). Quando a informação packaging está ausente, como neste caso, é utilizado o ficheiro jar packaging. Acrescenta-se aqui outra informação, «scope», que determina em que momentos do ciclo de vida do projeto a biblioteca é necessária. O valor predefinido é «compile», o que indica que a biblioteca é necessária para a compilação e para a execução. O valor «provided» significa que a biblioteca é necessária durante a compilação, mas não durante a execução. Neste caso, durante a execução, será fornecida pelo servidor Tomcat 7.
2.3.2. Execução do projeto
Executamos o projeto:
![]() |
Em [1], o projeto Maven é executado. O servidor Tomcat é então iniciado, caso ainda não o estivesse. É também iniciado um navegador e é solicitada a página URL do contexto do projeto [2]. Como não é solicitado nenhum documento, a página index.html, index.jsp, index.xhtml é então utilizada, caso exista. Neste caso, será a página [index.jsp].
2.3.3. O sistema de ficheiros de um projeto Maven
![]() |
- [1]: o sistema de ficheiros do projeto encontra-se no separador [Files],
- [2]: os códigos-fonte Java encontram-se na pasta [src / main / java],
- [3]: as páginas web encontram-se na pasta [src / main / webapp],
- [4]: a pasta [target] é criada durante a compilação do projeto,
- [5]: neste caso, a compilação do projeto criou um arquivo [mv-jsf2-01-1.0-SNAPSHOT.war]. Foi este arquivo que foi executado pelo servidor Tomcat.
2.3.4. Configurar um projeto para JSF
O nosso projeto atual não é um projeto JSF. Faltam-lhe as bibliotecas do framework JSF. Para transformar o projeto atual num projeto JSF, proceda da seguinte forma:
![]() |
- em [1], acede-se às propriedades do projeto,
- no [2], seleciona-se a categoria [Frameworks],
- em [3], adiciona-se um framework,
![]() |
- No [4], seleciona-se Java Server Faces,
- em [5], o NetBeans sugere-nos a versão 2.1 do framework. Aceitamo-la,
- em [6], o projeto passa então a incluir novas dependências.
O ficheiro [pom.xml] foi atualizado para refletir esta nova configuração:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
...
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
...
</build>
<repositories>
<repository>
<URL>http://download.java.net/maven/2/</URL>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<URL>http://repo1.maven.org/maven2/</URL>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
Nas linhas 14-33, foram adicionadas novas dependências. O Maven descarrega-as automaticamente. Ele procura-as no que se denomina «repositórios». O repositório central (Central Repository) é utilizado automaticamente. É possível adicionar outros repositórios através da baliza <repository>. Aqui, foram adicionados dois repositórios:
- linhas 46-51: um repositório para a biblioteca JSF 2,
- linhas 52-57: um repositório para a biblioteca JSTL 1.1.
O projeto foi também enriquecido com uma nova página web:
![]() |
A página [index.HTML] é a seguinte:
<?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:h="http://java.sun.com/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
Temos aqui um ficheiro XML (linha 1). Nele encontram-se as balizas do HTML, mas no formato XML. A isto chama-se XHTML. A tecnologia utilizada para criar páginas web com o JSF 2 chama-se Facelets. Por isso, por vezes, a página XHTML é designada por página Facelet.
As linhas 3-4 definem a baliza <html> com os espaços de nomes XML (xmlns=XML Name Space).
- A linha 3 define o espaço de nomes principal http://www.w3.org/1999/xhtml,
- a linha 4 define o espaço de nomes http://java.sun.com/jsf/html das balizas HTML. Estas serão prefixadas por h:, conforme indicado por xmlns:h. Estas balizas encontram-se nas linhas 5, 7, 8 e 10.
Ao encontrar a declaração de um espaço de nomes, o servidor web irá explorar as pastas [META-INF] e Classpath da aplicação, à procura de ficheiros com a extensão .tld (TagLib Definition). Aqui, irá encontrá-los no arquivo [jsf-impl.jar] [1,2]:
![]() |
Vamos analisar o ficheiro [3] e o ficheiro [HTML_basic.tld]:
- na linha 19, o URI da biblioteca de tags,
- na linha 16, o seu nome abreviado.
As definições das diferentes balizas <h:xx> encontram-se neste ficheiro. Estas balizas são geridas por classes Java que também se encontram no artefacto [jsf-impl.jar].
Voltemos ao nosso projeto JSF. Este foi ampliado com um novo ramo:
![]() |
O ramo [Other Sources] [1] contém os ficheiros que devem estar no Classpath do projeto e que não são código Java. É o caso dos ficheiros de mensagens em JSF. Vimos que, sem a adição do framework JSF ao projeto, esta ramificação não existe. Para a criar, basta criar a pasta [src / main / resources] [3] no separador [Files] [2].
Por fim, surgiu uma nova pasta no ramo [Web Pages]:
![]() |
A pasta [WEB-INF] foi criada, contendo o ficheiro [web.xml] . Este ficheiro configura a aplicação web:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- as linhas 7-10 definem um servlet, c.a.d, uma classe Java capaz de processar os pedidos dos clientes. Uma aplicação JSF funciona da seguinte forma:
![]() |
Esta arquitetura implementa o Padrão de Design MVC (Modelo, Vista, Controlador). Recordamos o que já foi referido anteriormente. O processamento de um pedido de um cliente decorre de acordo com as quatro etapas seguintes:
1 - pedido - o navegador do cliente envia um pedido ao controlador [Faces Servlet]. Este recebe todos os pedidos dos clientes. É a porta de entrada da aplicação. É o C de MVC,
2 - processamento - o controlador C processa esta solicitação. Para tal, conta com a ajuda de gestores de eventos específicos da aplicação [2a]. Estes gestores podem necessitar da ajuda da camada de negócio [2b]. Depois de processado o pedido do cliente, este pode gerar várias respostas. Um exemplo clássico é:
- uma página de erros, caso a solicitação não tenha podido ser processada corretamente;
- uma página de confirmação, caso contrário,
3 - navegação - o controlador escolhe a resposta (= vista) a enviar ao cliente. A escolha da resposta a enviar ao cliente requer várias etapas:
- escolher o Facelet que irá gerar a resposta. É o que se denomina a vista V, o V de MVC. Esta escolha depende, em geral, do resultado da execução da ação solicitada pelo utilizador;
- fornecer a este Facelet os dados de que necessita para gerar essa resposta. Com efeito, esta contém, na maioria das vezes, informações calculadas pelo controlador. Estas informações constituem o chamado modelo M da vista, o M de MVC,
A etapa 3 consiste, portanto, na escolha de uma vista V e na construção do modelo M necessário para a mesma.
4 - resposta - o controlador C solicita à Facelet escolhida que se apresente. Esta utiliza o modelo M preparado pelo controlador C para inicializar as partes dinâmicas da resposta que deve enviar ao cliente. A forma exata desta resposta pode variar: pode ser um fluxo HTML, PDF, Excel, ...
Num projeto JSF:
- o controlador C é o servlet [javax.faces.webapp.FacesServlet],
- as vistas V são implementadas por páginas que utilizam a tecnologia Facelets,
- os modelos M e os gestores de eventos são implementados por classes Java frequentemente designadas por «backing beans» ou, mais simplesmente, «Beans».
Voltemos ao conteúdo do ficheiro [web.xml):
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- linhas 12-15: a baliza <servlet-mapping> serve para associar um servlet a um URL solicitado pelo navegador do cliente. Aqui, indica-se que os URL com o formato [/faces/*] devem ser processados pelo servlet com o nome [Faces Servlet]. Este está definido nas linhas 7-10. Como não existe nenhuma outra baliza <servlet-mapping> no ficheiro, isso significa que a servlet [Faces Servlet] processará apenas os URL com o formato [/faces/*]. Vimos que o contexto da aplicação se chamava [/mv-jsf2-01]. Os URL dos clientes processados pelo servlet [Faces Servlet] terão, portanto, o formato [http://machine:port/mv-jsf2-01/faces/*]. As páginas .html e .jsp serão processadas, por predefinição, pelo próprio contentor de servlets, e não por uma servlet específica. Com efeito, o contentor de servlets sabe como as gerir,
- linhas 7-10: definem a servlet [Faces Servlet]. Como todas as URL aceites são encaminhadas para ela, esta é o controlador C do modelo MVC,
- linha 10: indica que a servlet deve ser carregada na memória logo que o servidor web for iniciado. Por predefinição, uma servlet só é carregada quando recebe o primeiro pedido que lhe é feito,
- linhas 3-6: definem um parâmetro destinado à servlet [Faces Servlet]. O parâmetro javax.faces.PROJECT_STAGE define a fase em que se encontra o projeto em execução. Na fase de Desenvolvimento, a servlet [Faces Servlet] exibe mensagens de erro úteis para a depuração. Na fase de Produção, essas mensagens deixam de ser exibidas,
- linhas 17-19: duração, em minutos, de uma sessão. Um cliente interage com a aplicação através de uma sequência de ciclos de pedido/resposta. Cada ciclo utiliza uma ligação TCP-IP que lhe é própria, criada de novo a cada novo ciclo. Assim, se um cliente C efetuar duas solicitações D1 e D2, o servidor S não tem como saber que ambas as solicitações pertencem ao mesmo cliente C. O servidor S não dispõe da memória do cliente. É o protocolo HTTP utilizado (Protocolo de Transporte HyperText) que assim o exige: o cliente comunica com o servidor através de uma sucessão de ciclos de pedido do cliente / resposta do servidor, utilizando sempre uma nova ligação TCP-IP. Trata-se de um protocolo sem estado. Noutros protocolos, como por exemplo o FTP (File Transfer Protocol), o cliente C utiliza a mesma ligação durante todo o período da sua interação com o servidor S. Uma ligação está, portanto, associada a um cliente específico. O servidor S sabe sempre com quem está a lidar. Para poder reconhecer que um pedido pertence a um determinado cliente, o servidor web pode utilizar a técnica da sessão:
- na primeira solicitação de um cliente, o servidor S envia-lhe a resposta esperada, juntamente com um token, uma sequência de caracteres aleatória, exclusiva desse cliente;
- em cada pedido subsequente, o cliente C reenvia ao servidor S o token que recebeu, permitindo assim que o servidor S o reconheça.
A aplicação tem agora a possibilidade de solicitar ao servidor que memorize informações associadas a um determinado cliente. Fala-se de sessão do cliente. A linha 18 indica que a duração de uma sessão é de 30 minutos. Isto significa que, se um cliente C não efetuar uma nova solicitação durante 30 minutos, a sua sessão é encerrada e as informações que continha são perdidas. Na sua próxima solicitação, tudo decorrerá como se fosse um novo cliente e será iniciada uma nova sessão,
- linhas 21-23: a lista de páginas a apresentar quando o utilizador solicita o contexto sem especificar uma página, por exemplo, aqui [http://machine:port/mv-jsf2-01]. Neste caso, o servidor web (não o servlet) verifica se a aplicação definiu uma baliza <welcome-file-list>. Se sim, apresenta a primeira página encontrada na lista. Se esta não existir, a segunda página, e assim sucessivamente até encontrar uma página existente. Neste caso, quando o cliente solicita o URL [http://machine:port/mv-jsf2-01], é o URL [http://machine:port/mv-jsf2-01/index.xhtml] que lhe será apresentado.
2.3.5. Executar o projeto
Quando se executa o novo projeto, o resultado obtido no navegador é o seguinte:
![]() |
- em [1], o contexto foi solicitado sem especificação de documento,
- em [2], tal como foi explicado, é então a página inicial (welcome-file) [index.xhtml] que é apresentada.
Pode ser interessante dar uma vista de olhos ao código-fonte recebido [3]:
Recebemos o HTML. Todas as balizas <h:xx> do index.xhtml foram traduzidas para as suas correspondentes no HTML.
2.3.6. O repositório Maven local
Já referimos que o Maven descarrega as dependências necessárias para o projeto e as armazena localmente. É possível explorar este repositório local:
![]() |
- em [1], seleciona-se a opção [Window / Other / Maven Repository Browser],
- em [2], abre-se um separador [Maven Repositories],
- em [3], este contém dois ramos, um para o repositório local e outro para o repositório central. Este último é gigantesco. Para visualizar o seu conteúdo, é necessário atualizar o seu índice [4]. Esta atualização demora várias dezenas de minutos.
![]() |
- em [5], as bibliotecas do repositório local,
- em [6], encontra-se um ramo [istia.st] que corresponde ao [groupId] do nosso projeto,
- Em [7], acede-se às propriedades do repositório local;
- em [8], temos o caminho do repositório local. É útil conhecê-lo porque, por vezes (raramente), o Maven deixa de utilizar a versão mais recente do projeto. Fazemos alterações e verificamos que estas não são tidas em conta. Podemos então eliminar manualmente o ramo do repositório local correspondente ao nosso [groupId]. Isto obriga o Maven a recriar o ramo a partir da última versão do projeto.
2.3.7. Procurar um artefacto com o Maven
Vamos agora aprender a procurar um artefacto com o Maven. Comecemos pela lista de dependências atuais do ficheiro [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
...
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
...
</build>
<repositories>
<repository>
<url>http://download.java.net/maven/2/</url>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
As linhas 13-40 definem as dependências e as linhas 45-58 indicam os repositórios onde estas podem ser encontradas, para além do repositório central, que é sempre utilizado. Vamos alterar as dependências para utilizar as bibliotecas na sua versão mais recente.
![]() |
Em primeiro lugar, eliminamos as dependências atuais [1]. O ficheiro [pom.xml] é então alterado:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
...
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
<repositories>
<repository>
<url>http://download.java.net/maven/2/</url>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
Nas linhas 5-12, as dependências removidas já não aparecem no [pom.xml]. Agora, vamos procurá-las nos repositórios do Maven.
![]() |
- no [1], adiciona-se uma dependência ao projeto,
- no [2], é necessário especificar informações sobre o artefacto procurado (groupId, artifactId, versão, embalagem (Tipo) e âmbito). Começamos por especificar o [groupId] [3],
- em [4], digitamos [espace] para exibir a lista de artefactos possíveis. Aqui, [jsf-api] e [jsf-impl]. Escolhemos [jsf-api],
- em [5], seguindo o mesmo procedimento, escolhemos a versão mais recente. O tipo de empacotamento é jar.
Procedemos da mesma forma para todos os artefactos:
![]() | ![]() | ![]() | ![]() |
![]() |
No ficheiro [6], as dependências adicionadas aparecem no projeto. O ficheiro [pom.xml] reflete estas alterações:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Suponhamos agora que não conhecemos o [groupId] do artefacto que pretendemos. Por exemplo, queremos utilizar o Hibernate como ORM (Mapeador Objeto-Relacional) e é tudo o que sabemos. Podemos então aceder ao site [http://mvnrepository.com/]:
![]() |
No [1], é possível introduzir palavras-chave. Digamos hibernate e iniciemos a pesquisa.
![]() |
- em [2], selecionemos o [groupId], o org.hibernate e o [artifactId], o hibernate-core,
- em [3], escolhemos a versão 4.1.2-Final,
- em [4], obtemos o código Maven para colar no ficheiro [pom.xml]. Fazemo-lo.
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2.Final</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
...
</dependencies>
Guardamos o ficheiro [pom.xml]. O Maven inicia então o download das novas dependências. O projeto evolui da seguinte forma:
![]() |
- em [5], a dependência [hibernate-core-4.1.2-Final]. No repositório onde foi encontrado, este [artifactId] também é descrito por um ficheiro [pom.xml]. Este ficheiro foi lido e o Maven descobriu que o [artifactId] tinha dependências. Também as descarrega. Fará o mesmo para cada [artifactId] descarregado. No final, encontramos no [6] dependências que não tínhamos solicitado diretamente. Estas são assinaladas por um ícone diferente do do [artifactId] principal.
Neste documento, utilizamos o Maven principalmente por causa desta característica. Isto evita-nos ter de conhecer todas as dependências de uma biblioteca que pretendemos utilizar. Deixamos que o Maven as gere. Além disso, ao partilhar um ficheiro [pom.xml] entre programadores, temos a garantia de que cada programador utiliza efetivamente as mesmas bibliotecas.
Nos exemplos que se seguem, limitar-nos-emos a fornecer o ficheiro [pom.xml] utilizado. O leitor só terá de o utilizar para se encontrar nas mesmas condições que o documento. Além disso, os projetos Maven são reconhecidos pelos principais ambientes de desenvolvimento Java (Eclipse, NetBeans, IntelliJ, JDeveloper). Assim, o leitor poderá utilizar o seu ambiente de desenvolvimento favorito para testar os exemplos.
2.4. Exemplo mv-jsf2-02: gestor de eventos – internacionalização – navegação entre páginas
2.4.1. A aplicação
A aplicação é a seguinte:
![]() |
- em [1], a página inicial da aplicação,
- em [2], dois links para alterar o idioma das páginas da aplicação,
- em [3], um link de navegação para outra página,
- ao clicar em [3], é apresentada a página [4],
- o link [5] permite regressar à página inicial.
![]() |
- na página inicial [1], os links [2] permitem mudar de idioma,
- em [3], a página inicial em inglês.
2.4.2. O projeto NetBeans
Iremos gerar um novo projeto web, tal como explicado no parágrafo 2.3.1. Iremos denominá-lo mv-jsf2-02:
![]() |
- em [1], o projeto gerado,
- em [2], eliminámos o pacote [istia.st.mvjsf202] e o ficheiro [index.jsp],
- No ficheiro [3], foram adicionadas dependências Maven através do seguinte ficheiro [pom.xml]:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
As dependências adicionadas são as do framework JSF. Basta copiar as linhas acima para o ficheiro [pom.xml], substituindo as dependências antigas.
![]() |
- em [4, 5]: cria-se uma pasta [src / main / resources] no separador [Files],
- em [6], no separador [Projects], o que criou o ramo [Other Sources].
Temos agora um projeto JSF. Nele, iremos criar diferentes tipos de ficheiros:
- páginas web no formato XHTML,
- classes Java,
- ficheiros de mensagens,
- o ficheiro de configuração do projeto JSF.
Vamos ver como criar cada tipo de ficheiro:
![]() |
- no [1], criamos uma página JSF
- no [2], criamos uma página [index.xhtml] no formato [Facelets] [3],
- em [4], foram criados dois ficheiros: [index.xhtml] e [WEB-INF / web.xml].
O ficheiro [web.xml] configura a aplicação JSF. É o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
Já comentámos este ficheiro no parágrafo 2.3.4. Recorde-se as suas principais propriedades:
- todos os URL do tipo faces/* são processados pelo servlet [javax.faces.webapp.FacesServlet],
- a página [index.xhtml] é a página inicial da aplicação.
O ficheiro [index.xhtml] criado é o seguinte:
<?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:h="http://java.sun.com/jsf/html">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
Já nos deparámos com este ficheiro no parágrafo 2.3.4.
Vamos agora criar uma classe Java:
![]() |
- em [1], criamos uma classe Java no ramo [Source Packages],
- em [2], atribuímos-lhe um nome e colocamo-la num pacote [3],
- em [4], a classe criada aparece no projeto.
O código da classe criada é um esboço de classe:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package istia.st;
/**
*
* @author Serge Tahé
*/
public class Form {
}
Por fim, vamos criar um ficheiro de mensagens:
- em [1], criação de um ficheiro [Properties],
- em [2], indicamos o nome do ficheiro e, em [3], a sua pasta,
- em [4], o ficheiro [messages.properties] foi criado.
Por vezes, é necessário criar o ficheiro [WEB-INF/faces-config.xml] para configurar o projeto JSF. Este ficheiro era obrigatório com o JSF 1. É opcional com o JSF 2. No entanto, é necessário se o site JSF for internacionalizado. Será esse o caso posteriormente. Por isso, vamos agora mostrar como criar este ficheiro de configuração.
![]() |
- em [1], criamos o ficheiro de configuração JSF,
- em [2], atribuímos-lhe um nome e, em [3], a sua pasta,
- em [4], o ficheiro criado.
O ficheiro [faces-config.xml] criado é o seguinte:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
</faces-config>
A baliza raiz é <faces-config>. O corpo desta baliza está vazio. Teremos de o preencher.
Temos agora todos os elementos para criar um projeto JSF. Nos exemplos que se seguem, apresentamos o projeto JSF completo e, em seguida, detalhamos os seus elementos um a um. Apresentamos agora um projeto para explicar os conceitos:
- gestão de eventos de um formulário,
- de internacionalização das páginas de um site JSF,
- da navegação entre páginas.
O projeto [mv-jsf2-02] tem a seguinte estrutura. O leitor pode encontrá-lo no site de exemplos (ver parágrafo 1.2).
![]() |
- em [1], dos ficheiros de configuração do projeto JSF,
- em [2], as páginas JSF do projeto,
- em [3], a única classe Java,
- em [4], os ficheiros de mensagens.
2.4.3. A página [index.xhtml]
O ficheiro [index.xhtml] [1] envia a página [2] para o navegador do cliente:
![]() |
O código que gera esta página é o seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
...
</head>
<body>
....
</body>
</f:view>
</html>
- linhas 7-9: os espaços de nomes / bibliotecas de tags utilizados pela página. As tags com o prefixo «h» são tags HTML, enquanto as tags com o prefixo «f» são específicas de JSF,
- linha 10: a baliza <f:view> serve para delimitar o código que o motor JSF deve processar, aquele onde aparecem as balizas <f:xx>. O atributo «locale» permite especificar um idioma de visualização para a página. Aqui, iremos utilizar dois: o inglês e o francês. O valor do atributo «local» é expresso sob a forma de uma expressão EL (Expression Language) #{expressão}. A forma da expressão pode variar. Na maioria das vezes, iremos expressá-la sob a forma «bean»['clé'] ou bean.champ. Nos nossos exemplos, bean será uma classe Java ou um ficheiro de mensagens. Com JSF 1, estes beans tinham de ser declarados no ficheiro [faces-config.xml]. Com JSF 2, isso já não é obrigatório para as classes Java. Agora é possível utilizar anotações que transformam uma classe Java num bean reconhecido pelo JSF 2. O ficheiro de mensagens, por sua vez, deve ser declarado no ficheiro de configuração [faces-config.xml].
2.4.4. O bean [changeLocale]
Na expressão EL #{changeLocale.locale}:
- changeLocale é o nome de um bean, neste caso a classe Java ChangeLocale,
- locale é um campo da classe ChangeLocale. A expressão é avaliada por [ChangeLocale].getLocale(). De um modo geral, a expressão #{bean.champ} é avaliada como [Bean].getChamp(), em que [Bean] é uma instância da classe Java à qual foram atribuídos os nomes bean e getChamp, o getter associado ao campo champ do bean.
A classe ChangeLocale é a seguinte:
package utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// a localização das páginas
private String locale="fr";
public ChangeLocale() {
}
...
public String getLocale() {
return locale;
}
}
- linha 11: o campo «local»,
- linha 17: o seu getter,
- linha 7: a anotação ManagedBean torna a classe Java ChangeLocale um bean reconhecido por JSF. Um bean é identificado por um nome. Este pode ser definido pelo atributo name da anotação: @ManagedBean(name= "xx "). Na ausência do atributo name, utiliza-se o nome da classe, convertendo o seu primeiro carácter em minúscula. O nome do bean ChangeLocale é, portanto, changeLocale. É importante ter em conta que a anotação ManagedBean pertence ao pacote javax.faces.bean.ManagedBean e não ao pacote javax.annotations.ManagedBean.
- linha 8: a anotação SessionScoped define o âmbito do bean. Existem vários. Utilizaremos habitualmente os três seguintes:
- RequestScoped: o tempo de vida do bean corresponde ao ciclo de pedido do navegador/resposta do servidor. Se, para processar um novo pedido do mesmo navegador ou de outro, este bean for novamente necessário, será instanciado de novo,
- SessionScoped: o tempo de vida do bean corresponde ao da sessão de um cliente específico. O bean é criado inicialmente para atender a uma das solicitações desse cliente. Permanecerá, em seguida, na memória durante a sessão desse cliente. Um bean deste tipo armazena, geralmente, dados específicos de um determinado cliente. Será destruído quando a sessão do cliente for encerrada,
- ApplicationScoped: a duração de vida do bean corresponde à da própria aplicação. Um bean com esta duração de vida é, na maioria das vezes, partilhado por todos os clientes da aplicação. É geralmente inicializado no início da aplicação.
Estas anotações existem em dois pacotes: javax.enterprise.context.SessionScoped (JSF 2) e javax.faces.bean.SessionScoped (JSF 1). Aqui, utilizamos o pacote JSF 2. Isto obriga-nos a criar o ficheiro [WEB-INF / beans.xml]:
![]() |
Este ficheiro é gerado automaticamente pelo NetBeans quando se importa o pacote [javax.enterprise.context.SessionScoped]. O seu conteúdo é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
Para além da baliza raiz <beans>, o ficheiro está vazio. Isso é suficiente. Basta apenas a sua presença.
Por fim, note-se que a classe [ChangeLocale] implementa a interface [Serializable]. Isto é obrigatório para os beans com âmbito Session, que o servidor web poderá ter de serializar em ficheiros. Voltaremos mais tarde ao bean [ChangeLocale].
2.4.5. O ficheiro de mensagens
Voltemos ao ficheiro [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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
...
</body>
</f:view>
</html>
- linha 8: a baliza <h:outputText> exibe o valor de uma expressão EL #{msg['welcome.titre']} da forma #{bean['champ']}. bean é o nome de uma classe Java ou de um ficheiro de mensagens. Neste caso, trata-se do nome de um ficheiro de mensagens. Este último deve ser declarado no ficheiro de configuração [faces-config.xml]. O bean msg é declarado da seguinte forma:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
- linhas 11-18: a baliza <application> serve para configurar a aplicação JSF,
- linhas 12-17: a baliza <resource-bundle> serve para definir recursos para a aplicação, neste caso um ficheiro de mensagens,
- linhas 13-15: a baliza <base-name> define o nome do ficheiro de mensagens,
- linha 14: o ficheiro chamar-se-á messages[_CodeLangue][_CodePays].properties. A baliza <base-name> define apenas a primeira parte do nome. O resto é implícito. Podem existir vários ficheiros de mensagens, um por idioma:
![]() |
- em [1], vemos quatro ficheiros de mensagens correspondentes ao nome base «messages» definido em [faces-config.xml],
- messages_fr.properties: contém as mensagens em francês (código fr);
- messages_en.properties: contém as mensagens em inglês (código en);
- messages_es_ES.properties: contém as mensagens em espanhol (código es) de Espanha (código ES). Existem outros tipos de espanhol, por exemplo, o da Bolívia (es_BO);
- messages.properties: é utilizado pelo servidor quando o idioma do computador em que este está a ser executado não tem nenhum ficheiro de mensagens associado. Seria utilizado, por exemplo, se a aplicação fosse executada num computador na Alemanha, onde o idioma predefinido seria o alemão (de). Como não existe nenhum ficheiro [messages_de.properties], a aplicação utilizaria o ficheiro [messages.properties],
- em [2]: os códigos dos idiomas seguem uma norma internacional,
- em [3]: o mesmo se aplica aos códigos dos países.
O nome do ficheiro de mensagens é definido na linha 14. Será procurado no ficheiro Classpath do projeto. Se estiver dentro de um pacote, este deve ser definido na linha 14, por exemplo, ressources.messages, se o ficheiro [messages.properties] se encontrar na pasta [ressources] do Classpath. Como o nome, na linha 14, não inclui nenhum pacote, o ficheiro [messages.properties] deve ser colocado na raiz da pasta [src / main / resources]:
![]() |
No [1], no separador [Projects] do projeto NetBeans, o ficheiro [messages.properties] é apresentado como uma lista das diferentes versões de mensagens definidas. As versões são identificadas por uma sequência de um a três códigos [codeLangue_codePays_codeVariante]. No [1], apenas o código [codeLangue] foi utilizado: «en» para o inglês e «fr» para o francês. Cada versão constitui um ficheiro separado no sistema de ficheiros.
No nosso exemplo, o ficheiro de mensagens em francês [messages_fr.properties] conterá os seguintes elementos:
welcome.titre=Tutoriel JSF (JavaServer Faces)
welcome.langue1=Fran\u00e7ais
welcome.langue2=Anglais
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Page d'accueil
Já o ficheiro [messages_en.properties] terá o seguinte conteúdo:
welcome.titre=JSF (JavaServer Faces) Tutorial
welcome.langue1=French
welcome.langue2=English
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Welcome page
O ficheiro [messages.properties] é idêntico ao ficheiro [messages_en.properties]. No final, o navegador do cliente poderá escolher entre páginas em francês e páginas em inglês.
Voltemos ao ficheiro [faces-config.xml], que declara o ficheiro de mensagens:
...
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
A linha 8 indica que uma linha do ficheiro de mensagens será referenciada pelo identificador «msg» nas páginas JSF. Este identificador é utilizado no ficheiro [index.xhtml] analisado:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
...
</body>
</f:view>
</html>
A baliza <h:outputText> da linha 8 irá apresentar o valor da mensagem (presença do identificador «msg») da chave welcome.titre. Esta mensagem é procurada e encontrada no ficheiro [messages.properties] do idioma ativo no momento. Por exemplo, para o francês:
welcome.titre=Tutoriel JSF (JavaServer Faces)
Uma mensagem tem o formato chave=valor. A linha 8 do ficheiro [index.xhtml] fica da seguinte forma após a avaliação da expressão #{msg['welcome.titre']}:
<title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>
Este mecanismo de ficheiros de mensagens permite alterar facilmente o idioma das páginas de um projeto JSF. Fala-se de internacionalização do projeto ou, mais frequentemente, da sua abreviatura i18n, porque a palavra «internacionalização» começa por i e termina por n e há 18 letras entre o i e o n.
2.4.6. O formulário
Vamos continuar a explorar o conteúdo do ficheiro [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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
- linhas 11-18: a baliza <h:form> introduz um formulário. Um formulário é geralmente constituído por:
- etiquetas de campos de introdução de dados (texto, botões de opção, caixas de seleção, listas de elementos, etc.);
- tags de validação do formulário (botões, links). É através de um botão ou de um link que o utilizador envia os seus dados para o servidor, que os processará,
Qualquer baliza JSF pode ser identificada por um atributo id. Na maioria das vezes, é possível prescindir dele, e foi isso que se fez na maioria das balizas JSF utilizadas aqui. No entanto, este atributo é útil em certos casos. Na linha 17, o formulário é identificado pelo id «formulário». Neste exemplo, o id do formulário não será utilizado e poderia ter sido omitido.
- linhas 18-21: a baliza <h:panelGrid> define aqui uma tabela HTML com duas colunas. Esta dá origem à baliza HTML <table>,
- o formulário dispõe de três links que acionam o seu processamento, nas linhas 19, 20 e 23. A baliza <h:commandLink> tem, pelo menos, dois atributos:
- value: o texto do link;
- action: ou uma cadeia de caracteres C, ou a referência a um método que, após a execução, devolve a cadeia de caracteres C. Esta cadeia de caracteres C pode ser:
- ou o nome de uma página JSF do projeto,
- ou um nome definido nas regras de navegação do ficheiro [faces-config.xml] e associado a uma página JSF do projeto;
Em ambos os casos, a página JSF é apresentada, assim que a ação definida pelo atributo action tiver sido executada.
Vamos analisar o funcionamento do processamento de formulários com o exemplo do link da linha 13:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>}"/>
Em primeiro lugar, o ficheiro de mensagens é utilizado para substituir a expressão #{msg['welcome.langue1']} pelo seu valor. Após a avaliação, a baliza passa a ser:
<h:commandLink value="Français" action="#{changeLocale.setFrenchLocale}"/>}"/>
A tradução HTML desta baliza JSF será a seguinte:
<a href="<a href="view-source:http://localhost:8080/mv-jsf2-02/faces/page1.xhtml#">#</a>" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),{'formulaire:j_idt8':'formulaire:j_idt8'},'');return false">Français</a>
o que resultará na seguinte apresentação visual:
![]() |
Deve-se notar o atributo onclick da baliza HTML <a>. Quando o utilizador clicar na ligação [Français], será executado código JavaScript. Este código está incorporado na página que o navegador recebeu e é o próprio navegador que o executa. O código JavaScript é amplamente utilizado nas tecnologias JSF e AJAX (Asynchronous JavaScript and XML). Em geral, tem como objetivo melhorar a usabilidade e a capacidade de resposta das aplicações web. Na maioria das vezes, é gerado automaticamente por ferramentas de software, pelo que não é necessário compreendê-lo. No entanto, por vezes, um programador pode ter de adicionar código JavaScript às suas páginas JSF. Nesses casos, o conhecimento de JavaScript é necessário.
Não é necessário, neste caso, compreender o código JavaScript gerado para a baliza JSF <h:commandLink>. No entanto, é possível destacar dois pontos:
- o código JavaScript utiliza o identificador de formulário que atribuímos à baliza JSF <h:form>,
- o JSF gera identificadores automáticos para todas as etiquetas em que o atributo id não foi definido. Vemos aqui um exemplo: j_idt8. Atribuir um identificador claro às etiquetas permite compreender melhor o código JavaScript gerado, caso isso se torne necessário. É nomeadamente o caso quando o programador tem de adicionar ele próprio código JavaScript que manipula os componentes da página. Nessa altura, precisa de conhecer os identificadores id dos seus componentes.
O que irá acontecer quando o utilizador clicar na ligação [Français] da página acima? Consideremos a arquitetura de uma aplicação JSF:
![]() |
O controlador [Faces Servlet] irá receber o pedido do navegador do cliente na seguinte forma: HTTP:
- linhas 1-2: o navegador solicita o URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. É sempre assim: os dados introduzidos num formulário JSF, inicialmente obtido com o URL URLFormulaire, são enviados para esse mesmo URL. O navegador dispõe de duas formas de enviar os valores introduzidos: GET e POST. Com o método GET, os valores introduzidos são enviados pelo navegador no URL que foi solicitado. No exemplo acima, o navegador poderia ter enviado a seguinte primeira linha:
GET /mv-jsf2-02/faces/index.xhtml?formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 HTTP/1.1
Com o método POST utilizado aqui, o navegador envia ao servidor os valores introduzidos através da linha 6.
- linha 3: indica o formato de codificação dos valores do formulário,
- linha 4: indica o tamanho em bytes da linha 6,
- linha 5: linha vazia que indica o fim dos cabeçalhos HTTP e o início dos 126 bytes dos valores do formulário,
- linha 6: os valores do formulário na forma element1=valor1&element2=valor2& ..., o formato de codificação definido pela linha 3. Neste formato de codificação, alguns caracteres são substituídos pelo seu valor hexadecimal. É o caso do último elemento:
formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_idt8=formulaire%3Aj_idt8
onde %3A representa o carácter :. É, portanto, a cadeia formulário:j_idt8=formulário:j_idt8 que é enviada para o servidor. Talvez nos lembremos de que já nos deparámos com o identificador j_idt8 quando analisámos o código HTML gerado para a baliza
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
Este tinha sido gerado automaticamente pelo JSF. O que nos interessa aqui é que a presença deste identificador na cadeia de valores enviados pelo navegador do cliente permite ao JSF saber que o link [Français] foi clicado. Em seguida, irá utilizar o atributo «action» acima referido para decidir como processar a cadeia recebida. O atributo action="#{changeLocale.setFrenchLocale}" indica ao JSF que o pedido do cliente deve ser processado pelo método [setFrenchLocale] de um objeto denominado changeLocale. Recorde-se que este bean foi definido por meio de anotações na classe Java [ChangeLocale]:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
O nome de um bean é definido pelo atributo «name» da anotação @ManagedBean. Na ausência deste atributo, é utilizado como nome do bean o nome da classe, com o primeiro caractere em minúscula.
Voltemos à solicitação do navegador:
![]() |
e à baliza <h:commandLink> que gerou o link [Français] no qual clicámos:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
O controlador irá transmitir a solicitação do navegador ao gestor de eventos definido pelo atributo action da tag <h:commandLink>. O gestor de eventos M referenciado pelo atributo action de um comando <h:commandLink> deve ter a seguinte assinatura:
- não recebe nenhum parâmetro. Veremos que, no entanto, pode ter acesso à solicitação do cliente,
- deve devolver um resultado C do tipo String. Esta cadeia de caracteres C pode ser:
- ou o nome de uma página JSF do projeto;
- ou um nome definido nas regras de navegação do ficheiro [faces-config.xml] e associado a uma página JSF do projeto;
- ou um ponteiro nulo, caso o navegador do cliente não deva mudar de página,
Na arquitetura JSF acima, o controlador [Faces Servlet] utilizará a cadeia C fornecida pelo gestor de eventos e, eventualmente, o seu ficheiro de configuração [faces-config.xml] para determinar qual a página JSF deve enviar em resposta ao cliente [4].
Na tag
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
o gestor do evento «clique no link» [Français] é o método [changeLocale.setFrenchLocale], em que changeLocale é uma instância da classe [utils.ChangeLocale] já analisada:
package utils;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// a configuração regional das páginas
private String locale="fr";
public ChangeLocale() {
}
public String setFrenchLocale(){
locale="fr";
return null;
}
public String setEnglishLocale(){
locale="en";
return null;
}
public String getLocale() {
return locale;
}
}
O método setFrenchLocale tem, de facto, a assinatura dos gestores de eventos. Recorde-se que o gestor de eventos deve processar o pedido do cliente. Uma vez que não recebe parâmetros, como pode ter acesso a esse pedido? Existem várias formas de o fazer:
- O bean B, que contém o gestor de eventos da página JSF P, é também, muitas vezes, aquele que contém o modelo M dessa página. Isto significa que o bean B contém campos que serão inicializados com os valores introduzidos na página P. Esta tarefa será realizada pelo controlador [Faces Servlet] antes de o gestor de eventos do bean B ser chamado. Assim, este gestor terá acesso, através dos campos do bean B ao qual pertence, aos valores introduzidos pelo cliente no formulário e poderá processá-los.
- O método estático [FacesContext.getCurrentInstance()], do tipo [FacesContext], dá acesso ao contexto de execução da requisição JSF atual, que é um objeto do tipo [FacesContext]. O contexto de execução da consulta assim obtido permite aceder aos parâmetros enviados ao servidor pelo navegador do cliente através do seguinte método:
Se os parâmetros enviados (POST) pelo navegador do cliente forem os seguintes:
o método getRequestParameterMap() irá devolver o seguinte dicionário:
chave | valor |
formulário | formulário |
javax.faces.ViewState | ... |
formulário:j_id_id21 | formulário:j_id_id21 |
Na baliza
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
o que se espera do gestor de eventos locale.setFrenchLocale? Pretende-se que ele defina o idioma utilizado pela aplicação. Na gíria Java, a isto chama-se «localizar» a aplicação. Esta localização é utilizada pela baliza <f:view> da página JSF [index.xhtml]:
<f:view locale="#{changeLocale.locale}">
...
</f:view>
Para mudar a página para francês, basta que o atributo locale tenha o valor fr. Para a mudar para inglês, é necessário atribuir-lhe o valor en. O valor do atributo **locale é obtido através da expressão *[ChangeLocale].getLocale()*. Esta expressão devolve o valor do campo locale da classe [ChangeLocale]. A partir daí, deduz-se o código do método [ChangeLocale].setFrenchLocale(), que deve apresentar as páginas em francês:
public String setFrenchLocale(){
locale="fr";
return null;
}
Já explicámos que um gestor de eventos deve devolver uma cadeia de caracteres C que será utilizada pelo [Faces Servlet] para localizar a página JSF a enviar como resposta ao navegador do cliente. Se a página a devolver for a mesma que a que está a ser processada, o gestor de eventos pode limitar-se a devolver o valor nulo. É isso que se faz aqui na linha 3: pretendemos devolver a mesma página [index.xhtml], mas num idioma diferente.
Voltemos à arquitetura de processamento do pedido:
![]() |
O gestor de eventos changeLocale.setFrenchLocale foi executado e devolveu o valor null ao controlador [Faces Servlet]. Este irá, portanto, voltar a apresentar a página [index.xhtml]. Vamos rever essa página:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
Sempre que um valor do tipo #{msg['...']} é avaliado, é utilizado um dos ficheiros de mensagens [messages.properties]. O ficheiro utilizado é aquele que corresponde à «localização» da página (linha 6). Como o gestor de eventos changeLocale.setFrenchLocale define esta localização como fr, será utilizado o ficheiro [messages_fr.properties]. Um clique na ligação [Anglais] (linha 14) alterará a localização para en (ver método changeLocale.setEnglishLocale). Será então utilizado o ficheiro [messages_en.properties] e a página aparecerá em inglês:
![]() | ![]() |
Sempre que a página [index.xhtml] for apresentada, a tag <f:view> é executada:
<f:view locale="#{changeLocale.locale}">
e, por conseguinte, o método [ChangeLocale].getLocale() é executado novamente. Como atribuímos o âmbito «Session» ao nosso bean:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
a localização efetuada durante um pedido é mantida para os pedidos seguintes.
Resta-nos analisar um último elemento da página [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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
A baliza <h:commandLink> da linha 17 tem um atributo «action» igual a uma cadeia de caracteres. Neste caso, não é chamado nenhum gestor de eventos para processar a página. Passamos imediatamente para a página [page1.xhtml]. Vamos analisar o funcionamento da aplicação neste caso de utilização:
![]() |
O utilizador clica na ligação [Page 1]. O formulário é enviado para o controlador [Faces Servlet]. Este reconhece, na solicitação que recebe, que a ligação [Page 1] foi clicada. Analisa a baliza correspondente:
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
Não existe nenhum gestor de eventos associado ao link. O controlador [Faces Servlet] passa imediatamente para a etapa [3] acima e apresenta a página [page1.xhtml]:
![]() | ![]() |
2.4.7. A página JSF [page1.xhtml]
A página [page1.xhtml] envia o seguinte fluxo para o navegador do cliente:
![]() |
O código que gera esta página é o seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['page1.titre']}"/></title>
</head>
<body>
<h1><h:outputText value="#{msg['page1.entete']}"/></h1>
<h:form>
<h:commandLink value="#{msg['page1.welcome']}" action="index"/>
</h:form>
</body>
</f:view>
</html>
Não há nada nesta página que não tenha já sido explicado. O leitor poderá estabelecer a correspondência entre o código JSF e a página enviada ao navegador do cliente. O link de regresso à página inicial:
<h:commandLink value="#{msg['page1.welcome']}" action="index"/>
fará com que seja apresentada a página [index.xhtml].
2.4.8. Execução do projeto
O nosso projeto está agora completo. Podemos compilá-lo (Clean and Build):
![]() |
- A compilação do projeto cria, no separador [Files], a pasta [target]. Nesta pasta, encontra-se o arquivo [mv-jsf2-02-1.0-SNAPSHOT.war] do projeto. É este arquivo que é implementado no servidor,
- em [WEB-INF / classes] e [2], encontram-se as classes compiladas da pasta [Source Packages] do projeto, bem como os ficheiros que se encontravam no ramo [Other Sources] — neste caso, os ficheiros de mensagens,
- em [WEB-INF / lib] [3], encontram-se as bibliotecas do projeto,
- na raiz de [WEB-INF] e [4], encontram-se os ficheiros de configuração do projeto,
![]() |
- na raiz do arquivo [5], encontram-se as páginas JSF que estavam no ramo [Web Pages] do projeto,
- assim que o projeto estiver compilado, pode ser executado [6]. Será executado de acordo com a sua configuração de execução [7],
- o servidor Tomcat será iniciado, caso ainda não estivesse em execução ([8]),
- o arquivo [mv-jsf2-02-1.0-SNAPSHOT.war] será carregado no servidor. A isto chama-se a implementação do projeto no servidor de aplicações,
- em [9], é solicitado que se inicie um navegador durante a execução. Este irá solicitar o contexto da aplicação [10], c.a.d. o URL [http://localhost:8080/mv-jsf2-02]. De acordo com as regras do ficheiro [web.xml] (ver página 44), é o ficheiro [faces/index.xhtml] que será servido ao navegador do cliente. Uma vez que o URL tem o formato [/faces/*], será processado pelo controlador [Faces Servlet] (ver [web.xml], página 44). Este irá processar a página e enviar o fluxo HTML seguinte:
![]() |
- O controlador [Faces Servlet] irá então processar os eventos que ocorrerão a partir desta página.
2.4.9. O ficheiro de configuração [faces-config.xml]
Utilizámos o seguinte ficheiro [faces-config.xml]:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
Este é o ficheiro mínimo para uma aplicação JSF 2 internacionalizada. Utilizámos aqui novas funcionalidades do JSF 2 em relação ao JSF 1:
- declarar beans e o seu âmbito com as anotações @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
- navegar entre páginas utilizando como chaves de navegação os nomes das páginas XHTML sem o sufixo xhtml.
Pode-se optar por não utilizar estas possibilidades e declarar estes elementos do projeto JSF em [faces-config.xml], tal como em JSF 1. Neste caso, o ficheiro [faces-config.xml] poderia ser o seguinte:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<!-- aplicação -->
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
<!-- beans geridos -->
<managed-bean>
<managed-bean-name>changeLocale</managed-bean-name>
<managed-bean-class>utils.ChangeLocale</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<!-- navegação -->
<navigation-rule>
<description/>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>p1</from-outcome>
<to-view-id>/page1.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<description/>
<from-view-id>/page1.xhtml</from-view-id>
<navigation-case>
<from-outcome>welcome</from-outcome>
<to-view-id>/index.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
- linhas 20-24: declaração do bean changeLocale:
- linha 21: nome do bean;
- linha 22: nome completo da classe associada ao bean;
- linha 23: âmbito do bean. Os valores possíveis são request, session, application,
- linhas 27-34: declaração de uma regra de navegação:
- linha 28: é possível descrever a regra. Neste caso, não o fizemos;
- linha 29: a página a partir da qual se navega (ponto de partida);
- linhas 30-33: um caso de navegação. Podem existir vários;
- linha 31: a chave de navegação;
- linha 32: a página para a qual se navega.
As regras de navegação podem ser apresentadas de forma mais visual. Ao editar o ficheiro [faces-config.xml], pode utilizar-se o separador [PageFlow]:
![]() |
Suponhamos que estamos a utilizar o ficheiro [faces-config.xml] anterior. Como evoluiria a nossa aplicação?
- Na classe [ChangeLocale], as anotações @ManagedBean e @SessionScoped desapareceriam, uma vez que, agora, o bean está declarado em [faces-config],
- A navegação de [index.xhtml] para [page1.xhtml] através de um link passaria a ser:
<h:commandLink value="#{msg['welcome.page1']}" action="p1"/>
Ao atributo «action», atribui-se a chave de navegação p1 definida em [faces-config],
- a navegação de [page1.xhtml] para [index.xhtml] através de um link passaria a ser:
<h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>
Ao atributo «action», atribui-se a chave de navegação «welcome» definida em [faces-config],
- os métodos setFrenchLocale e setEnglishLocale, que devem devolver uma chave de navegação, não precisam de ser alterados, uma vez que devolviam «null» para indicar que se permanecia na mesma página.
2.4.10. Conclusão
Voltemos ao projeto NetBeans que criámos:
![]() |
Este projeto segue a seguinte arquitetura:
![]() |
Em cada projeto JSF, encontraremos os seguintes elementos:
- páginas JSF [A] que são enviadas [4] aos navegadores dos clientes pelo controlador [Faces Servlet] [3],
- ficheiros de mensagens [C] que permitem alterar o idioma das páginas JSF,
- classes Java [B] que tratam os eventos que ocorrem no navegador do cliente [2a, 2b] e/ou que servem de modelos para as páginas JSF e [3]. Na maioria das vezes, as camadas [métier] e [DAO] são desenvolvidas e testadas separadamente. A camada [web] é então testada com uma camada [métier] fictícia. Se as camadas [métier] e [DAO] estiverem disponíveis, trabalha-se, na maioria das vezes, com os seus arquivos .jar.
- ficheiros de configuração [D] para ligar estes diversos elementos entre si. O ficheiro [web.xml] foi descrito na página 44 e raramente será alterado. O mesmo se aplica ao [faces-config], onde utilizaremos sempre a versão simplificada.
2.5. Exemplo mv-jsf2-03: formulário de introdução de dados - componentes JSF
A partir de agora, já não iremos mostrar a construção do projeto. Apresentamos projetos já prontos e explicamos o seu funcionamento. O leitor pode obter todos os exemplos no site deste documento (ver parágrafo 1.2).
2.5.1. A aplicação
A aplicação tem uma única vista:
![]() |
A aplicação apresenta os principais componentes JSF que podem ser utilizados num formulário de introdução de dados:
- a coluna [1] indica o nome da baliza JSF / HTML utilizada,
- a coluna [2] apresenta um exemplo de entrada para cada uma das tags encontradas,
- a coluna [3] exibe os valores do bean que serve de modelo para a página,
- as entradas efetuadas em [2] são validadas através do botão [4]. Esta validação limita-se a atualizar o bean modelo da página. A mesma página é, em seguida, reenviada. Assim, após a validação, a coluna [3] apresenta os novos valores do bean modelo, permitindo assim ao utilizador verificar o impacto das suas entradas no modelo da página.
2.5.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
- em [1], os ficheiros de configuração do projeto JSF,
- em [2], a única página do projeto: index.xhtml,
- em [3], uma folha de estilo [styles.css] para configurar o aspeto da página [index.xhtml]
- em [4], as classes Java do projeto,
- em [5], o ficheiro de mensagens da aplicação em duas línguas: francês e inglês.
2.5.3. O ficheiro [pom.xml]
Apresentamos apenas as dependências:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Estas são as dependências necessárias para um projeto JSF. Nos exemplos que se seguem, este ficheiro só será apresentado quando sofrer alterações.
2.5.4. O ficheiro [web.xml]
O ficheiro [web.xml] foi configurado para que a página [index.xhtml] seja a página inicial do projeto:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- linha 30: a página [index.xhtml] é a página inicial,
- linhas 11-14: um parâmetro para o servlet [Faces Servlet]. Este parâmetro determina que os comentários num facelet do tipo:
<!-- idiomas -->
sejam ignorados. Sem este parâmetro, os comentários causam problemas difíceis de compreender,
- linhas 3-6: um parâmetro para o servlet [Faces Servlet], que será explicado mais adiante.
2.5.5. O ficheiro [faces-config.xml]
O ficheiro [faces-config.xml] da aplicação é o seguinte:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
- linhas 11-16: configuram o ficheiro de mensagens da aplicação.
2.5.6. O ficheiro de mensagens [messages.properties]
Os ficheiros de mensagens (ver [5] na captura de ecrã do projeto) são os seguintes:
[messages_fr.properties]
form.langue1=Fran\u00e7ais
form.langue2=Anglais
form.titre=Java Server Faces - les tags
form.headerCol1=Type
form.headerCol2=Champs de saisie
form.headerCol3=Valeurs du modèle de la page
form.loginPrompt=login :
form.passwdPrompt=mot de passe :
form.descPrompt=description :
form.selectOneListBox1Prompt=choix unique :
form.selectOneListBox2Prompt=choix unique :
form.selectManyListBoxPrompt=choix multiple :
form.selectOneMenuPrompt=choix unique :
form.selectManyMenuPrompt=choix multiple :
form.selectBooleanCheckboxPrompt=marié(e) :
form.selectManyCheckboxPrompt=couleurs préférées :
form.selectOneRadioPrompt=moyen de transport préféré :
form.submitText=Valider
form.buttonRazText=Raz
Estas mensagens são apresentadas nos seguintes locais da página:
![]() |
A versão em inglês das mensagens é a seguinte:
[messages_en.properties]
form.langue1=French
form.langue2=English
form.titre=Java Server Faces - the tags
form.headerCol1=Input Type
form.headerCol2=Input Fields
form.headerCol3=Page Model Values
form.loginPrompt=login :
form.passwdPrompt=password :
form.descPrompt=description :
form.selectOneListBox1Prompt=unique choice :
form.selectOneListBox2Prompt=unique choice :
form.selectManyListBoxPrompt=multiple choice :
form.selectOneMenuPrompt=unique choice :
form.selectManyMenuPrompt=multiple choice :
form.selectBooleanCheckboxPrompt=married :
form.selectManyCheckboxPrompt=preferred colors :
form.selectOneRadioPrompt=preferred transport means :
form.submitText=Submit
form.buttonRazText=Reset
2.5.7. O modelo [Form.java] da página [index.xhtml]
![]() |
No projeto acima, a classe [Form.java] servirá de modelo ou backing bean para a página JSF [index.xhtml]. Vamos ilustrar este conceito de modelo com um exemplo retirado da página [index.xhtml]:
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
Na solicitação inicial da página [index.xhtml], o código acima gera a linha 2 da tabela de entradas:
![]() |
A linha 2 apresenta o campo [1]; as linhas 3 a 6, o campo [2]; e a linha 7, o campo [3].
As linhas 5 e 7 utilizam uma expressão EL que envolve o bean de formulário definido na classe [Form.java] da seguinte forma:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
- A linha 7 define um bean sem nome. Este será, portanto, o nome da classe que começa por uma minúscula: form,
- o bean tem âmbito de pedido. Isto significa que, num ciclo de pedido do cliente/resposta do servidor, é instanciado quando o pedido o necessita e eliminado quando a resposta ao cliente for entregue.
No código abaixo da página [index.xhtml]:
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
as linhas 5 e 7 utilizam o valor inputText do bean «form». Para compreender as ligações que unem uma página P ao seu modelo M, é necessário voltar ao ciclo de pedido do cliente / resposta do servidor que caracteriza uma aplicação web:
![]() |
É necessário distinguir o caso em que a página P é enviada como resposta ao navegador (etapa 4), por exemplo, aquando do pedido inicial da página, do caso em que o utilizador, ao provocar um evento na página P, este é tratado pelo controlador [Faces Servlet] (etapa 1).
É possível distinguir estes dois casos observando-os do ponto de vista do navegador:
- durante o pedido inicial da página, o navegador executa uma operação GET sobre o URL da página,
- ao enviar os valores introduzidos na página, o navegador executa uma operação POST sobre o URL da página.
Em ambos os casos, é solicitada a mesma URL. Dependendo da natureza do pedido GET ou POST do navegador, o tratamento do pedido irá diferir.
[cas 1 – demande initiale de la page P]
O navegador solicita o URL da página com um GET. O controlador [Faces Servlet] passará diretamente para a etapa [4] de renderização da resposta e a página [index.xhtml] será enviada ao cliente. O controlador JSF irá solicitar que cada tag da página seja exibida. Tomemos como exemplo a linha 5 do código de [index.xhtml]:
<h:inputText id="inputText" value="#{form.inputText}"/>
A baliza JSF <h:inputText value="valor"/> dá origem à baliza HTML <input type="text" value="valor"/>. A classe responsável por processar esta baliza encontra a expressão #{form.inputText}, que deve avaliar:
- se o bean «form» ainda não existir, é criado através da instanciação da classe forms.Form,
- a expressão #{form.inputText} é avaliada através da chamada ao método form.getInputText(),
- o texto <input id="formulário:inputText" type="text" name="formulaire:inputText" value="texto" /> é inserido no fluxo HTML, que será enviado ao cliente, supondo que o método form.getInputText() tenha devolvido a cadeia «texto». Além disso, o JSF atribui um nome (name) ao componente HTML incluído no fluxo. Este nome é construído a partir dos identificadores id do componente JSF analisado e dos seus componentes pai, neste caso a baliza <h:form id="formulaire"/>.
É importante notar que, se numa página P for utilizada a expressão #{M.champ}, em que M é o bean modelo da página P, este deve dispor do método público getChamp(). O tipo devolvido por este método deve poder ser convertido para o tipo String. Um modelo M possível e frequente é o seguinte:
onde T é um tipo que pode ser convertido no tipo String, eventualmente através do método toString.
Ainda no caso da visualização da página P, o processamento da linha:
<h:outputText value="#{form.inputText}"/>
será semelhante e será criado o seguinte fluxo HTML:
Internamente no servidor, a página P é representada como uma árvore de componentes, espelho da árvore de tags da página enviada ao cliente. Chamaremos a esta árvore de «vista» ou « » estado da página. Este estado é memorizado. Pode sê-lo de duas formas, dependendo de uma configuração definida no ficheiro [web.xml] da aplicação:
<web-app ...>
...
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
...
</web-app>
As linhas 7-11 definem o controlador [Faces Servlet]. Este pode ser configurado através de várias tags <context-param>, incluindo a das linhas 3-6, que indica que o estado de uma página deve ser guardado no cliente (o navegador). O outro valor possível, na linha 5, é «server», para indicar um armazenamento no servidor. Este é o valor predefinido.
Quando o estado de uma página é guardado no cliente, o controlador JSF adiciona a cada página HTML que envia um campo oculto cujo valor é o estado atual da página. Este campo oculto tem o seguinte formato:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />
O seu valor representa, de forma codificada, o estado da página enviada ao cliente. É importante compreender que este campo oculto faz parte do formulário da página e, por conseguinte, fará parte dos valores enviados pelo navegador aquando da validação do formulário. A partir deste campo oculto, o controlador JSF consegue restaurar a vista tal como foi enviada ao cliente.
Quando o estado de uma página é guardado no servidor, o estado da página enviada ao cliente é guardado na sessão deste último. Quando o navegador do cliente enviar os valores introduzidos no formulário, enviará também o seu token de sessão. A partir desse token, o controlador JSF recuperará o estado da página enviada ao cliente e irá restaurá-la.
O estado de uma página JSF pode exigir várias centenas de octetos para ser codificado. Como este estado é mantido para cada utilizador da aplicação, podem surgir problemas de memória se houver um grande número de utilizadores. Por este motivo, optámos aqui por guardar o estado da página no cliente (ver [web.xml], parágrafo 2.5.4, página 66).
[cas 2 – traitement de la page P]
![]() |
Estamos na etapa [1] acima, em que o controlador [Faces Servlet] irá receber um pedido POST do navegador do cliente, ao qual enviou anteriormente a página [index.xhtml]. Estamos perante o processamento de um evento da página. Várias etapas irão decorrer antes mesmo de o evento poder ser processado em [2a]. O ciclo de processamento de um pedido POST pelo controlador JSF é o seguinte:
FEDCBA

- em [A], graças ao campo oculto javax.faces.ViewState, a vista inicialmente enviada para o navegador do cliente é reconstituída. Aqui, os componentes da página recuperam o valor que tinham na página enviada. O nosso componente inputText recupera o seu valor «texto»,
- em [B], os valores enviados pelo navegador do cliente são utilizados para atualizar os componentes da vista. Assim, se no campo de introdução de dados HTML, denominado inputText, o utilizador tiver digitado «jean», o valor «jean» substitui o valor «texto». A partir de agora, a vista reflete a página tal como foi alterada pelo utilizador e não mais tal como foi enviada para o navegador,
- em [C], os valores enviados são verificados. Suponhamos que o componente inputText anterior seja o campo de introdução de uma idade. O valor introduzido terá de ser um número inteiro. Os valores enviados pelo navegador são sempre do tipo String. O seu tipo final no modelo M associado à página P pode ser qualquer outro. Verifica-se, então, uma conversão de um tipo String para outro tipo T. Esta conversão pode falhar. Nesse caso, o ciclo de pedido/resposta é concluído e a página P construída em [B] é devolvida ao navegador do cliente com mensagens de erro, caso o autor da página P as tenha previsto. Note-se que o utilizador vê a página tal como a introduziu, sem qualquer esforço por parte do programador. Noutra tecnologia, como a JSP, o programador tem de reconstruir ele próprio a página P com os valores introduzidos pelo utilizador. O valor de um componente pode também ser submetido a um processo de validação. Ainda com o exemplo do componente inputText, que é o campo de introdução da idade, o valor introduzido deverá ser não só um número inteiro, mas um número inteiro compreendido num intervalo [1,N]. Se o valor introduzido passar pela etapa de conversão, pode não passar pela etapa de validação. Nesse caso, o ciclo de pedido/resposta também está concluído e a página P, construída em [B], é devolvida ao navegador do cliente,
- No [D], se todos os componentes da página P passarem pela etapa de conversão e validação, os seus valores serão atribuídos ao modelo M da página P. Se o valor do campo de introdução de dados gerado a partir da seguinte baliza:
<h:inputText value="#{form.inputText}"/>
for «jean», então esse valor será atribuído ao modelo de formulário da página através da execução do código form.setInputText("jean"). É importante notar que, no modelo M da página P, os campos privados de M que armazenam o valor de um campo de entrada de P devem ter um método set,
- assim que o modelo M da página P for atualizado com os valores enviados, o evento que provocou o POST da página P pode ser processado. Esta é a etapa [E]. Note-se que, se o gestor deste evento pertencer ao bean M, este tem acesso aos valores do formulário P que foram armazenados nos campos desse mesmo bean.
- A etapa [E] irá devolver ao controlador JSF uma chave de navegação. Nos nossos exemplos, será sempre o nome da página XHTML a ser apresentada, sem o sufixo .xhtml. Trata-se da etapa [F]. Outra forma de proceder consiste em devolver uma chave de navegação que será procurada no ficheiro [faces-config.xml]. Já descrevemos este caso.
Do exposto, retemos que:
- uma página P apresenta os campos C do seu modelo M através dos métodos [M].getC(),
- os campos C do modelo M de uma página P são inicializados com os valores introduzidos na página P através dos métodos [M].setC(introdução). Nesta etapa, podem ocorrer processos de conversão e validação que podem falhar. Nesse caso, o evento que provocou o POST da página P não é processado e a página é reenviada ao cliente tal como este a introduziu.
O modelo [Form.java] da página [index.xhtml] será o seguinte:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
/** Cria uma nova instância do formulário */
public Form() {
}
// campos do formulário
private String inputText="texte";
private String inputSecret="secret";
private String inputTextArea="ligne1\nligne2\n";
private String selectOneListBox1="2";
private String selectOneListBox2="3";
private String[] selectManyListBox=new String[]{"1","3"};
private String selectOneMenu="1";
private String[] selectManyMenu=new String[]{"1","2"};
private String inputHidden="initial";
private boolean selectBooleanCheckbox=true;
private String[] selectManyCheckbox=new String[]{"1","3"};
private String selectOneRadio="2";
// eventos
public String submit(){
return null;
}
// getters e setters
...
}
Os campos das linhas 16 a 27 são utilizados nos seguintes locais do formulário:
![]() |
2.5.8. A página [index.xhtml]
A página [index.xhtml], que gera a vista anterior, é a seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- idiomas -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
<!-- cabeçalhos -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
<!-- linha 1 -->
...
<!-- linha 2 -->
...
<!-- linha 3 -->
...
<!-- linha 4 -->
...
<!-- linha 5 -->
...
<!-- linha 6 -->
...
<!-- linha 7 -->
...
<!-- linha 8 -->
...
<!-- linha 9 -->
...
<!-- linha 10 -->
...
<!-- linha 11 -->
...
<!-- linha 12 -->
...
</h:panelGrid>
<p>
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
</p>
</h:form>
</h:body>
</f:view>
</html>
Vamos analisar sucessivamente os principais componentes desta página. De notar a estrutura geral de um formulário JSF:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view ...>
<h:head>
...
</h:head>
<h:body ...>
<h:form id="formulaire">
...
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
...
</h:form>
</h:body>
</f:view>
</html>
Os componentes de um formulário devem estar dentro de uma baliza <h:form> (linhas 12-16). A baliza <f:view> (linhas 7-18) é necessária se a aplicação for internacionalizada. Além disso, um formulário deve dispor de um meio para ser enviado (POST), frequentemente um link ou um botão, como na linha 14. Também pode ser enviado por diversos eventos (alteração de uma seleção numa lista, mudança do campo ativo, introdução de um caractere num campo de entrada, etc.).
2.5.9. O estilo do formulário
Para tornar as colunas da tabela do formulário mais legíveis, este é acompanhado por uma folha de estilo:
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
- linha 4: a folha de estilo da página é definida dentro da baliza HTML <head>, através da baliza:
<h:outputStylesheet library="css" name="styles.css"/>
A folha de estilo será procurada na pasta [resources]:
![]() |
Na baliza:
<h:outputStylesheet library="css" name="styles.css"/>
- library é o nome da pasta que contém a folha de estilo,
- «name» é o nome da folha de estilo.
Vejamos um exemplo de utilização desta folha de estilo:
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
A baliza <h:panelGrid columns="3"/> define uma tabela com três colunas. O atributo columnClasses permite aplicar um estilo a essas colunas. Os valores col1, col2, col3 do atributo columnClasses designam os estilos respetivos das colunas 1, 2 e 3 da tabela. Estes estilos são procurados na folha de estilo da página:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
- linhas 7-9: o estilo denominado col1,
- linhas 11-13: o estilo denominado col2,
- linhas 15-17: o estilo denominado col3,
Estes três estilos definem a cor de fundo de cada uma das colunas.
- linhas 19-23: o estilo «entete» serve para definir o estilo dos textos da primeira linha da tabela:
<!-- cabeçalhos -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
- linhas 1-5: o estilo «info» serve para definir o estilo dos textos da primeira coluna da tabela:
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
Não nos deteremos muito na utilização das folhas de estilo, uma vez que estas merecem, por si só, um livro e que, além disso, a sua elaboração é frequentemente confiada a especialistas. No entanto, quisemos utilizar uma, minimalista, para recordar que a sua utilização é indispensável.
Vejamos agora como foi definida a imagem de fundo da página:
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
A imagem de fundo é definida pelo atributo «style» da baliza <h:body>. Este atributo permite definir elementos de estilo. A imagem de fundo encontra-se na pasta [resources/images/standard.jpg]:
![]() |
Esta imagem é obtida através do URL [/mv-jsf2-03/resources/images/standard.jpg]. Assim, poderíamos escrever:
<h:body style="background-image: url('mv-jsf2-03/resources/images/standard.jpg');">
/mv-jsf2-03 é o contexto da aplicação. Este contexto é definido pelo administrador do servidor web e, por isso, pode mudar. Este contexto pode ser obtido através da expressão EL ${request.contextPath}. Por isso, é preferível utilizar o seguinte atributo style:
style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"
que será válido independentemente do contexto.
2.5.10. Os dois ciclos de pedido do cliente / resposta do servidor de um formulário
Voltemos ao que já foi explicado no parágrafo 2.5.7 num caso geral e apliquemo-lo ao formulário em análise. Este será testado no ambiente clássico JSF:
![]() |
Aqui, não haverá gestores de eventos nem camada [métier]. As etapas [2x] não existirão, portanto. Distinguiremos o caso em que o formulário F é solicitado inicialmente pelo navegador do caso em que o utilizador, tendo provocado um evento no formulário F, este é tratado pelo controlador [Faces Servlet]. Existem dois ciclos de pedido do cliente/resposta do servidor que são diferentes.
- o primeiro, correspondente ao pedido inicial da página, é desencadeado por uma operação GET do navegador sobre o URL do formulário,
- o segundo, correspondente ao envio dos valores introduzidos na página, é desencadeado por uma operação POST sobre esse mesmo URL.
Dependendo da natureza do pedido GET ou POST do navegador, o tratamento do pedido pelo controlador [Faces Servlet] difere.
[cas 1 – demande initiale du formulaire F]
O navegador solicita o URL da página com um GET. O controlador [Faces Servlet] passará diretamente para a etapa [4] de renderização da resposta. O formulário [index.xhtml] será inicializado pelo seu modelo [Form.java] e enviado ao cliente, que receberá a seguinte visualização:

As trocas de dados HTTP entre o cliente e o servidor são as seguintes nesta ocasião:
Pedido HTTP do cliente:
Na linha 1, vê-se o GET do navegador.
Resposta HTTP do servidor:
Embora não seja mostrado aqui, a linha 7 é seguida por uma linha em branco e pelo código HTML do formulário. É este código que o navegador interpreta e apresenta.
[cas 2 – traitement des valeurs saisies dans le formulaire F]
O utilizador preenche o formulário e submete-o através do botão [Valider]. O navegador solicita então o URL do formulário com um POST. O controlador [Faces Servlet] processa este pedido, atualiza o modelo [Form.java] do formulário [index.xhtml] e reenvia o formulário [index.xhtml] atualizado com este novo modelo. Vamos analisar este ciclo com um exemplo:

No exemplo acima, o utilizador introduziu os seus dados e validou-os. Recebe como resposta a seguinte visualização:

As trocas de dados entre o cliente e o servidor HTTP são as seguintes nesta ocasião:
Pedido HTTP do cliente:
Na linha 1, o POST gerado pelo navegador. Na linha 14, os valores introduzidos pelo utilizador. É possível, por exemplo, ver o texto introduzido no campo de entrada:
Na linha 14, o campo oculto javax.faces.ViewState foi enviado. Este campo representa, de forma codificada, o estado do formulário tal como foi inicialmente enviado ao navegador durante o seu GET inicial.
Resposta HTTP do servidor:
Embora não seja aqui apresentada, a linha 6 é seguida por uma linha em branco e pelo código HTML do formulário atualizado pelo seu novo modelo, resultante do POST.
Vamos agora analisar os diferentes componentes deste formulário.
2.5.11. Etiqueta <h:inputText>
A baliza <h:inputText> gera uma baliza HTML <input type="text" ...>.
Consideremos o seguinte código:
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
e o seu modelo [Form.java]:
private String inputText="texte";
public String getInputText() {
return inputText;
}
public void setInputText(String inputText) {
this.inputText = inputText;
}
Quando a página [index.html] é solicitada pela primeira vez, a página obtida é a seguinte:
- a linha 2 do código XHTML gera [1],
- a baliza <h:panelGroup> (linhas 3-6) permite agrupar vários elementos numa mesma célula da tabela gerada pela baliza <h:panelGrid> da linha 20 do código completo da página (ver parágrafo 2.5.8). O texto [2] é gerado pela linha 4. O campo de introdução de dados [3] é gerado pela linha [5]. Aqui, o método getInputText de [Form.java] (linhas 3-5 do código Java) foi utilizado para gerar o texto do campo de introdução de dados,
- a linha 7 do código XHTML gera [4]. Mais uma vez, é o método getInputText de [Form.java] que é utilizado para gerar o texto [4].
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">inputText</span></td>
<td class="col2">login : <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /></td>
<td class="col3">texte</td>
</tr>
As balizas HTML <tr> e <td> são geradas pela baliza <h:panelGrid> utilizada para gerar a tabela do formulário.
Agora, abaixo, introduzamos um valor no campo de entrada [1] e validemos o formulário com o botão [Valider] [2]. Obtemos como resposta a página [3, 4]:
![]() |
O valor do campo [1] é enviado da seguinte forma:
Em [2], o formulário é validado com o seguinte botão:
<h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>
A baliza <h:commandButton> não possui o atributo «action». Neste caso, não é invocado nenhum gestor de eventos nem aplicada nenhuma regra de navegação. Após o processamento, é devolvida a mesma página. Vejamos novamente o seu ciclo de processamento:
ABCDEF

- em [A], a página P é restaurada tal como tinha sido enviada. Isto significa que o componente com o id inputText é restaurado com o seu valor inicial «texto»,
- em [B], os valores enviados pelo navegador (introduzidos pelo utilizador) são atribuídos aos componentes da página P. Aqui, o componente com o ID inputText recebe o valor «um novo texto»,
- em [C], realizam-se as conversões e validações. Aqui, não há nenhuma. No modelo M, o campo associado ao componente com o id inputText é o seguinte:
private String inputText="texte";
Como os valores introduzidos são do tipo String, não há conversão a efetuar. Além disso, não foi criada nenhuma regra de validação. Iremos criá-las posteriormente.
- Em [D], os valores introduzidos são atribuídos ao modelo. O campo inputText de [Form.java] recebe o valor «um novo texto»,
- em [E], nada acontece, pois não foi associado nenhum gestor de eventos ao botão [Valider].
- em [F], a página P é novamente enviada ao cliente, uma vez que o botão [Valider] não possui um atributo «action». São então executadas as seguintes linhas de [index.xhtml]:
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
As linhas 5 e 7 utilizam o valor do campo inputText do modelo, que agora é «um novo texto». Daí a apresentação obtida:
![]()
2.5.12. Tag <h:inputSecret>
A baliza <h:inputSecret> gera uma baliza HTML <input type="password" ...>. Trata-se de um campo de introdução de dados semelhante ao da baliza JSF <h:inputText>, com a diferença de que cada caractere digitado pelo utilizador é substituído visualmente por um asterisco (*).
Consideremos o seguinte código:
<!-- linha 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<h:outputText value="#{form.inputSecret}"/>
e o seu modelo em [Form.java]:
private String inputSecret="secret";
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
- a linha 2 do código XHTML gera [1]
- o texto [2] é gerado pela linha 4. O campo de introdução de dados [3] é gerado pela linha [5]. Normalmente, o método getInputSecret de [Form.java] deveria ter sido utilizado para gerar o texto do campo de introdução de dados. Existe uma exceção quando este é do tipo «palavra-passe». A baliza <h:inputSecret> serve apenas para ler uma entrada, não para a apresentar.
- A linha 7 do código XHTML gera [4]. Aqui, o método getInputSecret de [Form.java] foi utilizado para gerar o texto [4] (ver linha 1 do código Java).
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">inputSecret</span></td>
<td class="col2">mot de passe : <input id="formulaire:inputSecret" type="password" name="formulaire:inputSecret" value="" /></td>
<td class="col3">secret</td>
</tr>
- linha 3: a baliza HTML <input type= "password " .../> gerada pela baliza JSF <h:inputSecret>
Agora, abaixo, introduzamos um valor no campo de entrada [1] e validemos o formulário com o botão [Valider] [2]. Obtemos como resposta a página [3]:
![]() |
O valor do campo [1] é enviado da seguinte forma:
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. O campo inputSecret de [Form.java] recebeu então o valor «mdp». Como o formulário [index.xhtml] não definiu nenhuma regra de navegação nem nenhum gestor de eventos, é exibido novamente após a atualização do seu modelo. Voltamos, assim, à visualização resultante do pedido inicial da página [index.xhtml], onde apenas o campo inputSecret do modelo mudou de valor para [3].
2.5.13. Etiqueta <h:inputTextArea>
A baliza <h:inputTextArea> gera uma baliza HTML <textarea ...>texto</textarea>. Trata-se de um campo de introdução de texto semelhante ao da baliza JSF <h:inputText>, com a diferença de que, neste caso, é possível escrever várias linhas de texto.
Consideremos o seguinte código:
<!-- linha 3 -->
<h:outputText value="inputTextArea" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.descPrompt']}"/>
<h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
</h:panelGroup>
<h:outputText value="#{form.inputTextArea}"/>
e o seu modelo em [Form.java]:
private String inputTextArea="ligne1\nligne2\n";
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1],
- o texto [2] é gerado pela linha 4. O campo de introdução de dados [3] é gerado pela linha [5]. O seu conteúdo foi gerado através da chamada ao método getInputTextArea do modelo, que devolveu o valor definido na linha 1 do código Java acima,
- a linha 7 do código XHTML gera [4]. Aqui, o método getInputTextArea de [Form.java] foi novamente utilizado. A cadeia «linha1\nlinha2» continha saltos de linha \n. Estes continuam presentes. No entanto, quando inseridos num fluxo HTML, são apresentados como espaços pelos navegadores. A baliza HTML <textarea>, que apresenta [3], interpreta corretamente as quebras de linha.
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">inputTextArea</span></td>
<td class="col2">description : <textarea id="formulaire:inputTextArea" name="formulaire:inputTextArea" rows="4">ligne1
ligne2
</textarea></td>
<td class="col3">ligne1
ligne2
</td>
</tr>
- linhas 3-5: a baliza HTML <textarea>...</textarea>, gerada pela baliza JSF <h:inputTextArea>
Agora, abaixo, introduzamos um valor no campo de entrada [1] e validemos o formulário com o botão [Valider] [2]. Obtemos como resposta a página [3]:
![]() |
O valor do campo [1] enviado é o seguinte:
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. O campo textArea de [Form.java] recebeu então o valor «Tutorial JSF\nparte1». A nova visualização de [index.xhtml] mostra que o campo textArea do modelo foi efetivamente atualizado para [3].
2.5.14. Etiqueta <h:selectOneListBox>
A baliza <h:selectOneListBox> gera uma baliza HTML <select>...</select>. Visualmente, gera uma lista suspensa ou uma lista com barra de rolagem.
Consideremos o seguinte código:
<!-- linha 4 -->
<h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox1}"/>
e o seu modelo em [Form.java]:
private String selectOneListBox1="2";
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1]
- o texto [2] é gerado pela linha 4. A lista suspensa [3] é gerada pelas linhas [5-9]. É o valor do atributo size="1" que faz com que a lista apresente apenas um elemento. Se este atributo estiver em falta, o valor por predefinição do atributo size é 1. Os elementos da lista foram gerados pelas balizas <f:selectItem> das linhas 6 a 8. Estas balizas têm a seguinte sintaxe:
<f:selectItem itemValue="valeur" itemLabel="texte"/>
O valor do atributo itemLabel é o que é apresentado na lista. O valor do atributo itemValue é o valor do elemento. É este valor que será enviado ao controlador [Faces Servlet] se o elemento for selecionado na lista suspensa.
O elemento apresentado em [3] foi determinado através da chamada ao método getSelectOneListBox1() (linha 5). O resultado «2» obtido (linha 1 do código Java) fez com que o elemento da linha 7 da lista suspensa fosse apresentado, uma vez que o seu atributo itemValue tem o valor «2»,
- a linha 11 do código XHTML gera [4]. Aqui, o método getSelectOneListBox1 de [Form.java] foi novamente utilizado.
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">selectOneListBox (size=1)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox1" name="formulaire:selectOneListBox1" size="1">
<option value="1">un</option>
<option value="2" selected="selected">deux</option>
<option value="3">trois</option>
</select></td>
<td class="col3">2</td>
</tr>
- linhas 3 e 7: a baliza HTML <select ...>...</select> gerada pela baliza JSF <h:selectOneListBox>,
- linhas 4-6: as balizas HTML <option ...> ... </option> geradas pelas balizas JSF <f:selectItem>,
- linha 5: o facto de o elemento com value="2" estar selecionado na lista traduz-se na presença do atributo selected="selected".
Agora, a seguir, vamos escolher [1] um novo valor na lista e submeter o formulário com o botão [Valider] [2]. Obtemos como resposta a página [3]:
![]() |
O valor do campo [1] enviado é o seguinte:
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. O elemento HTML
<option value="3">trois</option>
foi selecionado. O navegador enviou a cadeia «3» como valor do componente JSF, o que gerou a lista suspensa:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
O controlador JSF irá utilizar o método setSelectOneListBox1("3") para atualizar o modelo da lista suspensa. Assim, após esta atualização, o campo do modelo [Form.java]
private String selectOneListBox1;
passa a conter o valor «3».
Quando a página [index.xhtml] é novamente apresentada após o seu processamento, este valor faz com que seja exibido o [3,4] acima:
- determina o elemento da lista suspensa que deve ser apresentado ([3]),
- o valor do campo selectOneListBox1 é apresentado em [4].
Consideremos uma variante da baliza <h:selectOneListBox>:
<!-- linha 5 -->
<h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox2}"/>
O modelo em [Form.java] da baliza <h:selectOneListBox> da linha 5 é o seguinte:
private String selectOneListBox2="3";
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1],
- o texto [2] é gerado pela linha 4. A lista com barra de deslocamento [3] é gerada pelas linhas [5-11]. É o valor do atributo size= "3 " que faz com que tenhamos uma lista com barra de deslocamento em vez de uma lista suspensa. Os elementos da lista foram gerados pelas balizas <f:selectItem> das linhas 6-8,
O elemento selecionado em [3] foi determinado através da chamada ao método getSelectOneListBox2() (linha 5). O resultado «3» obtido (linha 1 do código Java) fez com que o elemento da linha 8 da lista fosse apresentado, uma vez que o seu atributo itemValue tem o valor «3»,
- a linha 13 do código XHTML gera [4]. Aqui, o método getSelectOneListBox2 de [Form.java] foi novamente utilizado.
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">selectOneListBox (size=3)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox2" name="formulaire:selectOneListBox2" size="3">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3" selected="selected">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select></td>
<td class="col3">3</td>
</tr>
- linha 6: o facto de o elemento com value="3" estar selecionado na lista traduz-se na presença do atributo selected="selected".
Agora, a seguir, selecionemos um novo valor na lista e validemos o formulário com o botão. Obtemos como resposta a página:
![]() |
O valor enviado para o campo [1] é o seguinte:
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. O elemento HTML
<option value="5">cinq</option>
foi selecionado. O navegador enviou a cadeia «5» como valor do componente JSF, o que gerou a lista suspensa:
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
O controlador JSF irá utilizar o método setSelectOneListBox2("5") para atualizar o modelo da lista. Assim, após esta atualização, o campo
private String selectOneListBox2;
passa a conter o valor «5».
Quando a página [index.xhtml] é novamente apresentada após o seu processamento, este valor dá origem à apresentação [3,4] acima:
- determina o elemento da lista que deve ser selecionado ([3]),
- o valor do campo selectOneListBox2 é apresentado em [4].
2.5.15. Etiqueta <h:selectManyListBox>
A baliza <h:selectmanyListBox> gera uma baliza <select multiple="multiple">...</select> que permite ao utilizador selecionar vários elementos numa lista.
Consideremos o seguinte código:
<!-- linha 6 -->
<h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectManyListbox>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyListBoxValue}"/>
e o seu modelo em [Form.java]:
private String[] selectManyListBox=new String[]{"1","3"};
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1]
- o texto [2] é gerado pela linha 4. A lista [3] é gerada pelas linhas [5-11]. O atributo size="3" faz com que a lista exiba, num determinado momento, três desses elementos. Os elementos selecionados na lista foram determinados através da chamada ao método getSelectManyListBox() (linha 5) do modelo Java. O resultado {"1","3"} obtido (linha 1 do código Java) é um array de elementos do tipo String. Cada um destes elementos serve para selecionar um dos elementos da lista. Neste caso, serão selecionados os elementos das linhas 6 e 10 cujo atributo itemValue consta na matriz {"1","3"}. É isso que mostra [3].
- A linha 14 do código XHTML gera [4]. Aqui, não é chamado o método getSelectManyListBox do modelo Java da lista, mas sim o seguinte método getSelectManyListBoxValue:
private String[] selectManyListBox=new String[]{"1","3"};
...
// getters e setters
public String getSelectManyListBoxValue(){
return getValue(selectManyListBox);
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
Se tivéssemos chamado o método getSelectManyListBox, teríamos obtido um array de String. Para incluir este elemento no fluxo HTML, o controlador teria de chamar o seu método toString. No entanto, este método, quando aplicado a um array, limita-se a devolver o «hashcode» do mesmo e não a lista dos seus elementos, como pretendemos. Por isso, utilizamos o método getSelectManyListBoxValue acima referido para obter uma cadeia de caracteres que represente o conteúdo do array,
- a linha 12 do código XHTML gera o botão [5]. Quando se clica neste botão, o código JavaScript do atributo onclick é executado. Este será incorporado na página HTML, que será gerada pelo código JSF. Para compreender isto, precisamos de conhecer a natureza exata desta página.
O fluxo HTML gerado pela página XHTML é o seguinte:
<tr>
<td class="col1"><span class="info">selectManyListBox (size=3)</span></td>
<td class="col2">choix multiple : <select id="formulaire:selectManyListBox" name="formulaire:selectManyListBox" multiple="multiple" size="3">
<option value="1" selected="selected">un</option>
<option value="2">deux</option>
<option value="3" selected="selected">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select>
<p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</td>
<td class="col3">[ 1 3]</td>
</tr>
- linhas 3 e 9: a baliza HTML <select multiple="multiple"...>...</select> gerada pela baliza JSF <h:selectManyListBox>. É a presença do atributo «multiple» que indica que se trata de uma lista de seleção múltipla,
- o facto de o modelo da lista ser o array de String {"1","3"} faz com que os elementos da lista nas linhas 4 (value="1") e 6 (value="3") tenham o atributo selected="selected",
- linha 10: quando se clica no botão [Raz], o código JavaScript do atributo onclick é executado. A página é representada no navegador por uma árvore de objetos frequentemente designada por DOM (Document Object Model). Cada objeto da árvore é acessível ao código JavaScript através do seu atributo name. A lista da linha 3 do código HTML acima é denominada formulário:selectManyListBox. O próprio formulário pode ser referido de várias formas. Aqui, é referido pela notação this.form, em que «this» designa o botão [Raz] e this.form o formulário no qual esse botão se encontra. A lista formulário:selectManyListBox encontra-se nesse mesmo formulário. Assim, a notação this.form['formulaire:selectManyListBox'] designa a localização da lista na árvore de componentes do formulário. O objeto que representa uma lista possui um atributo selectedIndex cujo valor corresponde ao número do elemento selecionado na lista. Este número começa em 0 para designar o primeiro elemento da lista. O valor -1 indica que nenhum elemento está selecionado na lista. O código JavaScript que atribui o valor -1 ao atributo selectedIndex tem como efeito desmarcar todos os elementos da lista, caso existam.
Agora, abaixo, vamos selecionar novos valores na lista (para selecionar vários elementos da lista, mantenha a tecla Ctrl premida enquanto clica) e submeter o formulário com o botão [Valider] [2]. Recebemos como resposta a página [3,4]:
![]() |
O valor do campo [1] enviado é o seguinte:
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. Os elementos HTML
<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
foram selecionados. O navegador enviou as três cadeias «3», «4», «5» como valores do componente JSF que gerou a lista suspensa:
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
O método setSelectManyListBox do modelo será utilizado para atualizar este modelo com os valores enviados pelo navegador:
private String[] selectManyListBox;
....
public void setSelectManyListBox(String[] selectManyListBox) {
this.selectManyListBox = selectManyListBox;
}
Na linha 3, vemos que o parâmetro do método é um array de String. Neste caso, será o array {"3","4","5"}. Após esta atualização, o campo
private String[] selectManyListBox;
contém agora a matriz {"3","4","5"}.
Quando a página [index.xhtml] é novamente apresentada após o seu processamento, este valor dá origem à apresentação [3,4] acima:
- determina os elementos da lista que devem ser selecionados [3],
- o valor do campo selectManyListBox é apresentado em [4].
2.5.16. Etiqueta <h:selectOneMenu>
A baliza <h:selectOneMenu> é idêntica à baliza <h:selectOneListBox size="1">. No exemplo, o código JSF executado é o seguinte:
<!-- linha 7 -->
<h:outputText value="selectOneMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
<h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectOneMenu>
</h:panelGroup>
<h:outputText value="#{form.selectOneMenu}"/>
O modelo da baliza <h:selectOneMenu> em [Form.java] é o seguinte:
private String selectOneMenu="1";
Na solicitação inicial da página [index.xhtml], o código anterior gera a visualização:
![]() |
Um exemplo de execução poderia ser o seguinte:
![]() |
O valor lançado para o campo [1] é o seguinte:
2.5.17. Etiqueta <h:selectManyMenu>
A baliza <h:selectManyMenu> é idêntica à baliza <h:selectManyListBox size="1">. O código JSF executado no exemplo é o seguinte:
<!-- linha 8 -->
<h:outputText value="selectManyMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
<h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectManyMenu>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
O modelo da baliza <h:selectManyMenu> em [Form.java] é o seguinte:
private String[] selectManyMenu=new String[]{"1","2"};
Na solicitação inicial da página [index.xhtml], o código anterior gera a página:
![]() |
A lista [1] contém os textos «um», ..., «cinco», com os elementos «um» e «dois» selecionados. O código HTML gerado é o seguinte:
<tr>
<td class="col1"><span class="info">selectManyMenu</span></td>
<td class="col2"><span class="prompt">choix multiple : </span><select id="formulaire:selectManyMenu" name="formulaire:selectManyMenu" multiple="multiple" size="1">
<option value="1" selected="selected">un</option>
<option value="2" selected="selected">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select>
<p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</td>
<td class="col3"><span class="prompt">[ 1 2]</span></td>
</tr>
Como se pode ver acima, nas linhas 4 e 5, os elementos «um» e «dois» estão selecionados (presença do atributo selected).
É difícil apresentar uma captura de ecrã de um exemplo de execução, uma vez que não é possível mostrar os elementos selecionados no menu. Convidamos o leitor a fazer o teste por si próprio (para selecionar vários elementos da lista, mantenha a tecla Ctrl premida enquanto clica).
2.5.18. Etiqueta <h:inputHidden>
A baliza <h:inputHidden> não tem representação visual. Serve apenas para inserir uma baliza <input type="hidden" value="..."/> no fluxo da página. Incluídos dentro de uma baliza <h:form>, os seus valores fazem parte dos valores enviados ao servidor quando o formulário é submetido. Como se trata de campos de formulário que o utilizador não vê, são denominados campos ocultos. A utilidade destes campos é preservar a memória entre os diferentes ciclos de pedido/resposta de um mesmo cliente:
- o cliente solicita um formulário F. O servidor envia-lho e coloca uma informação I num campo oculto C, na forma <h:inputHidden id="C" value="I"/>,
- quando o cliente preenche o formulário F e o envia para o servidor, o valor I do campo C é devolvido ao servidor. Este pode então recuperar a informação I que tinha armazenado na página. Criou-se assim uma memória entre os dois ciclos de pedido/resposta,
- o próprio JSF utiliza esta técnica. A informação I que armazena no formulário F é o valor de todos os seus componentes. Para tal, utiliza o seguinte campo oculto:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...8PswawAA" />
O campo oculto chama-se javax.faces.ViewState e o seu valor é uma cadeia de caracteres que representa, de forma codificada, o valor de todos os componentes da página enviada ao cliente. Quando este devolve a página após ter introduzido dados no formulário, o campo oculto javax.faces.ViewState é devolvido com os valores introduzidos. É isto que permite ao controlador JSF reconstituir a página tal como tinha sido enviada inicialmente. Este mecanismo foi explicado na página 72.
O código JSF do exemplo é o seguinte:
<!-- linha 9 -->
<h:outputText value="inputHidden" styleClass="info"/>
<h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
<h:outputText value="#{form.inputHidden}"/>
O modelo da baliza <h:inputHidden> em [Form.java] é o seguinte:
private String inputHidden="initial";
O que resulta na seguinte apresentação aquando do pedido inicial da página [index.xhtml]:
- a linha 2 gera [1], a linha 4 gera [2]. A linha 3 não gera nenhum elemento visual.
O código HTML gerado é o seguinte:
<tr>
<td class="col1"><span class="info">inputHidden</span></td>
<td class="col2"><input id="formulaire:inputHidden" type="hidden" name="formulaire:inputHidden" value="initial" /></td>
<td class="col3">initial</td>
</tr>
No momento do envio do formulário com o código POST, o valor «inicial» do campo denominado formulário:inputHidden da linha 3 será enviado juntamente com os outros valores do formulário. O campo
private String inputHidden;
será atualizado com este valor, que é o que já tinha inicialmente. Este valor será incluído na nova página enviada ao cliente. Assim, obtém-se sempre a captura de ecrã acima.
O valor enviado para o campo oculto é o seguinte:
2.5.19. Tag <h:selectBooleanCheckBox>
A baliza <h:selectBooleanCheckBox> gera uma baliza HTML <input type="checkbox" ...>.
Consideremos o seguinte código JSF:
<!-- linha 10 -->
<h:outputText value="selectBooleanCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}" styleClass="prompt" />
<h:selectBooleanCheckbox id="selectBooleanCheckbox" value="#{form.selectBooleanCheckbox}"/>
</h:panelGroup>
<h:outputText value="#{form.selectBooleanCheckbox}"/>
O modelo da baliza <h:selectBooleanCheckbox> da linha 5 acima, em [Form.java], é o seguinte:
private boolean selectBooleanCheckbox=true;
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1],
- o texto [2] é gerado pela linha 4. A caixa de seleção [3] é gerada pela linha [5]. Aqui, o método getSelectBooleanCheckbox de [Form.java] foi utilizado para marcar ou não a caixa de seleção. Como o método atribui o valor booleano «true» (ver código Java), a caixa de seleção foi marcada,
- a linha 7 do código XHTML gera [4]. É novamente o método getSelectBooleanCheckbox de [Form.java] que é utilizado para gerar o texto [4].
O fluxo HTML gerado pelo código JSF anterior é o seguinte:
<tr>
<td class="col1"><span class="info">selectBooleanCheckbox</span></td>
<td class="col2"><span class="prompt">marié(e) : </span>
<input id="formulaire:selectBooleanCheckbox" type="checkbox" name="formulaire:selectBooleanCheckbox" checked="checked" /></td>
<td class="col3">true</td>
</tr>
No [4], vemos a baliza HTML <input type="checkbox"> que foi gerada. O valor true do modelo associado fez com que o atributo checked="checked" fosse adicionado à baliza. Isso faz com que a caixa de seleção esteja marcada.
Agora, abaixo, vamos desmarcar a caixa de seleção [1], submeter o formulário [2] e observar o resultado obtido [3, 4]:
![]() |
Como a caixa de seleção está desmarcada, não existe nenhum valor registado para o campo [1].
A validação do formulário por [2] provocou a atualização do modelo [Form.java] através da entrada [1]. O campo selectBooleanCheckbox de [Form.java] recebeu então o valor «false». A nova visualização de [index.xhtml] mostra que o campo selectBooleanCheckbox do modelo foi efetivamente atualizado para [3] e [4]. É interessante notar aqui que foi graças ao campo oculto javax.faces.ViewState que JSF conseguiu determinar que a caixa de seleção inicialmente marcada tinha sido desmarcada pelo utilizador. Com efeito, o valor de uma caixa desmarcada não faz parte dos valores enviados pelo navegador. Graças à árvore de componentes armazenada no campo oculto javax.faces.ViewState, o JSF constata que existia uma caixa de seleção denominada «selectBooleanCheckbox» no formulário e que o seu valor não faz parte dos valores enviados pelo navegador do cliente. Pode-se concluir que estava desmarcada no formulário enviado, o que permite atribuir o valor booleano false ao modelo Java associado:
private boolean selectBooleanCheckbox;
2.5.20. Tag <h:selectManyCheckBox>
A baliza <h:selectManyCheckBox> gera um grupo de caixas de seleção e, por conseguinte, várias balizas HTML <input type="checkbox" ...>. Esta baliza é o equivalente à baliza <h:selectManyListBox>, com a diferença de que os elementos a selecionar são apresentados sob a forma de caixas de seleção contíguas, em vez de sob a forma de lista. O que foi dito sobre a baliza <h:selectManyListBox> mantém-se válido aqui.
Consideremos o seguinte código JSF:
<!-- linha 11 -->
<h:outputText value="selectManyCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
<h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
<f:selectItem itemValue="1" itemLabel="rouge"/>
<f:selectItem itemValue="2" itemLabel="bleu"/>
<f:selectItem itemValue="3" itemLabel="blanc"/>
<f:selectItem itemValue="4" itemLabel="noir"/>
</h:selectManyCheckbox>
</h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>
O modelo da baliza <h:selectManyCheckbox> da linha 5 acima, em [Form.java], é o seguinte:
private String[] selectManyCheckbox=new String[]{"1","3"};
Quando a página [index.xhtml] é solicitada pela primeira vez, a página obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1],
- o texto [2] é gerado pela linha 4. As caixas de seleção [3] são geradas pelas linhas 5 a 10. Para cada uma delas:
- o atributo itemLabel define o texto exibido junto à caixa de seleção;
- o atributo itemvalue define o valor que será enviado para o servidor se a caixa de seleção estiver marcada,
O modelo das quatro caixas de seleção é o seguinte campo Java:
private String[] selectManyCheckbox=new String[]{"1","3"};
Esta tabela define:
- quando a página é apresentada, as caixas de seleção que devem estar marcadas. Isto é feito através do seu valor, c.a.d, e do seu campo itemValue. No exemplo acima, as caixas cujos valores constam na tabela {"1","3"} serão marcadas. É isso que se vê na captura de ecrã acima;
- quando a página é enviada, o modelo selectManyCheckbox recebe o tabuleiro com os valores das caixas de seleção que o utilizador marcou. É isso que vamos ver a seguir,
- a linha 12 do código XHTML gera o [4]. Foi o método getSelectManyCheckboxValue a seguir que gerou o [4]:
public String getSelectManyCheckboxValue(){
return getValue(getSelectManyCheckbox());
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
O fluxo HTML gerado pelo código JSF anterior é o seguinte:
<tr>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:0" value="1" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:0"> rouge</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:1" value="2" type="checkbox" /><label for="formulaire:selectManyCheckbox:1"> bleu</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:2" value="3" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:2"> blanc</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:3" value="4" type="checkbox" /><label for="formulaire:selectManyCheckbox:3"> noir</label></td>
</tr>
</table></td>
<td class="col3">[ 1 3]</td>
</tr>
Foram geradas quatro tags HTML <input type="checkbox" ...>. As tags das linhas 3 e 7 têm o atributo checked="checked", o que faz com que apareçam marcadas. Note-se que todas elas têm o mesmo atributo name="formulário:selectManyCheckbox"; por outras palavras, os quatro campos HTML têm o mesmo nome. Se as caixas de seleção das linhas 5 e 9 forem marcadas pelo utilizador, o navegador enviará os valores das quatro caixas de seleção da seguinte forma:
e o modelo das quatro caixas
private String[] selectManyCheckbox=new String[]{"1","3"};
receberá o array {"2","4"}.
Vamos verificar isso abaixo. No [1], fazemos a alteração; no [2], validamos o formulário. No [3], o resultado obtido é:
![]() |
Os valores enviados para os campos [1] são os seguintes:
2.5.21. Etiqueta <h:selectOneRadio>
A baliza <h:selectOneRadio> gera um grupo de botões de opção mutuamente exclusivos.
Consideremos o seguinte código JSF:
<!-- linha 12 -->
<h:outputText value="selectOneRadio" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
<h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
<f:selectItem itemValue="1" itemLabel="voiture"/>
<f:selectItem itemValue="2" itemLabel="vélo"/>
<f:selectItem itemValue="3" itemLabel="scooter"/>
<f:selectItem itemValue="4" itemLabel="marche"/>
</h:selectOneRadio>
</h:panelGroup>
<h:outputText value="#{form.selectOneRadio}"/>
O modelo da baliza <h:selectOneRadio> da linha 5 acima é o seguinte em [Form.java]:
private String selectOneRadio="2";
Quando a página [index.xhtml] é solicitada pela primeira vez, a visualização obtida é a seguinte:
![]() |
- a linha 2 do código XHTML gera [1],
- O texto [2] é gerado pela linha 4. Os botões de opção [3] são gerados pelas linhas 5 a 10. Para cada um deles:
- o atributo itemLabel define o texto exibido junto ao botão de opção;
- o atributo itemvalue define o valor que será enviado para o servidor se o botão estiver selecionado,
O modelo dos quatro botões de opção é o seguinte campo Java:
private String selectOneRadio="2";
Este modelo define:
- quando a página é apresentada, o único botão de opção que deve estar marcado. Isto é feito através do seu valor, c.a.d, e do seu campo itemValue. No exemplo acima, o botão de opção com o valor «2» estará marcado. É isso que se vê na captura de ecrã acima;
- quando a página é enviada, o modelo selectOneRadio recebe o valor do botão de opção que foi selecionado. É isso que vamos ver a seguir,
- a linha 12 do código XHTML gera o [4].
O fluxo HTML gerado pelo código JSF anterior é o seguinte:
<tr>
<td class="col1"><span class="info">selectOneRadio</span></td>
<td class="col2">moyen de transport préféré : <table id="formulaire:selectOneRadio">
<tr>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:0" value="1" /><label for="formulaire:selectOneRadio:0"> voiture</label></td>
<td>
<input type="radio" checked="checked" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:1" value="2" /><label for="formulaire:selectOneRadio:1"> vélo</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:2" value="3" /><label for="formulaire:selectOneRadio:2"> scooter</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:3" value="4" /><label for="formulaire:selectOneRadio:3"> marche</label></td>
</tr>
Foram geradas quatro tags HTML <input type="radio" ...>. A tag da linha 8 tem o atributo checked="checked", o que faz com que o botão de opção correspondente apareça marcado. Note-se que todas as tags têm o mesmo atributo name="formulário:selectOneRadio"; por outras palavras, os quatro campos HTML têm o mesmo nome. Esta é a condição necessária para se ter um grupo de botões de opção exclusivos: quando um está marcado, os outros não estão.
Abaixo, em [1], marca-se um dos botões de opção; em [2], valida-se o formulário; em [3], o resultado obtido:
![]() |
O valor enviado para o campo [1] é o seguinte:
2.6. Exemplo mv-jsf2-04: listas dinâmicas
2.6.1. A aplicação
A aplicação é a mesma que anteriormente:
![]() |
As únicas alterações dizem respeito à forma como são gerados os elementos das listas das áreas [1] e [2]. Aqui, são gerados dinamicamente por código Java, enquanto na versão anterior estavam «fixos» no código da página JSF.
2.6.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
O projeto [mv-jsf2-04] é idêntico ao projeto [mv-jsf2-03], com as seguintes diferenças:
- no [1], na página JSF, os elementos das listas já não serão escritos «fixos» no código,
- no [2], o modelo da página JSF [1] será alterado,
- na [3], uma das mensagens será alterada.
2.6.3. A página [index.xhtml] e o seu modelo [Form.java]
A página JSF [index.xhtml] passa a ter o seguinte aspeto:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- línguas -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
...
<!-- linha 4 -->
<h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox1}"/>
<!-- linha 5 -->
<h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
<f:selectItems value="#{form.selectOneListbox2Items}"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox2}"/>
<!-- linha 6 -->
<h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
<f:selectItems value="#{form.selectManyListBoxItems}"/>
</h:selectManyListbox>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyListBoxValue}"/>
<!-- linha 7 -->
<h:outputText value="selectOneMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
<h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
<f:selectItems value="#{form.selectOneMenuItems}"/>
</h:selectOneMenu>
</h:panelGroup>
<h:outputText value="#{form.selectOneMenu}"/>
<!-- linha 8 -->
<h:outputText value="selectManyMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
<h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
<f:selectItems value="#{form.selectManyMenuItems}"/>
</h:selectManyMenu>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
...
<!-- linha 11 -->
<h:outputText value="selectManyCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
<h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
<f:selectItems value="#{form.selectManyCheckboxItems}"/>
</h:selectManyCheckbox>
</h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>
<!-- linha 12 -->
<h:outputText value="selectOneRadio" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
<h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
<f:selectItems value="#{form.selectOneRadioItems}"/>
</h:selectOneRadio>
</h:panelGroup>
<h:outputText value="#{form.selectOneRadio}"/>
</h:panelGrid>
<p>
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
</p>
</h:form>
</h:body>
</f:view>
</html>
As alterações introduzidas são ilustradas nas linhas 26-28. Onde anteriormente se encontrava o código:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>
agora temos este:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
As três balizas <f:selectItem> das linhas 2 a 4 foram substituídas pela única baliza <f:selectItems> da linha b. Esta baliza possui um atributo «value», cujo valor é uma coleção de elementos do tipo javax.faces.model.SelectItem. Acima, o valor do atributo «value» será obtido através da chamada do seguinte método [form].getSelectOneListbox1Items:
public SelectItem[] getSelectOneListbox1Items() {
return getItems("A",3);
}
private SelectItem[] getItems(String label, int qte) {
SelectItem[] items=new SelectItem[qte];
for(int i=0;i<qte;i++){
items[i]=new SelectItem(i,label+i);
}
return items;
}
- na linha 1, o método getSelectOneListbox1Items devolve um tabuleiro de elementos do tipo javax.faces.model.SelectItem construído pelo método privado getItems da linha 5. Note-se que o método getSelectOneListbox1Items não é o getter de um campo privado selectOneListBox1Items,
- a classe javax.faces.model.SelectItem tem vários construtores.

Utilizamos a linha 8 do método getItems, o construtor SelectItem(Object value, String label), que corresponde à baliza JSF
<f:selectItem itemValue="value" labelValue="label"/>
- linhas 5-10: o método getItems(String label, int qte) constrói um array com qte elementos do tipo SelectItem, em que o elemento i é obtido pelo construtor SelectItem(i, label+i).
O código JSF
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
torna-se, assim, funcionalmente equivalente ao seguinte código JSF:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="0" itemLabel="A0"/>
<f:selectItem itemValue="1" itemLabel="A1"/>
<f:selectItem itemValue="2" itemLabel="A2"/>
</h:selectOneListbox>
O mesmo se aplica a todas as outras listas da página JSF. Assim, no modelo [Form.java] encontram-se os seguintes novos métodos:
public SelectItem[] getSelectOneListbox1Items() {
return getItems("A",3);
}
public SelectItem[] getSelectOneListbox2Items() {
return getItems("B",4);
}
public SelectItem[] getSelectManyListBoxItems() {
return getItems("C",5);
}
public SelectItem[] getSelectOneMenuItems() {
return getItems("D",3);
}
public SelectItem[] getSelectManyMenuItems() {
return getItems("E",4);
}
public SelectItem[] getSelectManyCheckboxItems() {
return getItems("F",3);
}
public SelectItem[] getSelectOneRadioItems() {
return getItems("G",4);
}
private SelectItem[] getItems(String label, int qte) {
SelectItem[] items=new SelectItem[qte];
for(int i=0;i<qte;i++){
items[i]=new SelectItem(i,label+i);
}
return items;
}
2.6.4. O ficheiro de mensagens
Apenas uma mensagem é alterada:
[messages_fr.properties]
form.titre=Java Server Faces - remplissage dynamique des listes
[messages_en.properties]
form.titre=Java Server Faces - dynamic filling of lists of elements
2.6.5. Testes
Convidamos o leitor a testar esta nova versão.
Na maioria das vezes, os elementos dinâmicos de um formulário são o resultado de um processamento específico da área de negócio ou provêm de uma base de dados:
![]() |
Analisemos o pedido inicial da página JSF [index.xhtml] através de um GET do navegador:
- a página JSF é solicitada por [1],
- o controlador [Faces Servlet] solicita a sua exibição em [3]. O motor JSF, que processa a página, recorre ao modelo [Form.java] da mesma, por exemplo, ao método getSelectOneListBox1Items. Este método poderia muito bem devolver um tabuleiro de elementos do tipo SelectItem, a partir de informações registadas numa base de dados. Para tal, recorreria à camada [métier] [2b].
2.7. Exemplo mv-jsf2-05: navegação – sessão – gestão de exceções
2.7.1. A aplicação
A aplicação é a mesma que a anterior, com a diferença de que o formulário se apresenta agora sob a forma de um assistente com várias páginas:
![]() |
- em [1], a página 1 do formulário — pode ser acedida também através do link 1 de [2]
- em [2], um grupo de 5 links.
- em [3], a página 2 do formulário, acedida através do link 2 de [2]
![]() |
![]() |
- em [4], a página 3 do formulário acedida através do link 3 de [2]
- em [5], a página obtida através do link «Lançar uma exceção» de [2]
![]() |
- em [6], a página obtida através do link 4 de [2]. Esta página resume os dados introduzidos nas páginas 1 a 3.
2.7.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
O projeto [mv-jsf2-05] introduz duas novidades:
- no [1], a página JSF [index.xhtml] é dividida em três páginas [form1.xhtml, form2.xhtml, form3.xhtml], nas quais as entradas foram distribuídas. A página [form4.xhtml] é uma cópia da página [index.xhtml] do projeto anterior. Na página [2], a classe [Form.java] permanece inalterada. Servirá de modelo para as quatro páginas JSF anteriores,
- em [3], é adicionada uma página [exception.xhtml]: esta será utilizada quando ocorrer uma exceção na aplicação.
2.7.3. As páginas [form.xhtml] e o seu modelo [Form.java]
2.7.3.1. O código das páginas XHTML
A página JSF [form1.xhtml] é a seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- ligações -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form1.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2" columns="2" border="1">
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<!-- linha 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<!-- linha 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<!-- linha 3 -->
<h:outputText value="inputTextArea" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.descPrompt']}"/>
<h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
</h:panelGroup>
</h:panelGrid>
<!-- ligações -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="#{form.doAction2}"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="#{form.doAction4}"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>
</h:form>
</h:body>
</f:view>
</html>
e corresponde à seguinte visualização:
![]() |
Deve-se notar o seguinte:
- na linha 16, a tabela que anteriormente tinha três colunas passa a ter apenas duas. A coluna 3, que apresentava os valores do modelo, foi eliminada. Será a [form4.xhtml] que os apresentará,
- linhas 40-46: uma tabela com seis links. Os links das linhas 44 e 46 têm navegação estática: o seu atributo «action» está codificado de forma fixa. Os restantes links têm navegação dinâmica: o seu atributo «action» aponta para um método do bean de formulário responsável por devolver a chave de navegação. Os métodos referenciados em [Form.java] são os seguintes:
// eventos
public String doAction2(){
return "form2";
}
public String doAction4(){
return "form4";
}
public String doAlea(){
// um número aleatório entre 1 e 3
int i=1+(int)(3*Math.random());
// retornamos a chave de navegação
return "form"+i;
}
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
Por enquanto, ignoraremos o método throwException da linha 17. Voltaremos a este assunto mais tarde. Os métodos doAction2 e doAction4 limitam-se a devolver a chave de navegação sem efetuar qualquer processamento. Por isso, poderíamos igualmente ter escrito:
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="form2"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="form4"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
O método doAlea, por sua vez, gera uma chave de navegação aleatória cujo valor é selecionado do conjunto {"form1", "form2", "form3"}.
O código das páginas [form2.xhtml, form3.xhtml, form3.xhtml] é análogo ao da página [form1.xhtml].
2.7.3.2. Tempo de vida do modelo [Form.java] das páginas [form*.xhtml]
Consideremos a seguinte sequência de ações:
![]() |
- em [1], preenche-se a página 1 e passa-se para a página 3,
- em [2], preenche-se a página 3 e volta-se à página 1,
![]() |
- em [3], a página 1 aparece tal como foi introduzida. Volta-se então à página 3,
- em [4], a página 3 é apresentada tal como foi introduzida.
O mecanismo do campo oculto [javax.faces.ViewState] não é suficiente para explicar este fenómeno.
Durante a transição de [1] para [2], ocorrem várias etapas:
- o modelo [Form.java] é atualizado com o POST a partir do [form1.jsp]. Em particular, o campo inputText recebe o valor «outro texto»,
- a chave de navegação «form3» faz com que seja apresentado o [form3.xhtml]. O ViewState incorporado no [form3.xhtml] apresenta apenas o estado dos componentes do [form3.xhtml], e não os do [form1.xhtml].
Ao passar de [2] para [3]:
- o modelo [Form.java] é atualizado com o POST do [form3.xhtml]. Se o período de validade do modelo [Form.java] tiver expirado, é criado um objeto [Form.java] totalmente novo, antes de ser atualizado pelo POST a partir do [form3.xhtml]. Neste caso, o campo inputText do modelo volta ao seu valor por predefinição:
private String inputText="texte";
e mantém-no: de facto, no POST do [form3.xhtml], nada atualiza o campo inputText, que faz parte do modelo de [form1.xhtml] e não do de [form3.xhtml],
- a chave de navegação «form1» faz com que seja apresentado o [form1.xhtml]. A página apresenta o seu modelo. No nosso caso, o campo de introdução de dados login, associado ao modelo inputText, apresentará texte e não o valor «outro texto» introduzido em [1]. Para que o campo inputText mantenha o valor introduzido em [1], o período de validade do modelo [Form.java] deve ser «sessão» e não «pedido». Neste caso,
- ao final da execução do POST a partir do [form1.xhtml], o modelo será colocado na sessão do cliente. O campo inputText terá o valor «outro texto»,
- no momento da execução do POST a partir do [form3.xhtml], o modelo será procurado nessa sessão e atualizado pelo POST a partir do [form3.xhtml]. O campo inputText não será atualizado por este POST, mas manterá o valor «outro texto» adquirido no final do POST a partir do [form1.xhtml] e do [1].
A declaração do bean [Form.java] é, portanto, a seguinte:
package forms;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
@ManagedBean
@SessionScoped
public class Form {
A linha 8 atribui ao bean um âmbito de sessão.
2.7.4. Gestão de exceções
Voltemos à arquitetura geral de uma aplicação JSF:
![]() |
O que acontece quando um gestor de eventos ou um modelo deteta uma exceção proveniente da camada de negócio, por exemplo, uma desconexão imprevista de uma base de dados?
- Os gestores de eventos [2a] podem interceptar qualquer exceção proveniente da camada [métier] e fornecer ao controlador [Faces Servlet] uma chave de navegação para uma página de erro específica para a exceção,
- No caso dos modelos, esta solução não é aplicável, pois quando são solicitados ([3,4]), estamos na fase de renderização de uma página específica (XHTML) e já não na fase de seleção da mesma. Como é que se pode mudar de página quando se está na fase de renderização de uma delas? Uma solução simples, mas que nem sempre é adequada, consiste em não tratar a exceção, que será então encaminhada para o contentor de servlets que executa a aplicação. Este pode ser configurado para apresentar uma página específica quando uma exceção é encaminhada para o contentor de servlets. Esta solução é sempre viável e vamos analisá-la agora.
2.7.4.1. Configuração da aplicação web para a gestão de exceções
A configuração de uma aplicação web para a gestão de exceções é feita no seu ficheiro [web.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/form1.xhtml</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/faces/exception.xhtml</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/faces/exception.xhtml</location>
</error-page>
</web-app>
Nas linhas 32 a 39, encontra-se a definição de duas páginas de erro. É possível ter tantas balizas <error-page> quantas forem necessárias. A baliza <location> indica a página a apresentar em caso de erro. O tipo de erro associado à página pode ser definido de duas formas:
- através da baliza <exception-type>, que define o tipo Java da exceção tratada. Assim, a baliza <error-page> das linhas 36-39 indica que, se o contentor de servlets detetar uma exceção do tipo [java.lang.Exception] ou derivada (linha 37) durante a execução da aplicação, deve então apresentar a página [/faces/exception.xhtml] (linha 38). Ao utilizar aqui o tipo de exceção mais genérico, [java.lang.Exception], garantimos o tratamento de todas as exceções,
- através da baliza <error-code> (linha 33), que define um código de erro HTTP. Por exemplo, se um navegador solicitar o URL [http://machine:port/contexte/P] e a página P não existir no contexto da aplicação, esta não intervém na resposta. É o contentor de servlets que gera esta resposta, enviando uma página de erro por predefinição. A primeira linha do fluxo HTTP da sua resposta contém um código de erro 404, indicando que a página P solicitada não existe. Pode ser necessário gerar uma resposta que, por exemplo, respeite a identidade visual da aplicação ou que forneça links para resolver o problema. Neste caso, utilizar-se-á uma baliza <error-page> com uma baliza <error-code>404</error-code>.
Acima, o código de erro 500 HTTP é o código devolvido em caso de «falha» da aplicação. É o código que seria devolvido se uma exceção fosse propagada até ao contentor de servlets. As duas tags <error-page> nas linhas 28-35 são, portanto, provavelmente redundantes. Colocámo-las ambas para ilustrar as duas formas de gerir um erro.
2.7.4.2. Simulação da exceção
É gerada artificialmente uma exceção através do link [Lancer une exception]:
![]() |
![]() |
Um clique no link [Lancer une exception] [1] faz com que seja apresentada a página [2].
No código das páginas [formx.xhtml], o link [Lancer une exception] é gerado da seguinte forma:
<!-- links -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
...
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>
Na linha 5, verifica-se que, ao clicar no link, o método [form].throwException será executado. Este método é o seguinte:
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
Nele, é lançada uma exceção do tipo [java.lang.Exception]. Esta será encaminhada até ao contentor de servlets, que, por sua vez, apresentará a página [/faces/exception.xhtml].
2.7.4.3. As informações relacionadas com uma exceção
Quando uma exceção é transmitida até ao contentor de servlets, este exibe a página de erro correspondente, transmitindo-lhe informações sobre a exceção. Estas informações são inseridas como novos atributos da solicitação em processamento. A solicitação de um navegador e a resposta que este irá receber são encapsuladas em objetos Java do tipo [HttpServletRequest request] e [HttpServletResponse response]. Estes objetos estão disponíveis em todas as etapas do processamento da solicitação do navegador.
![]() |
Ao receber a solicitação HTTP do navegador, o contentor de servlets encapsula-a no objeto Java [HttpServletRequest request] e cria o objeto [HttpServletResponse response], que permitirá gerar a resposta. Neste objeto, encontra-se, nomeadamente, o canal TCP-IP a utilizar para o fluxo HTTP da resposta. Todas as camadas t1, t2, ..., tn que intervêm no processamento do objeto request têm acesso a estes dois objetos. Cada uma delas pode aceder aos elementos da solicitação inicial request e preparar a resposta, enriquecendo o objeto response. Uma camada de localisation poderá, por exemplo, definir o localisation da resposta através do método response.setLocale(Locale l).
As diferentes camadas podem trocar informações através do objeto request. Este possui um dicionário de atributos, vazio na sua criação, que pode ser enriquecido pelas camadas de processamento sucessivas. Estas podem inserir nos atributos do objeto request as informações necessárias para a camada de processamento seguinte. Existem dois métodos para gerir os atributos do objeto request:
- void setAttribute(String s, Object o), que permite adicionar aos atributos um objeto o identificado pela cadeia s,
- Object getAttribute(String s), que permite obter o atributo o identificado pela cadeia s.
Quando uma exceção é propagada até ao contentor de servlets, este insere os seguintes atributos na solicitação em processamento:
chave | valor |
o código de erro HTTP que será enviado ao cliente | |
o tipo Java da exceção, acompanhado da mensagem de erro. | |
o URL solicitado quando a exceção ocorreu | |
o servlet que estava a processar o pedido quando a exceção ocorreu |
Iremos utilizar estes atributos da solicitação na página [exception.xhtml] para os apresentar.
2.7.4.4. A página de erro [exception.xhtml]
O seu conteúdo é o seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<h3><h:outputText value="#{msg['exception.header']}"/></h3>
<h:panelGrid columnClasses="col1,col2" columns="2" border="1">
<h:outputText value="#{msg['exception.httpCode']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
<h:outputText value="#{msg['exception.message']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
<h:outputText value="#{msg['exception.requestUri']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
<h:outputText value="#{msg['exception.servletName']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
</h:panelGrid>
<!-- ligações -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="#{form.doAction2}"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="#{form.doAction4}"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
</h:panelGrid>
</h:form>
</h:body>
</f:view>
</html>
2.7.4.4.1. As expressões da página de exceção
Na cadeia de processamento da solicitação do cliente, a página XHTML é normalmente o último elo da cadeia:
![]() |
Todos os elementos da cadeia são classes Java, incluindo a página XHTML. Esta é, de facto, transformada num servlet pelo contentor de servlets, c.a.d, numa classe Java normal. Mais especificamente, a página XHTML é transformada em código Java que é executado no âmbito do seguinte método:
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HTTPSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
...
...code de la page XHTML
A partir da linha 14, encontra-se o código Java correspondente à página XHTML. Este código disporá de um certo número de objetos inicializados pelo método _jspService, linha 1 acima:
- linha 1: HttpServletRequest request: o pedido em curso de processamento,
- linha 1: HttpServletResponse response: a resposta que será enviada ao cliente,
- linha 7: ServletContext application: um objeto que representa a própria aplicação web. Tal como o objeto request, o objeto application pode ter atributos. Estes são partilhados por todas as solicitações de todos os clientes. Trata-se, geralmente, de atributos de leitura única,
- linha 6: HTTPSession sessão: representa a sessão do cliente. Tal como os objetos request e application, o objeto session pode ter atributos. Estes são partilhados por todas as solicitações de um mesmo cliente,
- linha 9: JspWriter out: um fluxo de escrita para o navegador do cliente. Este objeto é útil para a depuração de uma página XHTML. Tudo o que for escrito através de out.println(texto) será apresentado no navegador do cliente.
Quando, na página JSF, se escreve #{expressão}, a expressão pode ser a chave de um atributo dos objetos request, session ou application acima referidos. O atributo correspondente é procurado sucessivamente nestes três objetos. Assim, #{chave} é avaliada da seguinte forma:
- request.getAttribute(chave)
- session.getAttribute(chave)
- application.getAttribute(chave)
Assim que for obtido um valor que não seja null, a avaliação de #{chave} é interrompida. Pode ser necessário ser mais preciso, indicando o contexto em que o atributo deve ser procurado:
- #{requestScope['clé']} para procurar o atributo no objeto request,
- #{sessionScope['clé']} para procurar o atributo no objeto «session»,
- #{applicationScope['clé']} para procurar o atributo no objeto «application».
Foi isso que se fez na página [exception.xhtml], página 116. Os atributos utilizados são os seguintes:
chave | domínio | valor |
pedido | ver parágrafo 2.7.4.3. | |
idem | idem | |
idem | idem | |
idem | idem |
As diferentes mensagens necessárias para a página JSF [exception.xhtml] foram adicionadas aos ficheiros de mensagens já existentes:
[messages_fr.properties]
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=URL demandée lors de l'erreur
exception.servletName=Nom de la servlet demandée lorsque l'erreur s'est produite
[messages_en.properties]
exception.header=The following error occurred
exception.httpCode=HTTP error code
exception.message=Exception message
exception.requestUri=URL requested when error occurred
exception.servletName=Servlet requested when error occurred
2.8. Exemplo mv-jsf2-06: validação e conversão dos dados introduzidos
2.8.1. A aplicação
A aplicação apresenta um formulário de introdução de dados. Após a validação do mesmo, o próprio formulário é devolvido como resposta, acompanhado de eventuais mensagens de erro caso os dados introduzidos tenham sido considerados incorretos.
![]() |
![]() |
2.8.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
O projeto [mv-jsf2-06] baseia-se novamente numa única página [index.html] [1] e no seu modelo [Form.java] [2]. Continua a utilizar mensagens extraídas de [messages.properties], mas apenas em francês ([3]). A opção de alteração do idioma não está disponível.
2.8.3. O ambiente da aplicação
Apresentamos aqui o conteúdo dos ficheiros que configuram a aplicação, sem fornecer explicações específicas. Estes ficheiros permitem compreender melhor o que se segue.
[faces-config.xml]
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
</faces-config>
A linha 17 é nova. Será explicada posteriormente.
O ficheiro de mensagens [messages_fr.properties]
form.titre=Jsf - validations et conversions
saisie1.prompt=1-Nombre entier de type int
saisie2.prompt=2-Nombre entier de type int
saisie3.prompt=3-Nombre entier de type int
data.required=Vous devez entrer une donn\u00e9e
integer.required=Vous devez entrer un nombre entier
saisie4.prompt=4-Nombre entier de type int dans l'intervalle [1,10]
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
saisie5.prompt=5-Nombre r\u00e9el de type double
double.required=Vous devez entrer un nombre
saisie6.prompt=6-Nombre r\u00e9el>=0 de type double
saisie6.error=6-Vous devez entrer un nombre >=0
saisie7.prompt=7-Bool\u00e9en
saisie7.error=7-Vous devez entrer un bool\u00e9en
saisie8.prompt=8-Date au format jj/mm/aaaa
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
date.required=Vous devez entrer une date
saisie9.prompt=9-Cha\u00eene de 4 caract\u00e8res
saisie9.error=9-Vous devez entrer une cha\u00eene de 4 caract\u00e8res exactement
saisie9B.prompt=9B-Heure au format hh:mm
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
submit=Valider
cancel=Annuler
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du mod\u00e8le du formulaire
saisie10.prompt=10-Nombre entier de type int <1 ou >7
saisie10.incorrecte=10-Saisie n\u00b0 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
saisies11et12.incorrectes=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisies11et12.incorrectes_detail=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisie11.prompt=11-Nombre entier de type int
saisie12.prompt=12-Nombre entier de type int
error.sign="!"
error.sign_detail="!"
A folha de estilo [styles.css] é a seguinte:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.col4{
background-color: #ccffcc
}
.error{
color: #ff0000
}
.saisie{
background-color: #ffcccc;
border-color: #000000;
border-width: 5px;
color: #cc0033;
font-family: cursive;
font-size: 16px
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
2.8.4. A página [index.xhtml] e o seu modelo [Form.java]
A página [index.xhtml] é a seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['form.titre']}"/></h2>
<h:form id="formulaire">
<h:messages globalOnly="true" />
<h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
<!-- linha 1 -->
<h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
<h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
<!-- linha 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
<!-- linha 3 -->
<h:outputText value="#{msg['saisie2.prompt']}" />
<h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
<h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>
<!-- linha 4 -->
<h:outputText value="#{msg['saisie3.prompt']}" />
<h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>
<!-- linha 5 -->
<h:outputText value="#{msg['saisie4.prompt']}" />
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
<h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>
<!-- linha 6 -->
...
<!-- linha 7 -->
...
<!-- linha 8 -->
...
<!-- linha 9 -->
...
<!-- linha 10 -->
...
<!-- linha 11 -->
...
<!-- linha 12 -->
...
<!-- linha 13 -->
...
</h:panelGrid>
<!-- botões de comando -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
<h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>
A principal novidade reside na presença das balizas:
- para apresentar mensagens de erro <h:messages> (linha 14), <h:message> (linhas 24, 29, 34),
- que impõem restrições de validade às entradas <f:validateLongRange> (linha 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
- que definem um conversor entre a entrada e o seu modelo como <f:convertDateTime>.
O modelo desta página é a seguinte classe [Form.java]:
package forms;
import com.corejsf.util.Messages;
import java.util.Date;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
@ManagedBean
@RequestScoped
public class Form {
public Form() {
}
// entradas
private Integer saisie1 = 0;
private Integer saisie2 = 0;
private Integer saisie3 = 0;
private Integer saisie4 = 0;
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
private Boolean saisie7 = true;
private Date saisie8 = new Date();
private String saisie9 = "";
private Integer saisie10 = 0;
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
// ações
public String submit() {
...
}
public String cancel() {
...
}
// validadores
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// getters e setters
...
}
A novidade aqui é que os campos do modelo já não são apenas do tipo String, mas sim de vários tipos.
2.8.5. Os diferentes campos de preenchimento do formulário
Vamos agora analisar, sucessivamente, os diferentes campos do formulário.
2.8.5.1. Campos 1 a 4: introdução de um número inteiro
A página [index.xhtml] apresenta a entrada 1 da seguinte forma:
<!-- linha 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
O modelo form.saisie1 está definido da seguinte forma em [Form.java]:
private Integer saisie1 = 0;
Num GET do navegador, a página [index.xhtml] associada ao seu modelo [Form.java] apresenta visualmente o seguinte:
- a linha 2 produz [1],
- a linha 3 produz [2],
- a linha 4 produz [3],
- a linha 5 produz [4].
Suponhamos que seja introduzido e validado o seguinte:
![]() |
Obtém-se então o seguinte resultado no formulário devolvido pela aplicação:
![]() |
- em [1], a entrada incorreta,
- em [2], a mensagem de erro que o assinala,
- em [3], verifica-se que o valor do campo Integer «saisie1» do modelo não se alterou.
Vamos explicar o que aconteceu. Para tal, voltemos ao ciclo de processamento de uma página JSF:
![]() |
Analisamos este ciclo para o componente:
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
e o seu modelo:
private Integer saisie1 = 0;
- em [A], a página [index.xhtml] enviada durante o GET do navegador é restaurada. Em [A], a página apresenta-se tal como o utilizador a recebeu. O componente id="saisie1" recupera o seu valor inicial «0»,
- no [B], os componentes da página recebem como valores os valores enviados pelo navegador. No [B], a página apresenta-se tal como o utilizador a preencheu e validou. O componente id="saisie1" recebe como valor o valor enviado «x»,
- em [C], se a página contiver validadores e conversores explícitos, estes são executados. Os conversores implícitos também são executados se o tipo do campo associado ao componente não for do tipo String. É o que acontece aqui, onde o campo form.saisie1 é do tipo Integer. O JSF tentará converter o valor «x» do componente id="saisie1" para o tipo Integer. Isto provocará um erro que interromperá o ciclo de processamento [A-F]. Este erro será associado ao componente id="saisie1". Através do [D2], passa-se então diretamente para a fase de renderização da resposta. A mesma página [index.xhtml] é devolvida,
- a fase [D] só ocorre se todos os componentes de uma página tiverem passado pela fase de conversão/validação. É nesta fase que o valor do componente id="saisie1" será atribuído ao seu modelo form.saisie1.
Se a fase [C] falhar, a página é reexibida e o código seguinte é executado novamente:
<!-- linha 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
![]() |
A mensagem apresentada em [2] provém da linha 4 de [index.xhtml]. A baliza <h:message for="idComposant"/> apresenta a mensagem de erro associada ao componente indicado pelo atributo «for», caso ocorra um erro. A mensagem apresentada em [2] é padrão e encontra-se no ficheiro [javax/faces/Messages.properties] do arquivo [jsf-api.jar]:
![]() |
No [2], verifica-se que o ficheiro de mensagens existe em várias variantes. Analisemos o conteúdo do [Messages_fr.properties]:
O ficheiro contém mensagens divididas em categorias:
- erros num componente, linha 3,
- erros de conversão entre um componente e o seu modelo, linha 12
- erros de validação quando existem validadores na página, linha 23.
O erro que ocorreu no componente id="saisie1" é do tipo erro de conversão de um tipo String para um tipo Integer. A mensagem de erro associada é a da linha 18 do ficheiro de mensagens.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}
A mensagem de erro que foi apresentada é reproduzida abaixo:
![]() |
Verifica-se que, na mensagem:
- o parâmetro {2} foi substituído pelo identificador do componente para o qual ocorreu o erro de conversão,
- o parâmetro {0} foi substituído pela entrada efetuada em [1] para o componente,
- o parâmetro {1} foi substituído pelo número 9346.
A maioria das mensagens relacionadas com os componentes tem duas versões: uma versão resumida (summary) e uma versão detalhada (detail). É o caso das linhas 16-18:
A mensagem com a chave _detail (linha 2) é a mensagem denominada «detalhada». A outra é a mensagem denominada «resumida». A baliza <h:message> apresenta, por predefinição, a mensagem detalhada. Este comportamento pode ser alterado através dos atributos showSummary e showDetail. É isso que se faz para o componente com o ID «saisie2»:
<!-- linha 3 -->
<h:outputText value="#{msg['saisie2.prompt']}" />
<h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
<h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>
Na linha 2, o componente saisie2 está associado ao campo form.saisie2 seguinte:
private Integer saisie2 = 0;
O resultado obtido é o seguinte:
![]() |
- em [1], a mensagem detalhada; em [2], a mensagem resumida.
A baliza <h:messages> apresenta, sob a forma de uma lista, todas as mensagens de erro resumidas de todos os componentes, bem como as mensagens de erro não associadas a nenhum componente. Também neste caso, existem atributos que podem alterar este comportamento por predefinição:
- showDetail: true / false para solicitar ou não as mensagens detalhadas,
- showSummary: true / false para solicitar ou não as mensagens resumidas,
- globalOnly: true / false para solicitar ou não a exibição apenas das mensagens de erro não associadas a componentes. Uma mensagem deste tipo poderia, por exemplo, ser criada pelo programador.
A mensagem de erro associada a uma conversão pode ser alterada de várias formas. Em primeiro lugar, é possível indicar à aplicação para utilizar outro ficheiro de mensagens. Esta alteração é efetuada em [faces-config.xml]:
<faces-config ...">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
...
</faces-config>
As linhas 3-8 definem um ficheiro de mensagens, mas não é este que é utilizado pelas balizas <h:message> e <h:messages>. É necessário utilizar a baliza <message-bundle> da linha 9 para o definir. A linha 9 indica às balizas <h:message(s)> que o ficheiro [messages.properties] deve ser analisado antes do ficheiro [javax.faces.Messages.properties]. Assim, se adicionarmos as seguintes linhas ao ficheiro [messages_fr.properties]:
# conversões
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e
o erro devolvido para os componentes saisie1 e saisie2 passa a ser:

A outra forma de alterar a mensagem de erro de conversão é utilizar o atributo converterMessage do componente, como se segue, para o componente saisie3:
<!-- linha 4 -->
<h:outputText value="#{msg['saisie3.prompt']}" />
<h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>
O componente saisie3 está associado ao seguinte campo form.saisie3:
private Integer saisie3 = 0;
- na linha 3, o atributo converterMessage define explicitamente a mensagem a apresentar em caso de erro de conversão;
- na linha 3, o atributo required="true" indica que o preenchimento é obrigatório. O campo não pode ficar em branco. Um campo é considerado em branco se não contiver nenhum caractere ou se contiver uma sequência de espaços. Também neste caso, existe no [javax.faces.Messages.properties] uma mensagem por predefinição:
O atributo requiredMessage permite substituir esta mensagem predefinida. Se o ficheiro [messages.properties] contiver as seguintes mensagens:
...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
poderá obter-se o seguinte resultado:
![]() |
ou ainda este:
![]() |
Verificar se um valor introduzido corresponde efetivamente a um número inteiro nem sempre é suficiente. Por vezes, é necessário verificar se o número introduzido pertence a um determinado intervalo. Nesse caso, utiliza-se um validador. A entrada n.º 4 constitui um exemplo disso. O seu código em [index.xhtml] é o seguinte:
<!-- linha 5 -->
<h:outputText value="#{msg['saisie4.prompt']}" />
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
<h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>
Na linha 3, o componente saisie4 está associado ao seguinte modelo form.saisie4:
private Integer saisie4 = 0;
Nas linhas 3 a 5, a baliza <h:inputText> tem uma baliza filha <f:validateLongRange> que admite dois atributos opcionais: «minimum» e «maximum». Esta baliza, também designada por «validador», permite adicionar uma restrição ao valor introduzido: este deve ser não só um número inteiro, mas um número inteiro no intervalo [minimum, maximum] se os dois atributos minimum e maximum estiverem presentes, ser maior ou igual a minimum se apenas o atributo minimum estiver presente, e ser menor ou igual a maximum se apenas o atributo maximum estiver presente. O validador <f:validateLongRange> tem mensagens de erro predefinidas em [javax.faces.Messages.properties]:
Mais uma vez, é possível substituir estas mensagens por outras. Existe um atributo validatorMessage que permite definir uma mensagem específica para o componente. Assim, com o seguinte código JSF:
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
e a seguinte mensagem em [messages.properties]:
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
obtém-se o seguinte resultado:

2.8.5.2. Entradas 5 e 6: introdução de um número real
A introdução de números reais segue regras semelhantes às da introdução de números inteiros. O código XHTML das entradas 5 e 6 é o seguinte:
<!-- linha 6 -->
<h:outputText value="#{msg['saisie5.prompt']}" />
<h:inputText id="saisie5" value="#{form.saisie5}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
<h:message for="saisie5" styleClass="error"/>
<h:outputText value="#{form.saisie5}"/>
<!-- linha 7 -->
<h:outputText value="#{msg['saisie6.prompt']}"/>
<h:inputText id="saisie6" value="#{form.saisie6}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}" validatorMessage="#{msg['saisie6.error']}">
<f:validateDoubleRange minimum="0.0"/>
</h:inputText>
<h:message for="saisie6" styleClass="error"/>
<h:outputText value="#{form.saisie6}"/>
Os elementos do modelo [Form.java] associados aos componentes saisie5 e saisie6:
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
As mensagens de erro associadas aos conversores e validadores dos componentes saisie5 e saisie6, no [messages.properties]:
double.required=Vous devez entrer un nombre
saisie6.error=6-Vous devez entrer un nombre >=0
Eis um exemplo de execução:

2.8.5.3. Entrada 7: introdução de um valor booleano
A introdução de um valor booleano deve, normalmente, ser feita através de uma caixa de seleção. Se for feita através de um campo de introdução, a cadeia «true» é convertida no valor booleano «true» e qualquer outra cadeia no valor booleano «false».
O código XHTML do exemplo:
<!-- linha 8 -->
<h:outputText value="#{msg['saisie7.prompt']}"/>
<h:inputText id="saisie7" value="#{form.saisie7}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
<h:message for="saisie7" styleClass="error"/>
<h:outputText value="#{form.saisie7}"/>
O modelo do componente saisie7:
private Boolean saisie7 = true;
Eis um exemplo de entrada e a respetiva resposta:
![]() |
Em [1], o valor introduzido. Por conversão, esta cadeia «x» torna-se o valor booleano «false». É isso que mostra [2]. O valor [3] do modelo não se alterou. Só se altera quando todas as conversões e validações da página forem bem-sucedidas. Não foi esse o caso neste exemplo.
2.8.5.4. Entrada 8: introdução de uma data
A introdução de uma data é feita no exemplo com o código XHTML seguinte:
<!-- linha 9 -->
<h:outputText value="#{msg['saisie8.prompt']}"/>
<h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
<h:message for="saisie8" styleClass="error"/>
<h:outputText value="#{form.saisie8}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:outputText>
O componente saisie8 da linha 3 utiliza um conversor java.lang.String <--> java.util.Date. O modelo form.saisie8 associado ao componente saisie8 é o seguinte:
private Date saisie8 = new Date();
O componente definido pelas linhas 7-9 também utiliza um conversor, mas apenas no sentido java.util.Date --> java.lang.String.
O conversor <f:convertDateTime> admite vários atributos, incluindo o atributo «pattern», que define o formato da cadeia de caracteres que deve ser transformada em data ou o formato em que uma data deve ser apresentada.
Na solicitação inicial da página [index.xhtml], a linha 8 anterior é apresentada da seguinte forma:
Os campos [1] e [2] apresentam ambos o valor do modelo form.saisie8:
private Date saisie8 = new Date();
onde saisie8 assume como valor a data de hoje. O conversor utilizado em ambos os casos para a exibição da data é o seguinte:
<f:convertDateTime pattern="dd/MM/yyyy"/>
onde dd (day) designa o número do dia, MM (Month) o número do mês e yyyy (year) o ano. Em [1], o conversor é utilizado para a conversão inversa java.lang.String --> java.util.Date. A data introduzida deverá, portanto, seguir o formato «dd/MM/yyyy» para ser válida.
Existem mensagens predefinidas para datas inválidas no [javax.faces.Messages.properties]:
que podem ser substituídas por mensagens personalizadas. Assim, no exemplo:
<h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
a mensagem apresentada em caso de erro de conversão será a seguinte mensagem com a chave saisie8.error:
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
Eis um exemplo:
![]()
2.8.5.5. Entrada 9: introdução de uma cadeia de caracteres com comprimento restrito
A entrada 9 mostra como impor que uma cadeia introduzida tenha um número de caracteres compreendido num intervalo:
<!-- linha 10 -->
<h:outputText value="#{msg['saisie9.prompt']}"/>
<h:inputText id="saisie9" value="#{form.saisie9}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9.error']}">
<f:validateLength minimum="4" maximum="4"/>
</h:inputText>
<h:message for="saisie9" styleClass="error"/>
<h:outputText value="#{form.saisie9}"/>
Na linha 4, o validador <f:validateLength minimum="4" maximum="4"/> impõe que a cadeia introduzida tenha exatamente 4 caracteres. Só é possível utilizar um dos atributos: «minimum» para um número mínimo de caracteres e «maximum» para um número máximo.
O modelo form.saisie9 do componente saisie9 da linha 3 é o seguinte:
private String saisie9 = "";
Existem mensagens de erro predefinidas para este tipo de validação:
que podem ser substituídas utilizando o atributo validatorMessage, tal como na linha 3 acima. A mensagem da chave saisie9.error é a seguinte:
saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement
Eis um exemplo de execução:
![]()
2.8.5.6. Entrada 9B: introdução de uma cadeia de caracteres que deve seguir um modelo
A entrada 9B mostra como impor que uma cadeia introduzida tenha um número de caracteres dentro de um intervalo:
<!-- linha 10B -->
<h:outputText value="#{msg['saisie9B.prompt']}"/>
<h:inputText id="saisie9B" value="#{form.saisie9B}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9B.error']}">
<f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/>
</h:inputText>
<h:message for="saisie9B" styleClass="error"/>
<h:outputText value="#{form.saisie9B}"/>
Na linha 4, o validador <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> impõe que a cadeia introduzida corresponda ao padrão de uma expressão regular, neste caso: uma sequência de 0 ou mais espaços, 2 algarismos, o sinal :, 2 algarismos, uma sequência de 0 ou mais espaços.
O padrão form.saisie9B do componente saisie9B da linha 3 é o seguinte:
private String saisie9B;
Existem mensagens de erro predefinidas para este tipo de validação:
que podem ser substituídas utilizando o atributo validatorMessage, tal como na linha 3 acima. A mensagem de chave saisie9.error é a seguinte:
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
Eis um exemplo de execução:
![]()
2.8.5.7. Exercício 10: escrever um método de validação específico
Resumindo: JSF permite verificar, entre os valores introduzidos, a validade dos números (inteiros, reais), das datas, o comprimento das cadeias de caracteres e a conformidade de uma entrada em relação a uma expressão regular. O JSF permite adicionar aos validadores e conversores existentes os seus próprios validadores e conversores. Este ponto não é abordado aqui, mas pode consultar-se o [ref2] para aprofundar o assunto.
Apresentamos aqui outro método: aquele que consiste em validar um dado introduzido através de um método do modelo do formulário. Eis o exemplo seguinte:
<!-- linha 11 -->
<h:outputText value="#{msg['saisie10.prompt']}"/>
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
<h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>
O modelo form.saisie10 associado ao componente saisie10 da linha 3 é o seguinte:
private Integer saisie10 = 0;
Pretende-se que o número introduzido seja <1 ou >7. Não é possível verificar isso com os validadores básicos do JSF. Por isso, escreve-se o próprio método de validação do componente saisie10. Indica-se isso através do atributo «validator» do componente a validar:
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
O componente saisie10 é validado pelo método form.validateSaisie10. Este método é o seguinte:
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
int saisie = (Integer) value;
if (!(saisie < 1 || saisie > 7)) {
FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
A assinatura de um método de validação deve ser obrigatoriamente a da linha 1:
- FacesContext context: contexto de execução da página — dá acesso a várias informações, nomeadamente aos objetos HttpServletRequest request e HttpServletResponse response,
- UIComponent component: o componente a validar. A baliza <h:inputText> é representada por um componente do tipo UIInput derivado de UIComponent. Neste caso, é este componente UIInput que é recebido como segundo parâmetro,
- Valor do objeto: o valor introduzido a ser verificado, convertido para o tipo do seu modelo. É importante compreender aqui que, se a conversão de String para o tipo do modelo falhar, o método de validação não é executado. Quando se chega ao método validateSaisie10, significa que a conversão de String para Integer foi bem-sucedida. O terceiro parâmetro é, então, do tipo Integer.
- linha 2: o valor introduzido é convertido para o tipo int,
- linha 3: verifica-se se o valor introduzido é <1 ou >7. Se for esse o caso, a validação está concluída. Caso contrário, o validador deve sinalizar o erro lançando uma exceção do tipo ValidatorException.
A classe ValidatorException tem dois construtores:
![]() |
- o construtor [1] tem como parâmetro uma mensagem de erro do tipo FacesMessage. Este tipo de mensagem é o exibido pelas balizas <h:messages> e <h:message>,
- o construtor [2] permite, além disso, encapsular a causa do tipo Throwable ou derivada do erro.
Temos de construir uma mensagem do tipo FacesMessage. Esta classe possui vários construtores:
![]() |
O construtor [1] define as propriedades de um objeto FacesMessage:
- FacesMessage.Severity severity: um nível de gravidade selecionado da seguinte enumeração: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
- String summary: a versão resumida da mensagem de erro — é apresentada pelas balizas <h:message showSummary="true"> e <h:messages>,
- String detail: a versão detalhada da mensagem de erro — é apresentada pelas balizas <h:message> e <h:messages showDetail="true">.
Pode ser utilizado qualquer um dos construtores, podendo os parâmetros em falta ser definidos posteriormente através dos métodos set.
O construtor [1] não permite indicar uma mensagem que se encontre num ficheiro de mensagens internacionalizado. É, evidentemente, uma pena. David Geary e Cay Horstmann, no seu livro «Core JavaServer Faces», colmatam esta lacuna com a classe utilitária com.corejsf.util.Messages. É esta classe que é utilizada na linha 4 do código Java para criar a mensagem de erro. Contém apenas métodos estáticos, incluindo o método getMessage utilizado na linha 4:
public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)
O método getMessage aceita três parâmetros:
- String bundleName: o nome de um ficheiro de mensagens sem a sua extensão .properties, mas com o nome do seu pacote. Neste caso, o nosso primeiro parâmetro poderia ser messages para designar o ficheiro [messages.properties]. Antes de utilizar o ficheiro indicado pelo primeiro parâmetro, o getMessage tenta utilizar o ficheiro de mensagens da aplicação, caso exista. Assim, se no [faces-config.xml] tiver sido declarado um ficheiro de mensagens com a baliza:
<application>
...
<message-bundle>messages</message-bundle>
</application>
pode-se passar null como primeiro parâmetro ao método getMessage. Foi isso que se fez aqui (ver [web.xm], página 120),
- String resourceId: a chave da mensagem a ser processada no ficheiro de mensagens. Vimos que uma mensagem pode ter simultaneamente uma versão resumida e uma versão detalhada. resourceId é o identificador da versão resumida. A versão detalhada será procurada automaticamente com a chave resourceId_detail. Assim, teremos duas mensagens em [messages.properties] relativas ao erro na entrada n.º 10:
saisie10.incorrecte=10-Saisie n° 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
A mensagem do tipo FacesMessage gerada pelo método Messages.getMessage inclui tanto a versão resumida como a detalhada, caso tenham sido encontradas. Ambas as versões têm de estar presentes; caso contrário, ocorre uma exceção do tipo [NullPointerException],
- Object[] params: os parâmetros efetivos da mensagem, caso esta tenha parâmetros formais {0}, {1}, ... Estes parâmetros formais serão substituídos pelos elementos da matriz params.
Voltemos ao código do método de validação do componente saisie10:
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
int saisie = (Integer) value;
if (!(saisie < 1 || saisie > 7)) {
FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
- no [4], a mensagem do tipo FacesMessage é criada utilizando o método estático Messages.getMessage,
- em [5], define-se o nível de gravidade da mensagem,
- em [6], é lançada uma exceção do tipo ValidatorException com a mensagem criada anteriormente. O método de validação foi chamado pelo código XHTML seguinte:
<!-- linha 11 -->
<h:outputText value="#{msg['saisie10.prompt']}"/>
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
<h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>
Na linha 3, o método de validação é executado para o componente com o ID saisie10. Assim, a mensagem de erro gerada pelo método validateSaisie10 é associada a este componente e, por conseguinte, apresentada na linha 4 (atributo for="saisie10"). É a versão detalhada que é apresentada por predefinição pela baliza <h:message>.
Eis um exemplo de execução:
![]()
2.8.5.8. Entradas 11 e 12: validação de um grupo de componentes
Até agora, os métodos de validação abordados validavam apenas um único componente. Como proceder se a validação pretendida disser respeito a vários componentes? É isso que vamos ver agora. No formulário:

pretendemos que os campos 11 e 12 sejam dois números inteiros cuja soma seja igual a 10.
O código JSF será o seguinte:
<!-- linha 12 -->
<h:outputText value="#{msg['saisie11.prompt']}"/>
<h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie11" styleClass="error"/>
<h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie11}"/>
<!-- linha 13 -->
<h:outputText value="#{msg['saisie12.prompt']}"/>
<h:inputText id="saisie12" value="#{form.saisie12}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie12" styleClass="error"/>
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie12}"/>
e o modelo associado:
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
Na linha 3 do código JSF, utilizam-se as técnicas já apresentadas para verificar se o valor introduzido para o componente saisie11 é, de facto, um número inteiro. O mesmo se aplica, na linha 11, ao componente saisie12. Para verificar se saisie11 + saisie12 = 10, poderíamos criar um validador específico. Esta é a solução a preferir. Mais uma vez, consultaremos o [ref2] para a descobrir. Aqui, seguimos uma abordagem diferente.
A página [index.xhtml] é validada por um botão [Valider], cujo código JSF é o seguinte:
<!-- botões de comando -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
...
</h:panelGrid>
onde a mensagem msg['submit'] é a seguinte:
submit=Valider
Na linha 3, verifica-se que o método form.submit será executado para processar o clique no botão [Valider]. Este método é o seguinte:
// ações
public String submit() {
// últimas validações
validateForm();
// envia-se o mesmo formulário
return null;
}
// validações globais
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
...
}
É importante compreender que, quando o método «submit» é executado:
- todos os validadores e conversores do formulário foram executados e concluídos com sucesso,
- os campos do modelo [Form.java] receberam os valores enviados pelo cliente.
De facto, voltemos ao ciclo de processamento de um POST JSF:
![]() |
O método submit é um gestor de eventos. Este gere o evento clic no botão [Valider]. Tal como todos os gestores de eventos, este é executado na fase [E], depois de todos os validadores e conversores terem sido executados com sucesso ([C]) e de o modelo ter sido atualizado com os valores enviados ([D]). Por isso, já não se trata aqui de lançar exceções do tipo [ValidatorException], como fizemos anteriormente. Limitar-nos-emos a reenviar o formulário com mensagens de erro:
![]() |
Em [1], alertaremos o utilizador e, em [2] e [3], colocaremos um símbolo de erro. No código JSF, a mensagem [1] será obtida da seguinte forma:
<h:form id="formulaire">
<h:messages globalOnly="true" />
<h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
<!-- linha 1 -->
...
Na linha 2, a baliza <h:messages> apresenta, por predefinição, a versão resumida das mensagens de erro de todas as entradas incorretas dos componentes do formulário, bem como todas as mensagens de erro não relacionadas com componentes. O atributo globalOnly="true" limita a exibição a estas últimas.
As mensagens [2] e [3] são apresentadas com simples tags <h:outputText>:
<!-- linha 12 -->
<h:outputText value="#{msg['saisie11.prompt']}"/>
<h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie11" styleClass="error"/>
<h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie11}"/>
<!-- linha 13 -->
...
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
...
Nas linhas 4-7, o componente saisie11 apresenta duas mensagens de erro possíveis:
- aquela que indica uma conversão incorreta ou a ausência de dados. Esta mensagem, gerada pelo próprio JSF, estará contida num tipo FacesMessage e será apresentada pela baliza <h:message> da linha 5,
- aquela que iremos gerar se a entrada11 + a entrada12 não for igual a 10. Será apresentada na linha 6. A mensagem de erro estará contida no modelo form.errorSaisie11.
As duas mensagens correspondem a erros que não podem ocorrer simultaneamente. A verificação «entrada11 + entrada12 = 10» é efetuada no método submit, que só é executado se não houver nenhum erro no formulário. Quando este for executado, o componente saisie11 já terá sido verificado e o seu modelo form.saise11 terá recebido o seu valor. A mensagem da linha 5 já não poderá ser exibida. Por outro lado, se a mensagem da linha 5 for exibida, significa que ainda existe pelo menos um erro no formulário e o método submit não será executado. A mensagem da linha 6 não será exibida. Para que as duas possíveis mensagens de erro fiquem na mesma coluna da tabela, foram agrupadas numa baliza <h:panelGroup> (linhas 4 e 7).
O método submit é o seguinte:
// ações
public String submit() {
// últimas validações
validateForm();
// envia-se o mesmo formulário
return null;
}
// validações globais
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
// mensagem global
FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
// mensagens relacionadas com os campos
message = Messages.getMessage(null, "error.sign", null);
setErrorSaisie11(message.getSummary());
setErrorSaisie12(message.getSummary());
} else {
setErrorSaisie11("");
setErrorSaisie12("");
}
}
- linha 4: o método submit chama o método validateForm para efetuar as últimas validações,
- linha 11: verifica-se se saisie11+saisie12=10,
- se não for o caso, nas linhas 13-14, cria-se uma mensagem do tipo FacesMessage com o ID de mensagem saisies11et12.incorrectes. Esta é a seguinte:
saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
- a mensagem assim criada é adicionada (linhas 15-16) à lista de mensagens de erro da aplicação. Esta mensagem não está associada a nenhum componente específico. Trata-se de uma mensagem global da aplicação. Será apresentada pela baliza <h:messages globalOnly="true"/> apresentada acima,
- linha 18: cria-se uma nova mensagem do tipo FacesMessage com o ID de mensagem error.sign. Esta é a seguinte:
error.sign="!"
Já referimos que o método estático [Messages.getMessage] constrói uma mensagem do tipo FacesMessage com uma versão resumida e uma versão detalhada, caso estas existam. Neste caso, existe apenas a versão resumida da mensagem error.sign. A versão resumida de uma mensagem m é obtida através de m.getSummary(). Nas linhas 19 e 20, a versão resumida da mensagem error.sign é inserida nos campos errorSaisie11 e errorSaisie12 do modelo. Serão apresentados pelas seguintes balizas JSF:
<h:outputText value="#{form.saisie11}"/>
...
<h:outputText value="#{form.saisie12}"/>
- linhas 22-23: se a propriedade saisie11+saisie12=10 for verificada, então os dois campos errorSaisie11 e errorSaisie12 do modelo são esvaziados, para que uma eventual mensagem de erro anterior seja apagada. É importante lembrar que o modelo é mantido entre as solicitações, na sessão do cliente.
Eis um exemplo de execução:
![]() |
Observe-se na coluna [1] que o modelo recebeu os valores enviados, o que demonstra que todas as operações de validação e conversão entre os valores enviados e o modelo foram bem-sucedidas. O gestor de eventos form.submit, que gere o clique no botão [Valider], pôde assim ser executado. Foi este gestor que gerou as mensagens apresentadas em [2] e [3]. Verifica-se que o modelo foi atualizado, apesar de o formulário ter sido rejeitado e devolvido ao cliente. Poder-se-ia desejar que o modelo não fosse atualizado num caso como este. Com efeito, imaginando que o utilizador anule a atualização com o botão [Annuler] [4], não será possível regressar ao modelo inicial, a menos que este tenha sido guardado.
2.8.5.9. POST de um formulário sem verificação dos dados introduzidos
Consideremos o formulário acima e suponhamos que o utilizador, sem compreender os seus erros, queira abandonar o preenchimento do formulário. Nesse caso, utilizará o botão [Annuler] gerado pelo código JSF seguinte:
<!-- botões de comando -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
<h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
</h:panelGrid>
Na linha 4, a mensagem msg['cancel'] é a seguinte:
cancel=Annuler
O método form.cancel associado ao botão [Annuler] só será executado se o formulário for válido. Foi isso que demonstrámos para o método form.submit associado ao botão [Valider]. Se o utilizador quiser cancelar o preenchimento do formulário, é, naturalmente, desnecessário verificar a validade dos dados introduzidos. Este resultado é obtido com o atributo immediate="true", que indica ao JSF para executar o método form.cancel sem passar pela fase de validação e conversão. Voltemos ao ciclo de processamento do POST JSF:
![]() |
Os eventos dos componentes de ação <h:commandButton> e <h:commandLink> com o atributo immediate="true" são processados na fase [C] e, em seguida, o ciclo JSF passa diretamente para a fase [E] de renderização da resposta.
O método form.cancel é o seguinte:
public String cancel() {
saisie1 = 0;
saisie2 = 0;
saisie3 = 0;
saisie4 = 0;
saisie5 = 0.0;
saisie6 = 0.0;
saisie7 = true;
saisie8 = new Date();
saisie9 = "";
saisie10 = 0;
return null;
}
Se utilizarmos o botão [Annuler] no formulário anterior, obtemos em resposta a seguinte página:
![]() |
- obtém-se novamente o formulário, uma vez que o gestor de eventos form.cancel devolve a chave de navegação null. A página [index.xhtml] é, portanto, devolvida,
- o modelo [Form.java] foi alterado pelo método form.cancel. Isto é refletido na coluna [2], que apresenta este modelo,
- já a coluna [3] reflete o valor lançado para os componentes.
Voltemos ao código JSF do componente saisie1 [4];
<!-- linha 1 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
Na linha 4, o valor do componente saisie1 está associado ao modelo form.saisie1. Isto implica várias coisas:
- durante um GET de [index.xhtml], o componente saisie1 apresentará o valor do modelo form.saisie1,
- durante um POST a partir de [index.xhtml], o valor introduzido para o componente saisie1 só é atribuído ao modelo form.saisie1 se todas as validações e conversões do formulário forem bem-sucedidas. Quer o modelo tenha sido atualizado ou não pelos valores lançados, se o formulário for reenviado após a execução do POST, os componentes exibem o valor que foi lançado e não o valor do modelo que lhes está associado. É isso que mostra a captura de ecrã acima, onde as colunas [2] e [3] não têm os mesmos valores.
2.9. Exemplo mv-jsf2-07: eventos relacionados com a alteração de estado dos componentes JSF
2.9.1. A aplicação
A aplicação apresenta um exemplo de POST criado sem a utilização de um botão ou de um link. O formulário é o seguinte:
![]() |
O conteúdo da lista combo2 [2] está associado ao elemento selecionado no combo1 [1]. Quando se altera a seleção em [1], é executado um POST do formulário, durante o qual o conteúdo de combo2 é alterado para refletir o elemento selecionado em [1], sendo depois o formulário devolvido. Durante este POST, não é efetuada qualquer validação.
2.9.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
Existe um único formulário [index.xhtml] com o seu modelo [Form.java].
2.9.3. O ambiente da aplicação
O ficheiro de mensagens [messages_fr.properties]:
app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire
A folha de estilo [styles.css]:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.col4{
background-color: #ccffcc
}
.error{
color: #ff0000
}
.saisie{
background-color: #ffcccc;
border-color: #000000;
border-width: 5px;
color: #cc0033;
font-family: cursive;
font-size: 16px
}
.combo{
color: green;
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
2.9.4. O formulário [index.xhtml]
O formulário [index.xhtml] é o seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
...
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['app.titre2']}"/></h2>
<h:form id="formulaire">
<h:messages globalOnly="true"/>
<h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
<!-- cabeçalhos -->
<h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
<h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
<!-- linha 1 -->
<h:outputText value="#{msg['combo1.prompt']}"/>
<h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
<f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>
<h:panelGroup></h:panelGroup>
<h:outputText value="#{form.combo1}"/>
<!-- linha 2 -->
<h:outputText value="#{msg['combo2.prompt']}"/>
<h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
<f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>
<h:panelGroup></h:panelGroup>
<h:outputText value="#{form.combo2}"/>
<!-- linha 3 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" required="true" requiredMessage="#{msg['data.required']}" styleClass="saisie" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
</h:panelGrid>
<!-- botões de comando -->
<h:panelGrid columns="2" border="0">
<h:commandButton value="#{msg['submit']}"/>
...
</h:panelGrid>
</h:form>
</h:body>
</html>
A novidade reside no código da lista combo1, linhas 24-26. Surgem novos atributos:
- onchange: atributo HTML — declara uma função ou código JavaScript que deve ser executado quando o elemento selecionado em combo1 mudar. Aqui, o código JavaScript submit() envia o formulário para o servidor,
- valueChangeListener: atributo JSF — define o nome do método a executar no lado do servidor quando o elemento selecionado em combo1 se altera. No total, são executados dois métodos: um no lado do cliente e outro no lado do servidor,
- immediate=true: atributo JSF — define o momento em que o gestor de eventos do lado do servidor deve ser executado: após o formulário ter sido reconstruído de acordo com o que o utilizador introduziu, mas antes das verificações de validade das entradas. O objetivo aqui é preencher a lista combo2 com base no elemento selecionado na lista combo1, mesmo que, por outro lado, possam existir entradas erradas no formulário. Eis um exemplo:
![]() |
- na [1], uma primeira entrada,
- em [2], altera-se o elemento selecionado de combo1 de A para B.
O resultado obtido é o seguinte:
![]() |
O POST ocorreu. O conteúdo de combo2 e [2] foi adaptado aoelemento selecionado em combo1 e [1], apesar de a entrada em [3] estar incorreta. Foi o atributo immediate=true que fez com que o método form.combo1ChangeListener fosse executado antes das verificações de validade. Sem esse atributo, não teria sido executado, pois o ciclo de processamento teria sido interrompido nas verificações de validade devido ao erro em [3].
As mensagens associadas ao formulário são as seguintes no [messages.properties]:
app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire
O período de validade de [Form.java] está definido como «request»:
package forms;
...
@ManagedBean
@RequestScoped
public class Form {
Na linha 6, define-se o âmbito do bean como «request».
2.9.5. O modelo [Form.java]
O modelo [Form.java] é o seguinte:
package forms;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
@ManagedBean
@RequestScoped
public class Form {
public Form() {
}
// campos do formulário
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// campos de trabalho
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// métodos
public SelectItem[] getCombo1Items(){
// inicializar combo1
SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
for(int i=0;i<combo1Labels.length;i++){
combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
}
return combo1Items;
}
public SelectItem[] getCombo2Items(){
// inicialização do combo2 em função do combo1
SelectItem[] combo2Items=new SelectItem[5];
for(int i=1;i<=combo2Items.length;i++){
combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
}
return combo2Items;
}
// ouvintes
public void combo1ChangeListener(ValueChangeEvent event){
// acompanhamento
logger.info("combo1ChangeListener");
// recuperamos o valor enviado por combo1
combo1Label=(String)event.getNewValue();
// enviamos a resposta porque queremos ignorar as validações
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// continuação
logger.info("raz");
// limpar o formulário
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
// getters - setters
...
}
Vamos associar o formulário [index.xhtml] ao seu modelo [Form.java]:
A lista combo1 é gerada pelo código JSF seguinte:
<h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
<f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>
Esta lista obtém os seus elementos através do método getCombo1Items do seu modelo (linha 2). Este método está definido nas linhas 28 a 35 do código Java. Gera uma lista de três elementos {"A", "B", "C"}.
A lista combo2 é gerada pelo código JSF seguinte:
<h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
<f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>
Obtém os seus elementos através do método getCombo2Items do seu modelo (linha 2). Este método está definido nas linhas 37 a 44 do código Java. Gera uma lista de cinco elementos {"X1", "X2", "X3", "X4", "X5"}, em que X é o elemento combo1Label da linha 16. Assim, aquando da geração inicial do formulário, a lista combo2 contém os elementos {"A1","A2","A3", "A4", "A5"}.
Quando o utilizador alterar o elemento selecionado na lista combo1,
- o evento onchange="submit();" será processado pelo navegador do cliente. O formulário será, assim, enviado para o servidor,
- no lado do servidor, o JSF irá detetar que o componente combo1 alterou o seu valor. O método combo1ChangeListener das linhas 47-54 será executado. Um método do tipo ValueChangeListener recebe como parâmetro um objeto do tipo javax.faces.event.ValueChangeEvent. Este objeto permite obter o valor antigo e o novo valor do componente cujo valor se alterou, através dos seguintes métodos:

Aqui, o componente é a lista combo1 do tipo UISelectOne. O seu valor é do tipo String.
- linha 51 do modelo Java: o novo valor de combo1 é armazenado em combo1Label, que serve para gerar os elementos da lista combo2,
- linha 53: devolve-se a resposta. É importante lembrar que o gestor combo1ChangeListener é executado com o atributo immediate="true". Por conseguinte, é executado após a fase em que a árvore de componentes da página foi atualizada com os valores enviados e antes do processo de validação desses valores. No entanto, pretende-se evitar este processo de validação, uma vez que a lista combo2 deve ser atualizada mesmo que, no formulário, ainda existam entradas erradas. Solicita-se, portanto, que a resposta seja enviada imediatamente, sem passar pela fase de validação das entradas.
- O formulário será reenviado tal como foi preenchido. No entanto, os elementos das listas combo1 e combo2 não são valores lançados. Serão gerados novamente através da chamada aos métodos getCombo1Items e getCombo2Items. Este último método irá então utilizar o novo valor de combo1Label definido por combo1ChangeListener e os elementos da lista combo2 irão alterar-se.
2.9.6. O botão [Raz]
Com o botão [Raz], pretendemos repor o formulário ao seu estado inicial, conforme ilustrado abaixo:
![]() |
![]() |
![]() |
Em [1], o formulário antes do POST do botão [Raz]; em [2], o resultado do POST.
Embora seja funcionalmente simples, a gestão deste caso de utilização revela-se bastante complexa. Podem ser experimentadas várias soluções, nomeadamente a utilizada para o botão [Annuler] do exemplo anterior:
<h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>
onde o método form.raz é o seguinte:
public String raz(){
// limpar o formulário
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
O resultado obtido pelo botão [Raz] no exemplo anterior é, então, o seguinte:
![]() |
A coluna [1] mostra que o método form.raz foi executado. No entanto, a coluna [1] continua a apresentar os valores lançados:
- para combo1, o valor lançado era «B». Este elemento é, portanto, selecionado na lista;
- para combo2, o valor lançado era «B5». Devido à execução de form.raz, os elementos {«B1», ..., «B5»} de combo2 foram alterados para {«A1», ..., «A5»}. O elemento «B5» já não existe e, por isso, não pode ser selecionado. Assim, é exibido o primeiro elemento da lista,
- para saisie1, o valor introduzido era 10.
Este é o comportamento normal com o atributo immediate="true". Para obter um resultado diferente, é necessário enviar os valores que se pretende ver no novo formulário, mesmo que o utilizador tenha introduzido outros valores. Isto é conseguido com um pouco de código JavaScript do lado do cliente. O formulário passa a ser o seguinte:
<script language="javascript">
function raz(){
document.forms['formulaire'].elements['formulaire:combo1'].value="A";
document.forms['formulaire'].elements['formulaire:combo2'].value="A1";
document.forms['formulaire'].elements['formulaire:saisie1'].value=0;
//document.forms['formulaire'].submit();
}
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
- na linha 10, o atributo onclick='raz()' indica que se deve executar a função JavaScript raz quando o utilizador clicar no botão [Raz],
- linha 3: atribui-se o valor «A» ao elemento HTML com o nome «formulário:combo1». Os diferentes elementos da linha 3 são os seguintes:
- documento: página apresentada pelo navegador,
- document.forms: conjunto de formulários do documento,
- document.forms['formulaire']: o formulário com o atributo name="formulaire",
- documents.forms['formulaire'].elements: conjunto de elementos do formulário que possuem o atributo name="formulaire",
- document.forms['formulaire'].elements['formulaire:combo1']: elemento do formulário com o atributo name="formulaire:combo1"
- document.forms['formulaire'].elements['formulaire:combo1'].value: valor que será enviado pelo elemento do formulário com o atributo name="formulaire:combo1".
Para conhecer os atributos name dos diferentes elementos da página apresentada pelo navegador, pode consultar-se o seu código-fonte (abaixo com IE7):
![]() |
<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>
Dito isto, é possível compreender que, no código JavaScript da função raz:
- a linha 3 faz com que o valor enviado para o componente combo1 seja a cadeia A,
- a linha 4 faz com que o valor enviado para o componente combo2 seja a cadeia A1,
- a linha 5 faz com que o valor enviado para o componente saisie1 seja a cadeia 0.
Feito isto, o POST do formulário, associado a qualquer botão do tipo <h:commandButton> (linha 10), será executado. O método form.raz será executado e o formulário será devolvido tal como foi enviado. Obtém-se então o seguinte resultado:
![]() |
Este resultado esconde muitas coisas. Os valores «A», «A1» e «0» dos componentes combo1, combo2 e saisie1 são enviados para o servidor. Suponhamos que o valor anterior de combo1 fosse «B». Nesse caso, verifica-se uma alteração no valor do componente combo1 e o método form.combo1ChangeListener também deverá ser executado. Temos dois gestores de eventos com o atributo immediate="true". Serão ambos executados? Se sim, em que ordem? Apenas um? Se sim, qual?
Para saber mais, criamos registos na aplicação:
package forms;
import java.util.logging.Logger;
...
public class Form {
...
// campos do formulário
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// campos de trabalho
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// ouvinte
public void combo1ChangeListener(ValueChangeEvent event){
// acompanhamento
logger.info("combo1ChangeListener");
// recuperamos o valor enviado do combo1
combo1Label=(String)event.getNewValue();
// enviamos a resposta porque queremos ignorar as validações
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// continuação
logger.info("raz");
// limpar o formulário
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
...
}
- linha 16: é criado um gerador de registos. O parâmetro de getLogger permite diferenciar as origens dos registos. Aqui, o gerador de registos chama-se forms.Form,
- linha 21: regista-se a entrada no método combo1ChangeListener,
- linha 30: regista-se a entrada no método raz.
Quais são os registos produzidos pelo botão [Raz] ou pela alteração do valor de combo1? Consideremos vários casos:
- utiliza-se o botão [Raz] quando o elemento selecionado em combo1 é «A». «A» é, portanto, o último valor do componente combo1. Vimos que o botão [Raz] executava uma função JavaScript que enviava o valor «A» para o componente combo1. O componente combo1 não altera, portanto, o seu valor. Os registos mostram então que apenas o método form.raz é executado:
- utiliza-se o botão [Raz], embora o elemento selecionado em combo1 não seja «A». O componente combo1 altera, portanto, o seu valor: o seu último valor não era «A» e o botão [Raz] irá atribuir-lhe o valor «A». Os registos mostram então que são executados dois métodos. Por ordem: combo1ChangeListener, raz:
![]() |
- Altera-se o valor do combo1 sem utilizar o botão [Raz]. Os registos mostram que apenas o método combo1ChangeListener é executado:
![]() |
2.10. Exemplo mv-jsf2-08: a baliza <h:dataTable>
2.10.1. A aplicação
A aplicação apresenta uma lista de pessoas com a possibilidade de as eliminar:
![]() |
- em [1], uma lista de pessoas,
- em [2], os links que permitem eliminá-las.
2.10.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
Temos um único formulário [index.xhtml] com o seu modelo [Form.java].
2.10.3. O ambiente da aplicação
O ficheiro de configuração [faces-config.xml]:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
</faces-config>
O ficheiro de mensagens [messages_fr.properties]:
app.titre=intro-08
app.titre2=JSF - DataTable
submit=Valider
personnes.headers.id=Id
personnes.headers.nom=Nom
personnes.headers.prenom=Pr\u00e9nom
A folha de estilo [styles.css]:
.headers {
text-align: center;
font-style: italic;
color: Snow;
background: Teal;
}
.id {
height: 25px;
text-align: center;
background: MediumTurquoise;
}
.nom {
text-align: left;
background: PowderBlue;
}
.prenom {
width: 6em;
text-align: left;
color: Black;
background: MediumTurquoise;
}
2.10.4. O formulário [index.xhtml] e o seu modelo [Form.java]
Recorde-se a vista associada à página [index.xhtml]:
![]() |
O formulário [index.xhtml] é o seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['app.titre2']}"/></h2>
<h:form id="formulaire">
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
........................
</h:dataTable>
</h:form>
</h:body>
</html>
Na linha 14, a baliza <h:dataTable> utiliza o campo #{form.personnes} como fonte de dados. É o seguinte:
private List<Pessoa> pessoas;
A classe [Personne] é a seguinte:
package forms;
public class Personne {
// dados
private int id;
private String nom;
private String prénom;
// construtores
public Personne(){
}
public Personne(int id, String nom, String prénom){
this.id=id;
this.nom=nom;
this.prénom=prénom;
}
// toString
public String toString(){
return String.format("Personne[%d,%s,%s]", id,nom,prénom);
}
// getters e setters
...
}
Voltemos ao conteúdo da baliza <h:dataTable>:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
- o atributo var="pessoa" define o nome da variável que representa a pessoa atual dentro da baliza <h:datatable>,
- o atributo headerClass="headers" define o estilo dos títulos das colunas da tabela,
- o atributo columnClasses="...." define o estilo de cada uma das colunas da tabela.
Vamos analisar uma das colunas da tabela e ver como está construída:
![]() |
O código XHTML da coluna Id é o seguinte:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.id']}"/>
</f:facet>
<h:outputText value="#{personne.id}"/>
</h:column>
...
</h:dataTable>
lignes 3-5 : la balise <f:facet name="header"> définit le titre de la colonne,
ligne 4 : le titre de la colonne est pris dans le fichier des messages,
ligne 6 : personne fait référence à l'attribut var de la balise <h:dataTable ...> (ligne 1). On écrit donc l'id de la personne courante.
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.id']}"/>
</f:facet>
<h:outputText value="#{personne.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.nom']}"/>
</f:facet>
<h:outputText value="#{personne.nom}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.prenom']}"/>
</f:facet>
<h:outputText value="#{personne.prénom}"/>
</h:column>
...
</h:dataTable>
- linhas 3-7: a coluna «id» da tabela,
- linhas 8-13: a coluna «nome» da tabela,
- linhas 14-19: a coluna «prénom» da tabela.
Agora, vamos analisar a coluna dos links [Retirer]:
![]() |
Esta coluna é gerada pelo código seguinte:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
<h:column>
<h:commandLink value="Retirer" action="#{form.retirerPersonne}">
<f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>
</h:column>
</h:dataTable>
O link [Retirer] é gerado pelas linhas 4-6. Quando se clica no link, o método [Form].retirerPersonne será executado. É altura de analisar a classe [Form.java]:
package forms;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class Form {
// modelo
private List<Personne> personnes;
private int personneId;
// construtor
public Form() {
// inicialização da lista de pessoas
personnes = new ArrayList<Personne>();
personnes.add(new Personne(1, "dupont", "jacques"));
personnes.add(new Personne(2, "durand", "élise"));
personnes.add(new Personne(3, "martin", "jacqueline"));
}
public String retirerPersonne() {
// procura-se a pessoa selecionada
int i = 0;
for (Personne personne : personnes) {
// pessoa atual = pessoa selecionada?
if (personne.getId() == personneId) {
// elimina-se a pessoa atual da lista
personnes.remove(i);
// concluído
break;
} else {
// pessoa seguinte
i++;
}
}
// testamos na mesma página
return null;
}
// getters e setters
...
}
- linhas 18-24: o construtor inicializa a lista de pessoas da linha 14,
- linha 10: uma vez que esta lista deve permanecer ativa ao longo das consultas, o âmbito do bean é a sessão.
Quando o método [retirerPersonne] da linha 26 é executado, o campo da linha 15 foi inicializado com o ID da pessoa cujo link [Retirer] foi clicado:
<h:commandLink value="Retirer" action="#{form.retirerPersonne}">
<f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>
A baliza <f:setPropertyActionListener> permite transferir informações para o modelo. Aqui, o valor do atributo «value» é copiado para o campo do modelo identificado pelo atributo «target». Assim, o ID da pessoa atual, aquela que deve ser removida da lista de pessoas, é copiado para o campo [Form].personneId através do getter desse campo. Isto é feito antes da execução do método referenciado pelo atributo action da linha 1.
Linhas 26-43: o método [supprimerPersonne] elimina a pessoa cujo id é igual a personneId.
2.11. Exemplo mv-jsf2-09: layout de uma aplicação JSF
2.11.1. A aplicação
A aplicação mostra como definir o layout de uma aplicação JSF com duas vistas:
![]() |
A aplicação tem duas vistas:
- em [1], a página 1,
- em [2], a página 2.
É possível navegar entre as duas páginas. O que pretendemos mostrar aqui é que as páginas 1 e 2 partilham uma formatação comum, tal como se pode ver nas capturas de ecrã acima.
2.11.2. O projeto NetBeans
O projeto NetBeans da aplicação é o seguinte:
![]() |
A aplicação contém apenas páginas XHTML. Não existe nenhum modelo Java associado.
2.11.3. A página [layout.xhtml]
A página [layout.xhtml] define o formato das páginas da aplicação:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</html>
Na linha 7, surge um novo espaço de nomes «ui». Este espaço de nomes contém as balizas que permitem formatar as páginas de uma aplicação. As balizas deste espaço são utilizadas nas linhas 17, 22, 25 e 32.
A página [layout.xhtml] apresenta informações numa tabela HTML (linha 14). É possível aceder a esta página através de um navegador:
![]() |
- em [1], a URL solicitada.
O campo [2] foi gerado pelo código XHTML seguinte:
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
...
</table>
</h:form>
</h:body>
A baliza <ui:include> da linha 6 permite incluir na página um código XHTML externo. O ficheiro [entete.xhtml] é o seguinte:
<?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:h="http://java.sun.com/jsf/html">
<body>
<h2>entête</h2>
</body>
</html>
Todo o código das linhas 3 a 8 será inserido no ficheiro [layout.xhtml]. Assim, as balizas <html> e <body> serão inseridas numa baliza <td>. Isto não provoca erros. Portanto, as páginas inseridas por <ui:include> são páginas XHTML completas. Do ponto de vista visual, apenas a linha 6 terá efeito. As tags <html> e <body> estão presentes por razões sintáticas.
A área [3] foi gerada pelo seguinte código XHTML:
<h:form id="formulaire">
<table style="width: 400px">
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
...
</tr>
...
</table>
</h:form>
A baliza <ui:include> da linha 5 inclui o seguinte ficheiro [menu.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:h="http://java.sun.com/jsf/html">
<body>
<h2>menu</h2>
</body>
</html>
A área [4] foi gerada pelo código XHTML seguinte:
<h:form id="formulaire">
<table style="width: 400px">
...
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
A baliza <ui:include> da linha 6 inclui o seguinte ficheiro [basdepage.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:h="http://java.sun.com/jsf/html">
<body>
<h2>bas de page</h2>
</body>
</html>
A área [5] foi gerada pelo código XHTML seguinte:
<h:form id="formulaire">
...
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
...
</table>
</h:form>
A baliza <ui:insert> da linha 5 define uma zona denominada «conteúdo». Trata-se de uma zona que pode receber um conteúdo variável. Veremos como. Quando solicitámos a página [layout.xhtml], não foi definido qualquer conteúdo para a zona denominada «conteúdo». Neste caso, é utilizado o conteúdo da baliza <ui:insert> das linhas 4 a 6. Assim, a linha 5 é apresentada.
2.11.4. A página [page1.xhtml]
A página [layout.xhtml] não se destina a ser visualizada. Serve de modelo para as páginas [page1.xhtml] e [page2.xhtml]. Fala-se de modelo de páginas. A página [page1.xhtml] é a seguinte:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<h2>page 1</h2>
<h:commandLink value="page 2" action="page2"/>
</ui:define>
</ui:composition>
</html>
- na linha 6, utiliza-se o espaço de nomes ui,
- na linha 7, indica-se que a página está associada ao modelo [layout.xhtml] através de uma baliza <ui:composition>,
- na linha 8, esta associação faz com que cada tag <ui:define> seja associada a uma tag <ui:insert> do modelo utilizado, neste caso o [layout.xhtml]. A ligação é feita através do atributo «name» de ambas as tags. Estes devem ser idênticos.
A página apresentada é a [layout.xhtml], onde o conteúdo de cada baliza <ui:insert> é substituído pelo conteúdo da baliza <ui:define> da página solicitada. Neste caso, é como se a página apresentada fosse:
<?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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<h2>page 1</h2>
<h:commandLink value="page 2" action="page2"/>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</html>
As linhas 25-26 de [page1.xhtml] foram inseridas no lugar da baliza <ui:insert> de [layout.xml].
A página [page2.xhtml] é análoga à [page1.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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<h2>page 2</h2>
<h:commandLink value="page 1" action="page1"/>
</ui:define>
</ui:composition>
</html>
2.12. Conclusion
A análise que acabámos de fazer de JSF 2 está longe de ser exaustiva. Mas é suficiente para compreender os exemplos que se seguem. Para aprofundar o tema, consulte [ref2].
2.13. Testes com o Eclipse
Vamos mostrar como realizar testes em projetos Maven com a SpringSource Tool Suite:
![]() |
- no [1], importa-se um projeto Maven [2], que se seleciona através do botão [3]. Aqui, utiliza-se o projeto Maven [mv-jsf2-09] para o Eclipse
- em [4], o projeto importado foi corretamente reconhecido como um projeto Maven [5],
![]() |
- em [6], o projeto importado para o explorador de projetos,
- em [7], é executado num servidor Tomcat [8] [9],
![]() |
- em [10], o Tomcat 7 foi iniciado,
- no [11], a página inicial do projeto [mv-jsf2-09] [11] é apresentada num navegador interno do Eclipse.

























































































































































