2. Java Server Faces
A continuación, presentamos el marco de trabajo Java Server Faces. Se utilizará la versión 2, aunque los ejemplos muestran principalmente características de la versión 1. De la versión 2, presentaremos únicamente las características necesarias para la aplicación de ejemplo que viene a continuación.
2.1. El papel de JSF en una aplicación web
En primer lugar, situemos JSF en el desarrollo de una aplicación web. En la mayoría de los casos, esta se construirá sobre una arquitectura multicapa como la siguiente:
![]() |
- la capa [web] es la que está en contacto con el usuario de la aplicación web. Este interactúa con la aplicación web a través de páginas web visualizadas por un navegador. Es en esta capa donde se sitúa JSF y únicamente en esta capa,
- la capa [métier] implementa las reglas de gestión de la aplicación, como el cálculo de un salario o de una factura. Esta capa utiliza datos procedentes del usuario a través de la capa [web] y del SGBD a través de la capa [DAO],
- la capa [DAO] (Data Access Objects), la capa [jpa] (Java Persistence API) y el controlador JDBC gestionan el acceso a los datos del SGBD. La capa [jpa] actúa como mapeador objeto-relacional (ORM). Sirve de puente entre los objetos manipulados por la capa [DAO] y las filas y columnas de los datos de una base de datos relacional,
- la integración de las capas puede llevarse a cabo mediante un contenedor Spring o EJB3 (Enterprise Java Bean).
Los ejemplos que se ofrecen a continuación para ilustrar JSF utilizarán únicamente una capa, la capa [web]:
![]() |
Una vez adquiridos los fundamentos de JSF, crearemos aplicaciones Java EE de varias capas.
2.2. El modelo de desarrollo MVC de JSF
JSF implementa el modelo de arquitectura denominado MVC (Modelo – Vista – Controlador) de la siguiente manera:
![]() |
Esta arquitectura implementa el patrón de diseño MVC (Modelo, Vista, Controlador). El procesamiento de una solicitud de un cliente se lleva a cabo siguiendo los cuatro pasos siguientes:
- solicitud: el navegador del cliente envía una solicitud al controlador [Faces Servlet]. Este controlador gestiona todas las solicitudes de los clientes. Es la puerta de entrada a la aplicación. Es la «C» de MVC;
- procesamiento: el controlador C procesa esta solicitud. Para ello, cuenta con la ayuda de gestores de eventos específicos de la aplicación [2a]. Estos gestores pueden necesitar la ayuda de la capa de negocio [2b]. Una vez procesada la solicitud del cliente, esta puede generar diversas respuestas. Un ejemplo clásico es:
- una página de errores si la solicitud no se ha podido procesar correctamente;
- una página de confirmación en caso contrario,
- navegación: el controlador elige la respuesta (= vista) que se va a enviar al cliente. Elegir la respuesta que se va a enviar al cliente requiere varios pasos:
- seleccionar el Facelet que generará la respuesta. Es lo que se denomina la vista V, la V de MVC. Esta elección depende, por lo general, del resultado de la ejecución de la acción solicitada por el usuario;
- proporcionar a este Facelet los datos que necesita para generar dicha respuesta. De hecho, esta suele contener información calculada por el controlador. Esta información conforma lo que se denomina el modelo M de la vista, la M de MVC,
El paso 3 consiste, por tanto, en elegir una vista V y en construir el modelo M necesario para ella.
- Respuesta: el controlador C solicita a la Facelet elegida que se muestre. Esta utiliza la plantilla M preparada por el controlador C para inicializar las partes dinámicas de la respuesta que debe enviar al cliente. La forma exacta de esta respuesta puede variar: puede ser un flujo HTML, PDF, Excel, etc.
En un proyecto JSF:
- el controlador C es el servlet [javax.faces.webapp.FacesServlet]. Este se encuentra en la biblioteca [javaee.jar],
- las vistas V se implementan mediante páginas que utilizan la tecnología Facelets,
- los modelos M y los gestores de eventos se implementan mediante clases Java a las que a menudo se denomina «backing beans» o, más sencillamente, «beans».
Ahora, aclaremos la relación entre la arquitectura web MVC y la arquitectura por capas. Se trata de dos conceptos diferentes que a veces se confunden. Tomemos como ejemplo una aplicación web JSF de una sola capa:
![]() |
Si implementamos la capa [web] con JSF, tendremos una arquitectura web MVC, pero no una arquitectura multicapa. En este caso, la capa [web] se encargará de todo: presentación, lógica de negocio y acceso a los datos. Con JSF, serán los beans los que realicen esta tarea.
Ahora, consideremos una arquitectura web multicapa:
![]() |
La capa [web] puede implementarse sin un marco de trabajo y sin seguir el modelo MVC. En este caso, se trata efectivamente de una arquitectura multicapa, pero la capa web no implementa el modelo MVC.
En MVC, dijimos que el modelo M era el de la vista V, c.a.d, es decir, el conjunto de datos mostrados por la vista V. A menudo se ofrece otra definición del modelo M de MVC:
![]() |
Muchos autores consideran que lo que se encuentra a la derecha de la capa [web] constituye el modelo M del MVC. Para evitar ambigüedades, hablaremos:
- del modelo del dominio cuando nos refiramos a todo lo que se encuentra a la derecha de la capa [web],
- del modelo de la vista cuando nos refiramos a los datos mostrados por una vista V.
En lo sucesivo, el término «modelo M» se referirá exclusivamente al modelo de una vista V.
2.3. Ejemplo mv-jsf2-01: los elementos de un proyecto JSF
Los primeros ejemplos se limitarán únicamente a la capa web implementada con JSF 2:
![]() |
Una vez adquiridos los conceptos básicos, estudiaremos ejemplos más complejos con arquitecturas multicapa.
2.3.1. Creación del proyecto
Generamos nuestro primer proyecto JSF2 con NetBeans 7.
![]() |
- En [1], crear un nuevo proyecto,
- en [2], seleccionamos la categoría [Maven] y el tipo de proyecto [Web Application],
![]() |
- en [3], seleccionar la carpeta principal de la carpeta del nuevo proyecto,
- en [4], asignar un nombre al proyecto,
- en [5], elegir un servidor. Con NetBeans 7, se puede elegir entre los servidores Apache Tomcat y GlassFish. La diferencia entre ambos es que GlassFish admite los EJB (Enterprise Java Bean) y Tomcat no. Nuestros ejemplos JSF no van a utilizar EJB. Por lo tanto, aquí podemos elegir cualquier servidor,
- en [6], elegimos la versión Java EE 6 Web,
- en [7], el proyecto generado.
Analicemos los elementos del proyecto y expliquemos la función de cada uno.
![]() |
- en [1]: las diferentes ramas del proyecto:
- [Web Pages]: contendrá las páginas web (.xhtml, .jsp, .html), los recursos (imágenes, documentos diversos), la configuración de la capa web, así como la del marco de trabajo JSF;
- [Source packages]: las clases Java del proyecto;
- [Dependencies]: los archivos .jar necesarios para el proyecto y gestionados por el framework Maven;
- [Java Dependencies]: los archivos .jar necesarios para el proyecto y no gestionados por el framework Maven;
- [Project Files]: archivo de configuración de Maven y NetBeans,
![]() |
- en [2]: la rama [Web Pages],
que contiene la siguiente 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>
Es una página web que muestra la cadena de caracteres «Hello World» en letra grande.
El archivo [META-INF/context.xml] es el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/mv-jsf2-01"/>
La línea 2 indica que el contexto de la aplicación (o su nombre) es /mv-jsf2-01. Esto significa que las páginas web del proyecto se solicitarán a través de un URL con el formato http://machine:port/mv-jsf2-01/page. El contexto es, por defecto, el nombre del proyecto. No tendremos que modificar este archivo.
![]() |
- en [3], la rama [Source Packages],
Esta rama contiene el código fuente de las clases Java del proyecto. Aquí no hay ninguna clase. NetBeans ha generado un paquete por defecto que se puede eliminar: [4].
![]() |
- en [5], la rama [Dependencies],
Esta rama muestra todas las bibliotecas necesarias para el proyecto y gestionadas por Maven. Todas las bibliotecas que aparecen aquí serán descargadas automáticamente por Maven. Por eso un proyecto Maven necesita conexión a Internet. Las bibliotecas descargadas se almacenarán localmente. Si otro proyecto necesita una biblioteca que ya está presente localmente, esta no se descargará. Veremos que esta lista de bibliotecas, así como los repositorios donde se pueden encontrar, se definen en el archivo de configuración del proyecto Maven.
![]() |
- en [6], las bibliotecas necesarias para el proyecto y que no gestiona Maven,
![]() |
- en [7], los archivos de configuración del proyecto Maven:
- [nb-configuration.xml] es el archivo de configuración de NetBeans. No nos ocuparemos de él.
- [pom.xml]: el archivo de configuración de Maven. POM significa «Project Object Model». En ocasiones tendremos que intervenir directamente en este archivo.
El archivo [pom.xml] generado es el siguiente:
<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>
- Las líneas 5-8 definen el objeto (artefacto) Java que va a crear el proyecto Maven. Esta información procede del asistente que se utilizó al crear el proyecto:
![]() |
Un objeto Maven se define mediante cuatro propiedades:
- [groupId]: una información que se asemeja a un nombre de paquete. Así, las bibliotecas del marco Spring tienen groupId=org.springframework, las del marco JSF tienen groupId=javax.faces,
- [artifactId]: el nombre del objeto Maven. En el grupo [org.springframework] se encuentran, por tanto, los siguientes artifactId: spring-context, spring-core, spring-beans, ... En el grupo [javax.faces] se encuentra el artifactId jsf-api,
- [version]: número de versión del artefacto Maven. Así, el artefacto org.springframework.spring-core tiene las siguientes versiones: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
- [packaging]: el formato que adopta el artefacto, normalmente war o jar.
Por lo tanto, nuestro proyecto Maven generará un [war] (línea 8) dentro del grupo [istia.st] (línea 5), denominado [mv-jsf2-01] (línea 6) y con la versión [1.0-SNAPSHOT] (línea 7). Estos cuatro datos deben definir de forma única un artefacto de Maven.
Las líneas 17-24 enumeran las dependencias del proyecto Maven, es decir, la lista de bibliotecas necesarias para el proyecto. Cada biblioteca se define mediante cuatro datos (groupId, artifactId, versión, empaquetado). Cuando falta el dato packaging, como en este caso, se utiliza el archivo JAR packaging. Se añade otro dato, «scope», que determina en qué momentos del ciclo de vida del proyecto se necesita la biblioteca. El valor por defecto es «compile», lo que indica que la biblioteca es necesaria tanto para la compilación como para la ejecución. El valor «provided» significa que la biblioteca es necesaria durante la compilación, pero no durante la ejecución. En este caso, durante la ejecución, la proporcionará el servidor Tomcat 7.
2.3.2. Ejecución del proyecto
Ejecutamos el proyecto:
![]() |
En [1], se ejecuta el proyecto Maven. A continuación, se inicia el servidor Tomcat, si aún no lo estaba. También se abre un navegador y se solicita la página URL del contexto del proyecto [2]. Como no se solicita ningún documento, se utiliza la página index.html, index.jsp, index.xhtml si existe. En este caso, será la página [index.jsp].
2.3.3. El sistema de archivos de un proyecto Maven
![]() |
- [1]: el sistema de archivos del proyecto se encuentra en la pestaña [Files],
- [2]: los códigos fuente de Java se encuentran en la carpeta [src / main / java],
- [3]: las páginas web se encuentran en la carpeta [src / main / webapp],
- [4]: la carpeta [target] se crea durante la compilación del proyecto,
- [5]: en este caso, la compilación del proyecto ha creado un archivo [mv-jsf2-01-1.0-SNAPSHOT.war]. Este es el archivo que ha sido ejecutado por el servidor Tomcat.
2.3.4. Configurar un proyecto para JSF
Nuestro proyecto actual no es un proyecto JSF. Le faltan las bibliotecas del marco de trabajo JSF. Para convertir el proyecto actual en un proyecto JSF, se procede de la siguiente manera:
![]() |
- en [1], se accede a las propiedades del proyecto,
- en [2], se selecciona la categoría [Frameworks],
- en [3], se añade un framework,
![]() |
- En [4], seleccionamos Java Server Faces,
- en [5], NetBeans nos propone la versión 2.1 del framework. La aceptamos,
- en [6], el proyecto se amplía con nuevas dependencias.
El archivo [pom.xml] se ha actualizado para reflejar esta nueva configuración:
<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>
En las líneas 14-33 se han añadido nuevas dependencias. Maven las descarga automáticamente. Las busca en lo que se conoce como repositorios. Se utiliza automáticamente el repositorio central (Central Repository). Se pueden añadir otros repositorios mediante la etiqueta <repository>. Aquí se han añadido dos repositorios:
- líneas 46-51: un repositorio para la biblioteca JSF 2,
- líneas 52-57: un repositorio para la biblioteca JSTL 1.1.
El proyecto también se ha enriquecido con una nueva página web:
![]() |
La página [index.HTML] es la siguiente:
<?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>
Aquí tenemos un archivo XML (línea 1). En él se encuentran las etiquetas de HTML, pero en formato XML. A esto se le denomina XHTML. La tecnología utilizada para crear páginas web con JSF 2 se llama Facelets. Por eso, a veces se denomina a la página XHTML una página Facelet.
Las líneas 3 y 4 definen la etiqueta <html> con los espacios de nombres XML (xmlns=XML Name Space).
- La línea 3 define el espacio de nombres principal http://www.w3.org/1999/xhtml,
- la línea 4 define el espacio de nombres http://java.sun.com/jsf/html de las etiquetas HTML. Estas llevarán el prefijo «h:», tal y como indica xmlns:h. Estas etiquetas se encuentran en las líneas 5, 7, 8 y 10.
Al encontrar la declaración de un espacio de nombres, el servidor web explorará las carpetas [META-INF] y Classpath de la aplicación, en busca de archivos con la extensión .tld (TagLib Definition). En este caso, los encontrará en el archivo [jsf-impl.jar] y [1,2]:
![]() |
Analicemos el archivo [3] y el archivo [HTML_basic.tld]:
- en la línea 19, la URI de la biblioteca de etiquetas,
- en la línea 16, su nombre abreviado.
Las definiciones de las diferentes etiquetas <h:xx> se encuentran en este archivo. Estas etiquetas están gestionadas por clases Java que también se encuentran en el artefacto [jsf-impl.jar].
Volvamos a nuestro proyecto JSF. Se ha ampliado con una nueva rama:
![]() |
La rama [Other Sources] [1] contiene los archivos que deben estar en la ruta de clases (Classpath) del proyecto y que no son código Java. Este es el caso de los archivos de mensajes en JSF. Hemos visto que, si no se añade el framework JSF al proyecto, esta rama no aparece. Para crearla, basta con crear la carpeta [src / main / resources] [3] en la pestaña [Files] [2].
Por último, ha aparecido una nueva carpeta en la rama [Web Pages]:
![]() |
Se ha creado la carpeta [WEB-INF], que contiene el archivo [web.xml] . Este archivo configura la aplicación 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>
- Las líneas 7-10 definen un servlet, c.a.d, una clase Java capaz de procesar las solicitudes de los clientes. Una aplicación JSF funciona de la siguiente manera:
![]() |
Esta arquitectura implementa el patrón de diseño MVC (Modelo, Vista, Controlador). Recordemos lo que ya se ha mencionado anteriormente. El procesamiento de una solicitud de un cliente se lleva a cabo siguiendo los cuatro pasos siguientes:
1 - solicitud: el navegador del cliente envía una solicitud al controlador [Faces Servlet]. Este controlador gestiona todas las solicitudes de los clientes. Es la puerta de entrada a la aplicación. Es la «C» de MVC,
2 - procesamiento: el controlador C procesa esta solicitud. Para ello, cuenta con la ayuda de gestores de eventos específicos de la aplicación [2a]. Estos gestores pueden necesitar la ayuda de la capa de negocio [2b]. Una vez procesada la solicitud del cliente, esta puede generar diversas respuestas. Un ejemplo clásico es:
- una página de errores si la solicitud no se ha podido procesar correctamente;
- una página de confirmación en caso contrario,
3 - navegación - el controlador elige la respuesta (= vista) que se va a enviar al cliente. Elegir la respuesta que se va a enviar al cliente requiere varios pasos:
- seleccionar el Facelet que generará la respuesta. Es lo que se denomina la vista V, la V de MVC. Esta elección depende, por lo general, del resultado de la ejecución de la acción solicitada por el usuario;
- proporcionar a este Facelet los datos que necesita para generar dicha respuesta. De hecho, esta suele contener información calculada por el controlador. Esta información conforma lo que se denomina el modelo M de la vista, la M de MVC,
Por lo tanto, el paso 3 consiste en elegir una vista V y construir el modelo M necesario para ella.
4 - respuesta - el controlador C solicita a la Facelet elegida que se muestre. Esta utiliza la plantilla M preparada por el controlador C para inicializar las partes dinámicas de la respuesta que debe enviar al cliente. La forma exacta de esta respuesta puede variar: puede ser un flujo HTML, PDF, Excel, etc.
En un proyecto JSF:
- el controlador C es el servlet [javax.faces.webapp.FacesServlet],
- las vistas V se implementan mediante páginas que utilizan la tecnología Facelets,
- los modelos M y los gestores de eventos se implementan mediante clases Java a las que a menudo se denomina «backing beans» o, más sencillamente, «Beans».
Volvamos al contenido del archivo [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>
- líneas 12-15: la etiqueta <servlet-mapping> sirve para asociar un servlet a un URL solicitado por el navegador del cliente. Aquí se indica que los archivos URL con el formato [/faces/*] deben ser procesados por el servlet denominado [Faces Servlet]. Este se define en las líneas 7-10. Como no hay ninguna otra etiqueta <servlet-mapping> en el archivo, esto significa que el servlet [Faces Servlet] solo procesará los URL con el formato [/faces/*]. Hemos visto que el contexto de la aplicación se llama [/mv-jsf2-01]. Por lo tanto, los URL de los clientes procesados por el servlet [Faces Servlet] tendrán el formato [http://machine:port/mv-jsf2-01/faces/*]. Las páginas .html y .jsp serán procesadas por defecto por el propio contenedor de servlets, y no por una servlet concreta. De hecho, el contenedor de servlets sabe cómo gestionarlas,
- líneas 7-10: definen el servlet [Faces Servlet]. Dado que todas las solicitudes URL aceptadas se redirigen a él, es el controlador C del modelo MVC,
- línea 10: indica que el servlet debe cargarse en memoria nada más iniciarse el servidor web. Por defecto, un servlet solo se carga al recibir la primera solicitud que se le envía,
- líneas 3-6: definen un parámetro destinado al servlet [Faces Servlet]. El parámetro javax.faces.PROJECT_STAGE define la fase en la que se encuentra el proyecto en ejecución. En la fase de desarrollo, el servlet [Faces Servlet] muestra mensajes de error útiles para la depuración. En la fase de producción, estos mensajes ya no se muestran,
- líneas 17-19: duración en minutos de una sesión. Un cliente interactúa con la aplicación mediante una sucesión de ciclos de solicitud/respuesta. Cada ciclo utiliza una conexión TCP-IP propia, nueva en cada ciclo. Por lo tanto, si un cliente C realiza dos solicitudes D1 y D2, el servidor S no tiene forma de saber que ambas solicitudes pertenecen al mismo cliente C. El servidor S no dispone de la memoria del cliente. Así lo establece el protocolo HTTP utilizado (Protocolo de transporte HyperText): el cliente se comunica con el servidor mediante una sucesión de ciclos de solicitud del cliente/respuesta del servidor, utilizando en cada ocasión una nueva conexión TCP-IP. Se trata de un protocolo sin estado. En otros protocolos, como por ejemplo el FTP (Protocolo de transferencia de archivos), el cliente C utiliza la misma conexión durante toda la duración de su diálogo con el servidor S. Por lo tanto, una conexión está vinculada a un cliente concreto. El servidor S siempre sabe con quién está tratando. Para poder reconocer que una solicitud pertenece a un cliente determinado, el servidor web puede utilizar la técnica de la sesión:
- al recibir la primera solicitud de un cliente, el servidor S le envía la respuesta esperada junto con un token, una secuencia de caracteres aleatoria y única para ese cliente;
- en cada solicitud posterior, el cliente C devuelve al servidor S el token que ha recibido, lo que permite al servidor S reconocerlo.
La aplicación tiene ahora la posibilidad de solicitar al servidor que memorice información asociada a un cliente concreto. A esto se le denomina «sesión de cliente». La línea 18 indica que la duración de una sesión es de 30 minutos. Esto significa que, si un cliente C no realiza una nueva solicitud durante 30 minutos, su sesión se elimina y la información que contenía se pierde. En su próxima solicitud, todo sucederá como si se tratara de un nuevo cliente y se iniciará una nueva sesión,
- líneas 21-23: la lista de páginas que se mostrarán cuando el usuario solicite el contexto sin especificar una página concreta, por ejemplo, aquí [http://machine:port/mv-jsf2-01]. En este caso, el servidor web (no el servlet) comprueba si la aplicación ha definido una etiqueta <welcome-file-list>. Si es así, muestra la primera página que encuentra en la lista. Si no existe, muestra la segunda página, y así sucesivamente hasta encontrar una página existente. En este caso, cuando el cliente solicita URL [http://machine:port/mv-jsf2-01], se le mostrará URL [http://machine:port/mv-jsf2-01/index.xhtml].
2.3.5. Ejecutar el proyecto
Al ejecutar el nuevo proyecto, el resultado que se obtiene en el navegador es el siguiente:
![]() |
- en [1], se ha solicitado el contexto sin especificar ningún documento,
- en [2], tal y como se ha explicado, se sirve la página de inicio (welcome-file) [index.xhtml].
Puede que nos despierte la curiosidad echar un vistazo al código fuente recibido [3]:
Se ha recibido HTML. Todas las etiquetas <h:xx> de index.xhtml se han traducido a sus equivalentes en HTML.
2.3.6. El repositorio local de Maven
Ya hemos dicho que Maven descarga las dependencias necesarias para el proyecto y las almacena localmente. Podemos explorar este repositorio local:
![]() |
- en [1], seleccionamos la opción [Window / Other / Maven Repository Browser],
- en [2], se abre una pestaña [Maven Repositories],
- en [3], contiene dos ramas, una para el repositorio local y otra para el repositorio central. Este último es gigantesco. Para visualizar su contenido, hay que actualizar su índice [4]. Esta actualización dura varias decenas de minutos.
![]() |
- en [5], las bibliotecas del repositorio local,
- en [6], encontramos una rama [istia.st] que se corresponde con el [groupId] de nuestro proyecto,
- En [7] se accede a las propiedades del repositorio local;
- en [8], aparece la ruta del repositorio local. Es útil conocerla porque, en ocasiones (aunque sea raro), Maven deja de utilizar la última versión del proyecto. Realizamos modificaciones y observamos que no se tienen en cuenta. En ese caso, podemos eliminar manualmente la rama del repositorio local correspondiente a nuestro [groupId]. Esto obliga a Maven a volver a crear la rama a partir de la última versión del proyecto.
2.3.7. Buscar un artefacto con Maven
Ahora vamos a aprender a buscar un artefacto con Maven. Partamos de la lista de dependencias actuales del archivo [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>
Las líneas 13-40 definen las dependencias y las líneas 45-58 los repositorios donde se pueden encontrar, además del repositorio central, que siempre se utiliza. Vamos a modificar las dependencias para utilizar las bibliotecas en su versión más reciente.
![]() |
En primer lugar, eliminamos las dependencias actuales [1]. A continuación, se modifica el archivo [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>
...
<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>
En las líneas 5-12, las dependencias eliminadas ya no aparecen en [pom.xml]. Ahora, busquémoslas en los repositorios de Maven.
![]() |
- En [1], se añade una dependencia al proyecto;
- en [2], hay que especificar información sobre el artefacto buscado (groupId, artifactId, versión, empaquetado (tipo) y ámbito). Empezamos especificando el [groupId] [3],
- en [4], escribimos [espace] para que se muestre la lista de artefactos posibles. Aquí aparecen [jsf-api] y [jsf-impl]. Elegimos [jsf-api],
- en [5], siguiendo el mismo procedimiento, elegimos la versión más reciente. El tipo de empaquetado es jar.
Procedemos de esta forma con todos los artefactos:
![]() | ![]() | ![]() | ![]() |
![]() |
En [6], las dependencias añadidas aparecen en el proyecto. El archivo [pom.xml] refleja estos cambios:
<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>
Supongamos ahora que no conocemos el [groupId] del artefacto que deseamos. Por ejemplo, queremos utilizar Hibernate como ORM (mapeador objeto-relacional) y eso es todo lo que sabemos. En ese caso, podemos ir a la página web [http://mvnrepository.com/]:
![]() |
En [1], podemos introducir palabras clave. Escribamos hibernate y iniciemos la búsqueda.
![]() |
- En [2], seleccionemos [groupId], org.hibernate y [artifactId], hibernate-core,
- en [3], elegimos la versión 4.1.2-Final,
- en [4], obtenemos el código Maven que hay que pegar en el archivo [pom.xml]. Lo hacemos.
<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 el archivo [pom.xml]. A continuación, Maven comienza a descargar las nuevas dependencias. El proyecto evoluciona de la siguiente manera:
![]() |
- en [5], la dependencia [hibernate-core-4.1.2-Final]. En el repositorio donde se ha encontrado, este [artifactId] también está descrito por un archivo [pom.xml]. Este archivo se ha leído y Maven ha detectado que el [artifactId] tenía dependencias. También las descarga. Hará lo mismo con cada [artifactId] descargado. Al final, en el archivo [6] encontramos dependencias que no habíamos solicitado directamente. Se identifican mediante un icono diferente al del archivo principal [artifactId].
En este documento, utilizamos Maven principalmente por esta característica. Esto nos evita tener que conocer todas las dependencias de una biblioteca que queremos utilizar. Dejamos que Maven se encargue de gestionarlas. Además, al compartir un archivo [pom.xml] entre desarrolladores, nos aseguramos de que todos utilicen las mismas bibliotecas.
En los ejemplos que siguen, nos limitaremos a proporcionar el archivo [pom.xml] utilizado. El lector solo tendrá que utilizarlo para encontrarse en las mismas condiciones que el documento. Por otra parte, los proyectos Maven son compatibles con los principales entornos de desarrollo Java (Eclipse, NetBeans, IntelliJ, JDeveloper). Así pues, el lector podrá utilizar su entorno favorito para probar los ejemplos.
2.4. Ejemplo mv-jsf2-02: gestor de eventos – internacionalización – navegación entre páginas
2.4.1. La aplicación
La aplicación es la siguiente:
![]() |
- en [1], la página de inicio de la aplicación,
- en [2], dos enlaces para cambiar el idioma de las páginas de la aplicación,
- en [3], un enlace de navegación a otra página,
- al hacer clic en [3], se muestra la página [4],
- el enlace [5] permite volver a la página de inicio.
![]() |
- En la página de inicio [1], los enlaces [2] permiten cambiar de idioma,
- en [3], la página de inicio en inglés.
2.4.2. El proyecto NetBeans
Se generará un nuevo proyecto web tal y como se explica en el apartado 2.3.1. Se denominará mv-jsf2-02:
![]() |
- en [1], el proyecto generado,
- en [2], se han eliminado el paquete [istia.st.mvjsf202] y el archivo [index.jsp],
- En [3], se han añadido dependencias de Maven mediante el siguiente archivo [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>
Las dependencias añadidas son las del framework JSF. Basta con copiar las líneas anteriores en el archivo [pom.xml] en lugar de las dependencias anteriores.
![]() |
- en [4, 5]: se crea una carpeta [src / main / resources] en la pestaña [Files],
- en [6], en la pestaña [Projects], se ha creado la rama [Other Sources].
Ahora tenemos un proyecto JSF. En él crearemos diferentes tipos de archivos:
- páginas web en formato XHTML,
- clases Java,
- archivos de mensajes,
- el archivo de configuración del proyecto JSF.
Veamos cómo crear cada tipo de archivo:
![]() |
- en [1], creamos una página JSF
- en [2], creamos una página [index.xhtml] en formato [Facelets] [3],
- en [4], se han creado dos archivos: [index.xhtml] y [WEB-INF / web.xml].
El archivo [web.xml] configura la aplicación JSF. Es el siguiente:
<?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>
Ya hemos comentado este archivo en el apartado 2.3.4. Recordemos sus principales propiedades:
- todos los URL del tipo faces/* son procesados por el servlet [javax.faces.webapp.FacesServlet],
- la página [index.xhtml] es la página de inicio de la aplicación.
El archivo [index.xhtml] creado es el siguiente:
<?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>
Ya hemos visto este archivo en el apartado 2.3.4.
Ahora creemos una clase Java:
![]() |
- En [1], creamos una clase Java en la rama [Source Packages],
- en [2], le damos un nombre y la colocamos en un paquete [3],
- en [4], la clase creada aparece en el proyecto.
El código de la clase creada es un esqueleto de clase:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package istia.st;
/**
*
* @author Serge Tahé
*/
public class Form {
}
Por último, creamos un archivo de mensajes:
- en [1], creación de un archivo [Properties],
- en [2], se indica el nombre del archivo y en [3] su carpeta,
- en [4], se ha creado el archivo [messages.properties].
A veces, es necesario crear el archivo [WEB-INF/faces-config.xml] para configurar el proyecto JSF. Este archivo era obligatorio con JSF 1. Es opcional con JSF 2. Sin embargo, es necesario si el sitio JSF está internacionalizado. Este será el caso más adelante. Por ello, a continuación mostramos cómo crear este archivo de configuración.
![]() |
- En [1], creamos el archivo de configuración JSF,
- en [2], le asignamos un nombre y, en [3], su carpeta,
- en [4], el archivo creado.
El archivo [faces-config.xml] creado es el siguiente:
<?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>
La etiqueta raíz es <faces-config>. El cuerpo de esta etiqueta está vacío. Tendremos que completarlo.
Ahora disponemos de todos los elementos para crear un proyecto JSF. En los ejemplos que siguen, presentamos el proyecto JSF completo y, a continuación, detallamos sus elementos uno por uno. A continuación, presentamos un proyecto para explicar los conceptos:
- gestión de eventos de un formulario,
- la internacionalización de las páginas de un sitio web JSF,
- de la navegación entre páginas.
El proyecto [mv-jsf2-02] queda como se muestra a continuación. El lector puede encontrarlo en el sitio web de ejemplos (véase el apartado 1.2).
![]() |
- en [1], de los archivos de configuración del proyecto JSF,
- en [2], las páginas JSF del proyecto,
- en [3], la única clase Java,
- en [4], los archivos de mensajes.
2.4.3. La página [index.xhtml]
El archivo [index.xhtml] [1] envía la página [2] al navegador del cliente:
![]() |
El código que genera esta página es el siguiente:
<?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>
- líneas 7-9: los espacios de nombres o bibliotecas de etiquetas utilizados por la página. Las etiquetas con el prefijo «h» son etiquetas de HTML, mientras que las etiquetas con el prefijo «f» son propias de JSF,
- Línea 10: la etiqueta <f:view> sirve para delimitar el código que debe procesar el motor JSF, en el que aparecen las etiquetas <f:xx>. El atributo «locale» permite especificar el idioma de visualización de la página. En este caso, utilizaremos dos: el inglés y el francés. El valor del atributo «local» se expresa en forma de una expresión EL (Expression Language) #{expresión}. La forma de la expresión puede variar. La expresaremos con mayor frecuencia en forma de bean['clé'] o bean.champ. En nuestros ejemplos, bean será una clase Java o un archivo de mensajes. Con JSF 1, estos beans debían declararse en el archivo [faces-config.xml]. Con JSF 2, esto ya no es obligatorio para las clases Java. Ahora se pueden utilizar anotaciones que convierten una clase Java en un bean reconocido por JSF 2. El archivo de mensajes, por su parte, debe declararse en el archivo de configuración [faces-config.xml].
2.4.4. El bean [changeLocale]
En la expresión EL #{changeLocale.locale}:
- changeLocale es el nombre de un bean, en este caso la clase Java ChangeLocale,
- «locale» es un campo de la clase ChangeLocale. La expresión se evalúa mediante [ChangeLocale].getLocale(). En general, la expresión #{bean.champ} se evalúa como [Bean].getChamp(), donde [Bean] es una instancia de la clase Java a la que se le ha asignado el nombre bean y getChamp, el getter asociado al campo champ del bean.
La clase ChangeLocale es la siguiente:
package utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// la configuración regional de las páginas
private String locale="fr";
public ChangeLocale() {
}
...
public String getLocale() {
return locale;
}
}
- línea 11: el campo «local»,
- línea 17: su getter,
- línea 7: la anotación ManagedBean convierte la clase Java ChangeLocale en un bean reconocido por JSF. Un bean se identifica mediante un nombre. Este puede fijarse mediante el atributo name de la anotación: @ManagedBean(name= "xx "). Si no se incluye el atributo name, se utiliza el nombre de la clase, convirtiendo su primera letra en minúscula. Por lo tanto, el nombre del bean ChangeLocale es changeLocale. Hay que tener en cuenta que la anotación ManagedBean pertenece al paquete javax.faces.bean.ManagedBean y no al paquete javax.annotations.ManagedBean.
- Línea 8: la anotación SessionScoped establece el ámbito del bean. Hay varios. Utilizaremos habitualmente los tres siguientes:
- RequestScoped: la vida útil del bean es la del ciclo de solicitud del navegador/respuesta del servidor. Si para procesar una nueva solicitud del mismo navegador o de otro se vuelve a necesitar este bean, se volverá a instanciar,
- SessionScoped: el ciclo de vida del bean es el de la sesión de un cliente concreto. El bean se crea inicialmente para atender una de las solicitudes de ese cliente. A continuación, permanecerá en memoria durante la sesión de dicho cliente. Este tipo de bean suele almacenar datos específicos de un cliente determinado. Se eliminará cuando se cierre la sesión del cliente,
- ApplicationScoped: la vida útil del bean es la misma que la de la propia aplicación. Un bean con esta vida útil suele ser compartido por todos los clientes de la aplicación. Por lo general, se inicializa al inicio de la aplicación.
Estas anotaciones se encuentran en dos paquetes: javax.enterprise.context.SessionScoped (JSF 2) y javax.faces.bean.SessionScoped (JSF 1). En este caso, utilizamos el paquete JSF 2. Esto nos obliga a crear el archivo [WEB-INF / beans.xml]:
![]() |
Este archivo lo genera automáticamente NetBeans al importar el paquete [javax.enterprise.context.SessionScoped]. Su contenido es el siguiente:
<?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>
Aparte de la etiqueta raíz <beans>, el archivo está vacío. Con eso basta. Solo es necesaria su presencia.
Por último, cabe señalar que la clase [ChangeLocale] implementa la interfaz [Serializable]. Esto es obligatorio para los beans de ámbito Session, ya que el servidor web podría tener que serializarlos en archivos. Más adelante volveremos sobre el bean [ChangeLocale].
2.4.5. El archivo de mensajes
Volvamos al archivo [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>
- línea 8: la etiqueta <h:outputText> muestra el valor de una expresión EL #{msg['welcome.titre']} de la forma #{bean['champ']}. «bean» puede ser el nombre de una clase Java o el de un archivo de mensajes. En este caso, se trata del nombre de un archivo de mensajes. Este último debe declararse en el archivo de configuración [faces-config.xml]. El bean «msg» se declara de la siguiente manera:
<?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>
- líneas 11-18: la etiqueta <application> sirve para configurar la aplicación JSF,
- líneas 12-17: la etiqueta <resource-bundle> sirve para definir recursos para la aplicación, en este caso un archivo de mensajes,
- líneas 13-15: la etiqueta <base-name> define el nombre del archivo de mensajes,
- línea 14: el archivo se llamará messages[_CodeLangue][_CodePays].properties. La etiqueta <base-name> solo define la primera parte del nombre. El resto queda implícito. Puede haber varios archivos de mensajes, uno por idioma:
![]() |
- en [1], se ven cuatro archivos de mensajes que corresponden al nombre base «messages» definido en [faces-config.xml],
- messages_fr.properties: contiene los mensajes en francés (código fr);
- messages_en.properties: contiene los mensajes en inglés (código en);
- messages_es_ES.properties: contiene los mensajes en español (código es) de España (código ES). Existen otros tipos de español, por ejemplo, el de Bolivia (es_BO);
- messages.properties: lo utiliza el servidor cuando el idioma del equipo en el que se ejecuta no tiene ningún archivo de mensajes asociado. Se utilizaría, por ejemplo, si la aplicación se ejecutara en un equipo en Alemania, donde el idioma por defecto sería el alemán (de). Como no existe el archivo [messages_de.properties], la aplicación utilizaría el archivo [messages.properties],
- en [2]: los códigos de idioma siguen una norma internacional,
- en [3]: lo mismo ocurre con los códigos de país.
El nombre del archivo de mensajes se define en la línea 14. Se buscará en el archivo Classpath del proyecto. Si se encuentra dentro de un paquete, este debe definirse en la línea 14, por ejemplo, ressources.messages, si el archivo [messages.properties] se encuentra en la carpeta [ressources] del Classpath. Dado que el nombre de la línea 14 no incluye ningún paquete, el archivo [messages.properties] debe colocarse en la raíz de la carpeta [src / main / resources]:
![]() |
En [1], en la pestaña [Projects] del proyecto NetBeans, el archivo [messages.properties] se muestra como una lista de las diferentes versiones de mensajes definidas. Las versiones se identifican mediante una secuencia de entre uno y tres códigos [codeLangue_codePays_codeVariante]. En [1], solo se ha utilizado el código [codeLangue]: «en» para el inglés y «fr» para el francés. Cada versión constituye un archivo independiente en el sistema de archivos.
En nuestro ejemplo, el archivo de mensajes en francés [messages_fr.properties] contendrá los siguientes 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
Por su parte, el archivo [messages_en.properties] tendrá el siguiente contenido:
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
El archivo [messages.properties] es idéntico al archivo [messages_en.properties]. Al final, el navegador del cliente podrá elegir entre páginas en francés y páginas en inglés.
Volvamos al archivo [faces-config.xml], que declara el archivo de mensajes:
...
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
La línea 8 indica que una línea del archivo de mensajes se referenciará mediante el identificador «msg» en las páginas JSF. Este identificador se utiliza en el archivo [index.xhtml] que hemos analizado:
<?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>
La etiqueta <h:outputText> de la línea 8 mostrará el valor del mensaje (presencia del identificador «msg») de la clave welcome.titre. Este mensaje se busca y se encuentra en el archivo [messages.properties] del idioma activo en ese momento. Por ejemplo, para el francés:
welcome.titre=Tutoriel JSF (JavaServer Faces)
Un mensaje tiene el formato clave=valor. La línea 8 del archivo [index.xhtml] queda así tras evaluar la expresión #{msg['welcome.titre']}:
<title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>
Este mecanismo de archivos de mensajes permite cambiar fácilmente el idioma de las páginas de un proyecto JSF. Se habla de internacionalización del proyecto o, más a menudo, de su abreviatura «i18n», porque la palabra «internacionalización» empieza por «i» y termina por «n», y hay 18 letras entre la «i» y la «n».
2.4.6. El formulario
Sigamos explorando el contenido del archivo [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>
- líneas 11-18: la etiqueta <h:form> introduce un formulario. Un formulario suele estar compuesto por:
- etiquetas de campos de entrada (texto, botones de opción, casillas de selección, listas de elementos, etc.);
- etiquetas de validación del formulario (botones, enlaces). Es a través de un botón o un enlace como el usuario envía los datos introducidos al servidor, que se encargará de procesarlos,
Cualquier etiqueta JSF puede identificarse mediante un atributo id. En la mayoría de los casos, se puede prescindir de él, y así se ha hecho en la mayoría de las etiquetas JSF utilizadas aquí. No obstante, este atributo resulta útil en determinados casos. En la línea 17, el formulario se identifica mediante el identificador «formulaire». En este ejemplo, el identificador del formulario no se utilizará y podría haberse omitido.
- Líneas 18-21: la etiqueta <h:panelGrid> define aquí una tabla HTML de dos columnas. Da lugar a la etiqueta HTML <table>,
- el formulario cuenta con tres enlaces que activan su procesamiento, en las líneas 19, 20 y 23. La etiqueta <h:commandLink> tiene al menos dos atributos:
- value: el texto del enlace;
- action: o bien una cadena de caracteres C, o bien la referencia a un método que, tras su ejecución, devuelve la cadena de caracteres C. Esta cadena de caracteres C puede ser:
- el nombre de una página JSF del proyecto,
- o bien un nombre definido en las reglas de navegación del archivo [faces-config.xml] y asociado a una página JSF del proyecto;
En ambos casos, se muestra la página JSF una vez que se ha ejecutado la acción definida por el atributo «action».
Analicemos el funcionamiento del procesamiento de formularios con el ejemplo del enlace de la línea 13:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>}"/>
En primer lugar, se utiliza el archivo de mensajes para sustituir la expresión #{msg['welcome.langue1']} por su valor. Tras la evaluación, la etiqueta queda así:
<h:commandLink value="Français" action="#{changeLocale.setFrenchLocale}"/>}"/>
La traducción HTML de esta etiqueta JSF será la siguiente:
<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>
lo que dará lugar al siguiente aspecto visual:
![]() |
Cabe destacar el atributo «onclick» de la etiqueta HTML <a>. Cuando el usuario haga clic en el enlace [Français], se ejecutará código JavaScript. Este código está integrado en la página que ha recibido el navegador y es el propio navegador el que lo ejecuta. El código JavaScript se utiliza ampliamente en las tecnologías JSF y AJAX (Asynchronous JavaScript and XML). Por lo general, su objetivo es mejorar la usabilidad y la capacidad de respuesta de las aplicaciones web. Normalmente lo generan automáticamente diversas herramientas de software, por lo que no es necesario comprenderlo. Sin embargo, en ocasiones un desarrollador puede verse en la necesidad de añadir código JavaScript a sus páginas JSF. En ese caso, es necesario conocer JavaScript.
En este caso, no es necesario comprender el código JavaScript generado para la etiqueta JSF <h:commandLink>. Sin embargo, cabe destacar dos puntos:
- el código JavaScript utiliza el identificador de formulario que le hemos asignado a la etiqueta JSF <h:form>,
- JSF genera identificadores automáticos para todas las etiquetas en las que no se ha definido el atributo id. Aquí vemos un ejemplo: j_idt8. Asignar un identificador claro a las etiquetas permite comprender mejor el código JavaScript generado si fuera necesario. Esto ocurre, sobre todo, cuando el desarrollador tiene que añadir él mismo código JavaScript que manipula los componentes de la página. En ese caso, necesita conocer los identificadores «id» de dichos componentes.
¿Qué ocurrirá cuando el usuario haga clic en el enlace [Français] de la página anterior? Analicemos la arquitectura de una aplicación JSF:
![]() |
El controlador [Faces Servlet] recibirá la solicitud del navegador del cliente con el siguiente formato: HTTP:
- líneas 1-2: el navegador solicita el URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. Siempre ocurre así: los datos introducidos en un formulario JSF, obtenido inicialmente mediante URL y URLFormulaire, se envían a este mismo URL. El navegador dispone de dos formas de enviar los valores introducidos: GET y POST. Con el método GET, el navegador envía los valores introducidos en el URL solicitado. En el ejemplo anterior, el navegador podría haber enviado la siguiente primera línea:
GET /mv-jsf2-02/faces/index.xhtml?formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 HTTP/1.1
Con el método POST utilizado aquí, el navegador envía al servidor los valores introducidos mediante la línea 6.
- línea 3: indica el formato de codificación de los valores del formulario,
- línea 4: indica el tamaño en bytes de la línea 6,
- línea 5: línea vacía que indica el final de los encabezados HTTP y el inicio de los 126 bytes de los valores del formulario,
- línea 6: los valores del formulario en el formato element1=valor1&element2=valor2& ..., según el formato de codificación definido en la línea 3. En este formato de codificación, algunos caracteres se sustituyen por su valor hexadecimal. Este es el caso del último elemento:
formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_idt8=formulaire%3Aj_idt8
donde %3A representa el carácter :. Por lo tanto, es la cadena formulario:j_idt8=formulario:j_idt8 la que se envía al servidor. Quizá recordemos que ya nos hemos encontrado con el identificador j_idt8 cuando analizamos el código HTML generado para la etiqueta
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
Este había sido generado automáticamente por JSF. Lo que nos interesa aquí es que la presencia de este identificador en la cadena de valores enviada por el navegador del cliente permite a JSF saber que se ha hecho clic en el enlace [Français]. A continuación, utilizará el atributo «action» mencionado anteriormente para decidir cómo procesar la cadena recibida. El atributo action="#{changeLocale.setFrenchLocale}" indica a JSF que la solicitud del cliente debe ser procesada por el método [setFrenchLocale] de un objeto denominado changeLocale. Recordemos que este bean se definió mediante anotaciones en la clase Java [ChangeLocale]:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
El nombre de un bean viene definido por el atributo «name» de la anotación @ManagedBean. A falta de este atributo, se utiliza como nombre del bean el nombre de la clase, con la primera letra en minúscula.
Volvamos a la solicitud del navegador:
![]() |
y a la etiqueta <h:commandLink> que generó el enlace [Français] en el que se hizo clic:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
El controlador transmitirá la solicitud del navegador al gestor de eventos definido por el atributo «action» de la etiqueta <h:commandLink>. El gestor de eventos M al que hace referencia el atributo «action» de un comando <h:commandLink> debe tener la siguiente firma:
- no recibe ningún parámetro. Veremos que, no obstante, puede acceder a la solicitud del cliente,
- y debe devolver un resultado C de tipo String. Esta cadena de caracteres C puede ser:
- o bien el nombre de una página JSF del proyecto;
- o bien un nombre definido en las reglas de navegación del archivo [faces-config.xml] y asociado a una página JSF del proyecto;
- o bien un puntero nulo, si el navegador del cliente no debe cambiar de página,
En la arquitectura JSF anterior, el controlador [Faces Servlet] utilizará la cadena C devuelta por el gestor de eventos y, en su caso, su archivo de configuración [faces-config.xml] para determinar qué página JSF debe enviar como respuesta al cliente [4].
En la etiqueta
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
el gestor del evento «clic en el enlace» [Français] es el método [changeLocale.setFrenchLocale], donde changeLocale es una instancia de la clase [utils.ChangeLocale] ya estudiada:
package utils;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// la configuración regional de las 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;
}
}
El método setFrenchLocale tiene, efectivamente, la firma de los gestores de eventos. Recordemos que el gestor de eventos debe procesar la solicitud del cliente. Dado que no recibe parámetros, ¿cómo puede acceder a ella? Hay varias formas de hacerlo:
- El bean B, que contiene el gestor de eventos de la página JSF P, suele ser también el que contiene el modelo M de dicha página. Esto significa que el bean B contiene campos que se inicializarán con los valores introducidos en la página P. Esto lo llevará a cabo el controlador [Faces Servlet] antes de que se llame al gestor de eventos del bean B. Por lo tanto, este gestor tendrá acceso, a través de los campos del bean B al que pertenece, a los valores introducidos por el cliente en el formulario y podrá procesarlos.
- El método estático [FacesContext.getCurrentInstance()], de tipo [FacesContext], da acceso al contexto de ejecución de la solicitud JSF actual, que es un objeto de tipo [FacesContext]. El contexto de ejecución de la consulta así obtenido permite acceder a los parámetros enviados al servidor por el navegador del cliente mediante el siguiente método:
Si los parámetros enviados (POST) por el navegador del cliente son los siguientes:
el método getRequestParameterMap() devolverá el siguiente diccionario:
clave | valor |
formulario | formulario |
javax.faces.ViewState | ... |
formulario:j_id_id21 | formulario:j_id_id21 |
En la etiqueta
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
, ¿qué se espera del gestor de eventos locale.setFrenchLocale? Se quiere que establezca el idioma utilizado por la aplicación. En la jerga de Java, a esto se le llama «localizar» la aplicación. Esta localización la utiliza la etiqueta <f:view> de la página JSF [index.xhtml]:
<f:view locale="#{changeLocale.locale}">
...
</f:view>
Para cambiar la página al francés, basta con que el atributo «local» tenga el valor «fr». Para cambiarla al inglés, hay que asignarle el valor «en». El valor del atributo «local» se obtiene mediante la expresión [ChangeLocale].getLocale(). Esta expresión devuelve el valor del campo locale de la clase [ChangeLocale]. De ahí se deduce el código del método [ChangeLocale].setFrenchLocale(), que debe cambiar las páginas al francés:
public String setFrenchLocale(){
locale="fr";
return null;
}
Ya hemos explicado que un gestor de eventos debe devolver una cadena de caracteres C que será utilizada por [Faces Servlet] para encontrar la página JSF que se enviará como respuesta al navegador del cliente. Si la página que hay que devolver es la misma que la que se está procesando, el gestor de eventos puede limitarse a devolver el valor null. Eso es lo que se hace aquí en la línea 3: queremos devolver la misma página [index.xhtml], pero en un idioma diferente.
Volvamos a la arquitectura de procesamiento de la solicitud:
![]() |
El gestor de eventos changeLocale.setFrenchLocale se ha ejecutado y ha devuelto el valor null al controlador [Faces Servlet]. Por lo tanto, este volverá a mostrar la página [index.xhtml]. Repasemos esta 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>
Cada vez que se evalúa un valor de tipo #{msg['...']}, se utiliza uno de los archivos de mensajes [messages.properties]. El que se utiliza es el que se corresponde con la «localización» de la página (línea 6). Dado que el gestor de eventos changeLocale.setFrenchLocale define esta localización como fr, se utilizará el archivo [messages_fr.properties]. Al hacer clic en el enlace [Anglais] (línea 14), la localización cambiará a en (véase el método changeLocale.setEnglishLocale). En ese caso, se utilizará el archivo [messages_en.properties] y la página aparecerá en inglés:
![]() | ![]() |
Cada vez que se muestra la página [index.xhtml], se ejecuta la etiqueta <f:view>:
<f:view locale="#{changeLocale.locale}">
y, por lo tanto, se vuelve a ejecutar el método [ChangeLocale].getLocale(). Como hemos asignado el ámbito «Session» a nuestro bean:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
la localización realizada durante una solicitud se conserva para las solicitudes siguientes.
Nos queda un último elemento de la página [index.xhtml] por analizar:
<?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>
La etiqueta <h:commandLink> de la línea 17 tiene un atributo «action» cuyo valor es una cadena de caracteres. En este caso, no se invoca ningún controlador de eventos para procesar la página. Se pasa directamente a la página [page1.xhtml]. Analicemos el funcionamiento de la aplicación en este caso de uso:
![]() |
El usuario hace clic en el enlace [Page 1]. El formulario se envía al controlador [Faces Servlet]. Este reconoce en la solicitud que recibe que se ha hecho clic en el enlace [Page 1]. Examina la etiqueta correspondiente:
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
No hay ningún gestor de eventos asociado al enlace. El controlador [Faces Servlet] pasa inmediatamente al paso [3] anterior y muestra la página [page1.xhtml]:
![]() | ![]() |
2.4.7. La página JSF [page1.xhtml]
La página [page1.xhtml] envía el siguiente flujo al navegador del cliente:
![]() |
El código que genera esta página es el siguiente:
<?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>
En esta página no hay nada que no se haya explicado ya. El lector establecerá la correspondencia entre el código JSF y la página enviada al navegador del cliente. El enlace de vuelta a la página de inicio:
<h:commandLink value="#{msg['page1.welcome']}" action="index"/>
hará que se muestre la página [index.xhtml].
2.4.8. Ejecución del proyecto
Nuestro proyecto ya está completo. Podemos compilarlo (Clean and Build):
![]() |
- La compilación del proyecto crea, en la pestaña [Files], la carpeta [target]. En ella se encuentra el archivo [mv-jsf2-02-1.0-SNAPSHOT.war] del proyecto. Este es el archivo que se despliega en el servidor,
- en [WEB-INF / classes] y [2] se encuentran las clases compiladas de la carpeta [Source Packages] del proyecto, así como los archivos que se encontraban en la rama [Other Sources], en este caso los archivos de mensajes,
- en [WEB-INF / lib] [3] se encuentran las bibliotecas del proyecto,
- en la raíz de [WEB-INF] y [4] se encuentran los archivos de configuración del proyecto,
![]() |
- en la raíz del archivo [5] se encuentran las páginas JSF que estaban en la rama [Web Pages] del proyecto;
- una vez compilado el proyecto, se puede ejecutar [6]. Se ejecutará según su configuración de ejecución [7],
- se iniciará el servidor Tomcat si aún no estaba en marcha ([8]),
- el archivo [mv-jsf2-02-1.0-SNAPSHOT.war] se cargará en el servidor. A esto se le denomina «despliegue del proyecto en el servidor de aplicaciones»,
- en [9], se solicita que se inicie un navegador durante la ejecución. Este solicitará el contexto de la aplicación [10], c.a.d. URL y [http://localhost:8080/mv-jsf2-02]. Según las reglas del archivo [web.xml] (véase la página 44), será el archivo [faces/index.xhtml] el que se enviará al navegador del cliente. Dado que el archivo URL tiene el formato [/faces/*], será procesado por el controlador [Faces Servlet] (véase [web.xml], página 44). Este procesará la página y enviará el flujo HTML siguiente:
![]() |
- A continuación, el controlador [Faces Servlet] procesará los eventos que se produzcan a partir de esta página.
2.4.9. El archivo de configuración [faces-config.xml]
Hemos utilizado el siguiente archivo [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 es el archivo mínimo para una aplicación JSF 2 internacionalizada. Aquí hemos utilizado nuevas posibilidades de JSF 2 con respecto a JSF 1:
- declarar beans y su ámbito con las anotaciones @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
- navegar entre páginas utilizando como claves de navegación los nombres de las páginas XHTML sin su sufijo xhtml.
Es posible que no se desee utilizar estas opciones y que se declaren estos elementos del proyecto JSF en [faces-config.xml], al igual que en JSF 1. En ese caso, el archivo [faces-config.xml] podría ser el siguiente:
<?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">
<!-- aplicación -->
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
<!-- beans gestionados -->
<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>
<!-- navegación -->
<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>
- líneas 20-24: declaración del bean changeLocale:
- línea 21: nombre del bean;
- línea 22: nombre completo de la clase asociada al bean;
- línea 23: ámbito del bean. Los valores posibles son request, session, application,
- líneas 27-34: declaración de una regla de navegación:
- línea 28: se puede describir la regla. En este caso, no se ha hecho;
- línea 29: la página desde la que se navega (punto de partida);
- líneas 30-33: un caso de navegación. Puede haber varios;
- línea 31: la clave de navegación;
- línea 32: la página a la que se navega.
Las reglas de navegación se pueden visualizar de forma más gráfica. Al editar el archivo [faces-config.xml], se puede utilizar la pestaña [PageFlow]:
![]() |
Supongamos que utilizamos el archivo [faces-config.xml] anterior. ¿Cómo cambiaría nuestra aplicación?
- En la clase [ChangeLocale], las anotaciones @ManagedBean y @SessionScoped desaparecerían, ya que ahora el bean se declara en [faces-config],
- La navegación de [index.xhtml] a [page1.xhtml] mediante un enlace quedaría así:
<h:commandLink value="#{msg['welcome.page1']}" action="p1"/>
Al atributo «action» se le asigna la clave de navegación p1 definida en [faces-config],
- la navegación de [page1.xhtml] a [index.xhtml] mediante un enlace quedaría así:
<h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>
Al atributo «action» se le asigna la clave de navegación «welcome» definida en [faces-config],
- los métodos setFrenchLocale y setEnglishLocale, que deben devolver una clave de navegación, no tienen que modificarse, ya que devolvían «null» para indicar que se permanecía en la misma página.
2.4.10. Conclusión
Volvamos al proyecto de NetBeans que hemos escrito:
![]() |
Este proyecto presenta la siguiente arquitectura:
![]() |
En cada proyecto JSF, encontraremos los siguientes elementos:
- páginas JSF [A] que el controlador [Faces Servlet] [3] envía [4] a los navegadores de los clientes,
- archivos de mensajes [C] que permiten cambiar el idioma de las páginas JSF,
- las clases Java [B] que gestionan los eventos que se producen en el navegador del cliente [2a, 2b] y/o que sirven de plantillas para las páginas JSF y [3]. En la mayoría de los casos, las capas [métier] y [DAO] se desarrollan y prueban por separado. A continuación, la capa [web] se prueba con una capa [métier] ficticia. Si las capas [métier] y [DAO] están disponibles, se suele trabajar con sus archivos .jar.
- archivos de configuración [D] para vincular estos diversos elementos entre sí. El archivo [web.xml] se ha descrito en la página 44 y rara vez se modificará. Lo mismo ocurre con [faces-config], donde siempre utilizaremos la versión simplificada.
2.5. Ejemplo mv-jsf2-03: formulario de introducción de datos - componentes JSF
A partir de ahora, ya no mostraremos la construcción del proyecto. Presentaremos proyectos ya elaborados y explicaremos su funcionamiento. El lector puede descargar todos los ejemplos en la página web de este documento (véase el apartado 1.2).
2.5.1. La aplicación
La aplicación tiene una única vista:
![]() |
La aplicación presenta los principales componentes JSF que se pueden utilizar en un formulario de introducción de datos:
- la columna [1] indica el nombre de la etiqueta JSF / HTML utilizada,
- la columna [2] muestra un ejemplo de entrada para cada una de las etiquetas que aparecen,
- la columna [3] muestra los valores del bean que sirve de plantilla para la página,
- las entradas realizadas en [2] se validan mediante el botón [4]. Esta validación solo actualiza el bean modelo de la página. A continuación, se vuelve a mostrar la misma página. Así, tras la validación, la columna [3] muestra los nuevos valores del bean modelo, lo que permite al usuario comprobar el efecto de sus entradas en el modelo de la página.
2.5.2. El proyecto NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
- en [1], los archivos de configuración del proyecto JSF,
- en [2], la única página del proyecto: index.xhtml,
- en [3], una hoja de estilo [styles.css] para configurar el aspecto de la página [index.xhtml]
- en [4], las clases Java del proyecto,
- en [5], el archivo de mensajes de la aplicación en dos idiomas: francés e inglés.
2.5.3. El archivo [pom.xml]
Solo mostramos las dependencias:
<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 son las dependencias necesarias para un proyecto JSF. En los ejemplos que siguen, este archivo solo se mostrará cuando sufra algún cambio.
2.5.4. El archivo [web.xml]
El archivo [web.xml] se ha configurado para que la página [index.xhtml] sea la página de inicio del proyecto:
<?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>
- línea 30: la página [index.xhtml] es la página de inicio,
- líneas 11-14: un parámetro para el servlet [Faces Servlet]. Este parámetro indica que los comentarios en un facelet del tipo:
<!-- idiomas -->
se ignoren. Sin este parámetro, los comentarios plantean problemas difíciles de entender,
- líneas 3-6: un parámetro para el servlet [Faces Servlet] que se explicará más adelante.
2.5.5. El archivo [faces-config.xml]
El archivo [faces-config.xml] de la aplicación es el siguiente:
<?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>
- líneas 11-16: configuran el archivo de mensajes de la aplicación.
2.5.6. El archivo de mensajes [messages.properties]
Los archivos de mensajes (véase [5] en la captura de pantalla del proyecto) son los siguientes:
[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
Estos mensajes aparecen en los siguientes lugares de la página:
![]() |
La versión en inglés de los mensajes es la siguiente:
[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. La plantilla [Form.java] de la página [index.xhtml]
![]() |
En el proyecto anterior, la clase [Form.java] servirá de modelo o «backing bean» para la página JSF [index.xhtml]. Ilustremos este concepto de modelo con un ejemplo extraído de la página [index.xhtml]:
<!-- línea 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}"/>
Al realizar la solicitud inicial de la página [index.xhtml], el código anterior genera la línea 2 de la tabla de entradas:
![]() |
La línea 2 muestra el campo [1]; las líneas 3 a 6, el campo [2]; y la línea 7, el campo [3].
Las líneas 5 y 7 utilizan una expresión EL que incorpora el bean de formulario definido en la clase [Form.java] de la siguiente manera:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
- La línea 7 define un bean sin nombre. Por lo tanto, este será el nombre de la clase que comienza con una minúscula: form,
- el bean tiene un ámbito de solicitud. Esto significa que, en un ciclo de solicitud del cliente/respuesta del servidor, se instancia cuando la solicitud lo necesita y se elimina cuando se ha devuelto la respuesta al cliente.
En el código siguiente de la página [index.xhtml]:
<!-- línea 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}"/>
las líneas 5 y 7 utilizan el valor inputText del bean «form». Para comprender los vínculos que unen una página P a su modelo M, hay que volver al ciclo de solicitud del cliente y respuesta del servidor que caracteriza a una aplicación web:
![]() |
Hay que distinguir entre el caso en el que la página P se envía como respuesta al navegador (paso 4), por ejemplo, durante la solicitud inicial de la página, y el caso en el que el usuario ha provocado un evento en la página P y este es gestionado por el controlador [Faces Servlet] (paso 1).
Podemos distinguir estos dos casos observándolos desde el punto de vista del navegador:
- durante la solicitud inicial de la página, el navegador realiza una operación GET sobre el URL de la página;
- al enviar los valores introducidos en la página, el navegador realiza una operación POST sobre el URL de la página.
En ambos casos, se solicita la misma URL. Dependiendo de la naturaleza de la solicitud GET o POST del navegador, el procesamiento de la solicitud variará.
[cas 1 – demande initiale de la page P]
El navegador solicita el URL de la página con un GET. El controlador [Faces Servlet] pasará directamente a la etapa [4] de generación de la respuesta y la página [index.xhtml] se enviará al cliente. El controlador JSF solicitará que se muestre cada etiqueta de la página. Tomemos como ejemplo la línea 5 del código de [index.xhtml]:
<h:inputText id="inputText" value="#{form.inputText}"/>
La etiqueta JSF <h:inputText value="valor"/> da lugar a la etiqueta HTML <input type="text" value="valor"/>. La clase encargada de procesar esta etiqueta encuentra la expresión #{form.inputText}, que debe evaluar:
- si el bean «form» aún no existe, se crea mediante la instanciación de la clase forms.Form,
- la expresión #{form.inputText} se evalúa llamando al método form.getInputText(),
- el texto <input id="formulario:inputText" type="text" name="formulaire:inputText" value="texto" /> se inserta en el flujo HTML que se enviará al cliente si suponemos que el método form.getInputText() ha devuelto la cadena «texto». Además, JSF asignará un nombre (name) al componente HTML incluido en el flujo. Este nombre se construye a partir de los identificadores id del componente JSF analizado y de los de sus componentes padres, en este caso la etiqueta <h:form id="formulaire"/>.
Cabe destacar que, si en una página P se utiliza la expresión #{M.champ}, donde M es el bean modelo de la página P, este debe disponer del método público getChamp(). El tipo devuelto por este método debe poder convertirse al tipo String. Un modelo M posible y habitual es el siguiente:
donde T es un tipo que puede convertirse al tipo String, eventualmente mediante un método toString.
Siguiendo con el ejemplo de la visualización de la página P, el procesamiento de la línea:
<h:outputText value="#{form.inputText}"/>
será similar y se creará el siguiente flujo HTML:
Internamente, en el servidor, la página P se representa como un árbol de componentes, que refleja el árbol de etiquetas de la página enviada al cliente. Denominaremos «vista» o « » estado de la página a este árbol. Este estado se almacena. Puede almacenarse de dos formas, según la configuración establecida en el archivo [web.xml] de la aplicación:
<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>
Las líneas 7-11 definen el controlador [Faces Servlet]. Este se puede configurar mediante diferentes etiquetas <context-param>, entre ellas la de las líneas 3-6, que indica que el estado de una página debe guardarse en el cliente (el navegador). El otro valor posible, en la línea 5, es «server», que indica que el estado se guarda en el servidor. Este es el valor por defecto.
Cuando el estado de una página se guarda en el cliente, el controlador JSF añade a cada página HTML que envía un campo oculto cuyo valor es el estado actual de la página. Este campo oculto tiene el siguiente formato:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />
Su valor representa, en forma codificada, el estado de la página enviada al cliente. Es importante comprender que este campo oculto forma parte del formulario de la página y, por lo tanto, formará parte de los valores enviados por el navegador al validar el formulario. A partir de este campo oculto, el controlador JSF es capaz de restaurar la vista tal y como se envió al cliente.
Cuando se guarda el estado de una página en el servidor, el estado de la página enviada al cliente se guarda en la sesión de este. Cuando el navegador del cliente envíe los valores introducidos en el formulario, también enviará su token de sesión. A partir de este, el controlador JSF recuperará el estado de la página enviada al cliente y la restaurará.
El estado de una página JSF puede requerir varios cientos de bytes para su codificación. Dado que este estado se mantiene para cada usuario de la aplicación, pueden surgir problemas de memoria si hay un gran número de usuarios. Por este motivo, hemos optado aquí por guardar el estado de la página en el cliente (véase [web.xml], apartado 2.5.4, página 66).
[cas 2 – traitement de la page P]
![]() |
Nos encontramos en la etapa [1] anterior, en la que el controlador [Faces Servlet] va a recibir una solicitud POST del navegador del cliente al que había enviado previamente la página [index.xhtml]. Nos encontramos ante el procesamiento de un evento de la página. Se llevarán a cabo varias etapas antes incluso de que el evento pueda procesarse en [2a]. El ciclo de procesamiento de una solicitud POST por parte del controlador JSF es el siguiente:
FEDCBA

- en [A], gracias al campo oculto javax.faces.ViewState, se reconstruye la vista enviada inicialmente al navegador del cliente. Aquí, los componentes de la página recuperan el valor que tenían en la página enviada. Nuestro componente inputText recupera su valor «texto»,
- en [B], los valores enviados por el navegador del cliente se utilizan para actualizar los componentes de la vista. Así, si en el campo de entrada HTML, denominado inputText, el usuario ha escrito «jean», el valor «jean» sustituye al valor «texto». A partir de ahora, la vista refleja la página tal y como la ha modificado el usuario y ya no tal y como se envió al navegador,
- en [C], se comprueban los valores enviados. Supongamos que el componente inputText anterior sea el campo de entrada de una edad. El valor introducido deberá ser un número entero. Los valores enviados por el navegador son siempre de tipo String. Su tipo final en el modelo M asociado a la página P puede ser cualquier otro. Se produce entonces una conversión de un tipo String a otro tipo T. Esta conversión puede fallar. En ese caso, el ciclo de solicitud/respuesta finaliza y la página P, construida en [B], se devuelve al navegador del cliente con mensajes de error si el autor de la página P los ha previsto. Cabe señalar que el usuario ve la página tal y como la ha introducido, sin que el desarrollador tenga que hacer ningún esfuerzo. En otra tecnología, como JSP, el desarrollador debe reconstruir él mismo la página P con los valores introducidos por el usuario. El valor de un componente también puede someterse a un proceso de validación. Siguiendo con el ejemplo del componente inputText, que es el campo de introducción de la edad, el valor introducido no solo deberá ser un número entero, sino un número entero comprendido en un intervalo [1,N]. Si el valor introducido supera la etapa de conversión, es posible que no supere la etapa de validación. En este caso, el ciclo de solicitud/respuesta también ha finalizado y la página P generada en [B] se envía al navegador del cliente,
- En [D], si todos los componentes de la página P superan la etapa de conversión y validación, sus valores se asignarán al modelo M de la página P. Si el valor del campo de entrada generado a partir de la siguiente etiqueta:
<h:inputText value="#{form.inputText}"/>
es «jean», dicho valor se asignará al modelo de formulario de la página mediante la ejecución del código form.setInputText("jean"). Cabe destacar que, en el modelo M de la página P, los campos privados de M que almacenan el valor de un campo de entrada de P deben tener un método set,
- una vez actualizado el modelo M de la página P con los valores enviados, se puede procesar el evento que ha provocado el POST de la página P. Este es el paso [E]. Cabe señalar que, si el gestor de este evento pertenece al bean M, tiene acceso a los valores del formulario P que se han almacenado en los campos de ese mismo bean.
- La etapa [E] devolverá al controlador JSF una clave de navegación. En nuestros ejemplos, siempre será el nombre de la página XHTML que se va a mostrar, sin el sufijo .xhtml. Se trata del paso [F]. Otra forma de hacerlo es devolver una clave de navegación que se buscará en el archivo [faces-config.xml]. Ya hemos descrito este caso.
De lo anterior se desprende que:
- una página P muestra los campos C de su modelo M mediante los métodos [M].getC(),
- los campos C del modelo M de una página P se inicializan con los valores introducidos en la página P mediante los métodos [M].setC(introducción). En esta etapa, pueden intervenir procesos de conversión y validación que podrían fallar. En ese caso, el evento que provocó el POST de la página P no se procesa y la página se devuelve de nuevo al cliente tal y como la ha introducido.
La plantilla [Form.java] de la página [index.xhtml] será la siguiente:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
/** Crea una nueva instancia del formulario */
public Form() {
}
// campos del formulario
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;
}
// métodos getter y setter
...
}
Los campos de las líneas 16-27 se utilizan en los siguientes lugares del formulario:
![]() |
2.5.8. La página [index.xhtml]
La página [index.xhtml] que genera la vista anterior es la siguiente:
<?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">
<!-- encabezados -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
<!-- línea 1 -->
...
<!-- línea 2 -->
...
<!-- línea 3 -->
...
<!-- línea 4 -->
...
<!-- línea 5 -->
...
<!-- línea 6 -->
...
<!-- línea 7 -->
...
<!-- línea 8 -->
...
<!-- línea 9 -->
...
<!-- línea 10 -->
...
<!-- línea 11 -->
...
<!-- línea 12 -->
...
</h:panelGrid>
<p>
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
</p>
</h:form>
</h:body>
</f:view>
</html>
Vamos a analizar sucesivamente los principales componentes de esta página. Cabe destacar la estructura general de un formulario 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>
Los componentes de un formulario deben estar dentro de una etiqueta <h:form> (líneas 12-16). La etiqueta <f:view> (líneas 7-18) es necesaria si se internacionaliza la aplicación. Además, un formulario debe disponer de un medio para ser enviado (POST), a menudo un enlace o un botón, como en la línea 14. También puede enviarse mediante numerosos eventos (cambio de una selección en una lista, cambio del campo activo, introducción de un carácter en un campo de entrada, etc.).
2.5.9. El estilo del formulario
Para que las columnas de la tabla del formulario sean más legibles, este va acompañado de una hoja de estilo:
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
- línea 4: la hoja de estilo de la página se define dentro de la etiqueta HTML <head>, mediante la etiqueta:
<h:outputStylesheet library="css" name="styles.css"/>
La hoja de estilo se buscará en la carpeta [resources]:
![]() |
En la etiqueta:
<h:outputStylesheet library="css" name="styles.css"/>
- «library» es el nombre de la carpeta que contiene la hoja de estilo,
- «name» es el nombre de la hoja de estilo.
Veamos un ejemplo de uso de esta hoja de estilo:
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
La etiqueta <h:panelGrid columns="3"/> define una tabla de tres columnas. El atributo columnClasses permite aplicar un estilo a estas columnas. Los valores col1, col2 y col3 del atributo columnClasses designan los estilos respectivos de las columnas 1, 2 y 3 de la tabla. Estos estilos se buscan en la hoja de estilo de la 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
}
- líneas 7-9: el estilo denominado col1,
- líneas 11-13: el estilo denominado col2,
- líneas 15-17: el estilo denominado col3,
Estos tres estilos definen el color de fondo de cada una de las columnas.
- líneas 19-23: el estilo «entete» sirve para definir el estilo del texto de la primera línea de la tabla:
<!-- encabezados -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
- líneas 1-5: el estilo «info» sirve para definir el estilo de los textos de la primera columna de la tabla:
<!-- línea 1 -->
<h:outputText value="inputText" styleClass="info"/>
No nos detendremos mucho en el uso de las hojas de estilo, ya que este tema merecería un libro por sí solo y, además, su elaboración suele recaer en manos de especialistas. No obstante, hemos querido utilizar una, muy sencilla, para recordar que su uso es imprescindible.
Veamos ahora cómo se ha definido la imagen de fondo de la página:
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
La imagen de fondo se establece mediante el atributo «style» de la etiqueta <h:body>. Este atributo permite definir elementos de estilo. La imagen de fondo se encuentra en la carpeta [resources/images/standard.jpg]:
![]() |
Esta imagen se obtiene a través de URL [/mv-jsf2-03/resources/images/standard.jpg]. Por lo tanto, podríamos escribir:
<h:body style="background-image: url('mv-jsf2-03/resources/images/standard.jpg');">
/mv-jsf2-03 es el contexto de la aplicación. Este contexto lo establece el administrador del servidor web y, por lo tanto, puede cambiar. Se puede obtener mediante la expresión EL ${request.contextPath}. Por ello, es preferible utilizar el siguiente atributo de estilo:
style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"
que será válido independientemente del contexto.
2.5.10. Los dos ciclos de solicitud del cliente y respuesta del servidor de un formulario
Volvamos a lo que ya se ha explicado en el apartado 2.5.7 en un caso general y apliquémoslo al formulario estudiado. Este se probará en el entorno clásico JSF:
![]() |
Aquí no habrá gestores de eventos ni capa [métier]. Por lo tanto, los pasos [2x] no existirán. Se distinguirá entre el caso en el que el navegador solicita inicialmente el formulario F y el caso en el que el usuario, tras provocar un evento en el formulario F, este es procesado por el controlador [Faces Servlet]. Hay dos ciclos de solicitud del cliente/respuesta del servidor que son diferentes.
- el primero, correspondiente a la solicitud inicial de la página, es provocado por una operación GET del navegador sobre el control URL del formulario,
- el segundo, correspondiente al envío de los valores introducidos en la página, es provocado por una operación POST sobre ese mismo URL.
Según la naturaleza de la solicitud GET o POST del navegador, el tratamiento de la solicitud por parte del controlador [Faces Servlet] varía.
[cas 1 – demande initiale du formulaire F]
El navegador solicita el URL de la página con un GET. El controlador [Faces Servlet] pasará directamente a la etapa [4] de generación de la respuesta. El formulario [index.xhtml] se inicializará mediante su modelo [Form.java] y se enviará al cliente, que recibirá la siguiente vista:

Los intercambios HTTP entre el cliente y el servidor son los siguientes en esta ocasión:
Solicitud HTTP del cliente:
En la línea 1, se ve el GET del navegador.
Respuesta HTTP del servidor:
Aunque no se muestra aquí, la línea 7 va seguida de una línea en blanco y del código HTML del formulario. Este es el código que el navegador interpreta y muestra.
[cas 2 – traitement des valeurs saisies dans le formulaire F]
El usuario rellena el formulario y lo valida mediante el botón [Valider]. A continuación, el navegador solicita el URL del formulario con un POST. El controlador [Faces Servlet] procesa esta solicitud, actualiza la plantilla [Form.java] del formulario [index.xhtml] y vuelve a enviar el formulario [index.xhtml] actualizado con esta nueva plantilla. Veamos este ciclo con un ejemplo:

En el ejemplo anterior, el usuario ha introducido sus datos y los ha validado. A continuación, recibe la siguiente vista:

Los intercambios entre cliente y servidor HTTP en esta ocasión son los siguientes:
Solicitud HTTP del cliente:
En la línea 1, el POST generado por el navegador. En la línea 14, los valores introducidos por el usuario. Por ejemplo, allí se puede ver el texto introducido en el campo de entrada:
En la línea 14, se ha enviado el campo oculto javax.faces.ViewState. Este campo representa, en forma codificada, el estado del formulario tal y como se envió inicialmente al navegador durante su GET inicial.
Respuesta HTTP del servidor:
Aunque no se muestra aquí, la línea 6 va seguida de una línea en blanco y del código HTML del formulario actualizado según su nueva plantilla derivada del POST.
A continuación, analizaremos los distintos componentes de este formulario.
2.5.11. Etiqueta <h:inputText>
La etiqueta <h:inputText> genera una etiqueta HTML <input type="text" ...>.
Consideremos el siguiente código:
<!-- línea 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}"/>
y su plantilla [Form.java]:
private String inputText="texte";
public String getInputText() {
return inputText;
}
public void setInputText(String inputText) {
this.inputText = inputText;
}
Cuando se solicita la página [index.html] por primera vez, la página obtenida es la siguiente:
- la línea 2 del código XHTML genera [1],
- la etiqueta <h:panelGroup> (líneas 3-6) permite agrupar varios elementos en una misma celda de la tabla generada por la etiqueta <h:panelGrid> de la línea 20 del código completo de la página (véase el apartado 2.5.8). El texto [2] se genera mediante la línea 4. El campo de entrada [3] se genera mediante la línea [5]. En este caso, se ha utilizado el método getInputText de [Form.java] (líneas 3-5 del código Java) para generar el texto del campo de entrada,
- la línea 7 del código XHTML genera [4]. De nuevo, se utiliza el método getInputText de [Form.java] para generar el texto [4].
El flujo HTML generado por la página XHTML es el siguiente:
<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>
Las etiquetas HTML <tr> y <td> son generadas por la etiqueta <h:panelGrid> utilizada para generar la tabla del formulario.
Ahora, a continuación, introduzcamos un valor en el campo de entrada [1] y validemos el formulario con el botón [Valider] [2]. Obtenemos como respuesta la página [3, 4]:
![]() |
El valor del campo [1] se envía de la siguiente manera:
En [2], el formulario se valida con el siguiente botón:
<h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>
La etiqueta <h:commandButton> no tiene el atributo «action». En este caso, no se invoca ningún gestor de eventos ni se aplica ninguna regla de navegación. Tras el procesamiento, se devuelve la misma página. Repasemos su ciclo de procesamiento:
ABCDEF

- en [A], la página P se restaura tal y como se envió. Esto significa que el componente con id inputText se restaura con su valor inicial «texto»,
- en [B], los valores enviados por el navegador (introducidos por el usuario) se asignan a los componentes de la página P. Aquí, el componente con id inputText recibe el valor «un nuevo texto»,
- en [C], se llevan a cabo las conversiones y validaciones. En este caso, no hay ninguna. En la plantilla M, el campo asociado al componente con id inputText es el siguiente:
private String inputText="texte";
Dado que los valores introducidos son de tipo String, no hay que realizar ninguna conversión. Por otra parte, no se ha creado ninguna regla de validación. Las crearemos más adelante.
- En [D], los valores introducidos se asignan a la plantilla. El campo inputText de [Form.java] recibe el valor «un nuevo texto»,
- en [E] no se realiza ninguna acción, ya que no se ha asociado ningún gestor de eventos al botón [Valider].
- En [F], la página P se envía de nuevo al cliente porque el botón [Valider] no tiene ningún atributo «action». A continuación, se ejecutan las siguientes líneas de [index.xhtml]:
<!-- línea 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}"/>
Las líneas 5 y 7 utilizan el valor del campo inputText de la plantilla, que ahora es «un nuevo texto». De ahí el resultado que se obtiene:
![]()
2.5.12. Etiqueta <h:inputSecret>
La etiqueta <h:inputSecret> genera una etiqueta HTML <input type="password" ...>. Se trata de un campo de entrada similar al de la etiqueta JSF <h:inputText>, salvo que cada carácter que teclea el usuario se sustituye visualmente por un asterisco (*).
Consideremos el siguiente código:
<!-- línea 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}"/>
y su plantilla en [Form.java]:
private String inputSecret="secret";
Cuando se solicita la página [index.xhtml] por primera vez, la página obtenida es la siguiente:
- la línea 2 del código XHTML genera [1]
- El texto [2] se genera mediante la línea 4. El campo de entrada [3] se genera mediante la línea [5]. Normalmente, se debería haber utilizado el método getInputSecret de [Form.java] para generar el texto del campo de entrada. Hay una excepción cuando se trata de un campo de tipo «contraseña». La etiqueta <h:inputSecret> solo sirve para leer una entrada, no para mostrarla.
- La línea 7 del código XHTML genera [4]. Aquí se ha utilizado el método getInputSecret de [Form.java] para generar el texto [4] (véase la línea 1 del código Java).
El flujo HTML generado por la página XHTML es el siguiente:
<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>
- línea 3: la etiqueta HTML <input type= "password " .../> generada por la etiqueta JSF <h:inputSecret>
Ahora, a continuación, introduzcamos un valor en el campo de entrada [1] y validemos el formulario con el botón [Valider] [2]. Obtenemos como respuesta la página [3]:
![]() |
El valor del campo [1] se envía de la siguiente manera:
La validación del formulario mediante [2] ha provocado la actualización del modelo [Form.java] mediante la entrada [1]. El campo inputSecret de [Form.java] recibió entonces el valor «mdp». Dado que el formulario [index.xhtml] no ha definido ninguna regla de navegación ni ningún gestor de eventos, se vuelve a mostrar tras la actualización de su plantilla. De este modo, se vuelve a la visualización correspondiente a la solicitud inicial de la página [index.xhtml], en la que simplemente el campo inputSecret de la plantilla ha cambiado de valor a [3].
2.5.13. Etiqueta <h:inputTextArea>
La etiqueta <h:inputTextArea> genera una etiqueta HTML <textarea ...>texto</textarea>. Se trata de un campo de entrada similar al de la etiqueta JSF <h:inputText>, salvo que aquí se pueden escribir varias líneas de texto.
Veamos el siguiente código:
<!-- línea 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}"/>
y su plantilla en [Form.java]:
private String inputTextArea="ligne1\nligne2\n";
Cuando se solicita la página [index.xhtml] por primera vez, la página que se obtiene es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1],
- el texto [2] se genera mediante la línea 4. El campo de entrada [3] se genera mediante la línea [5]. Su contenido se ha generado mediante la llamada al método getInputTextArea del modelo, que ha devuelto el valor definido en la línea 1 del código Java anterior,
- la línea 7 del código XHTML genera [4]. Aquí se ha vuelto a utilizar el método getInputTextArea de [Form.java]. La cadena «línea1\nlínea2» contenía saltos de línea \n. Siguen estando ahí. Pero, al insertarse en un flujo HTML, los navegadores los muestran como espacios. La etiqueta HTML <textarea>, que muestra [3], interpreta correctamente los saltos de línea.
El flujo HTML generado por la página XHTML es el siguiente:
<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>
- líneas 3-5: la etiqueta HTML <textarea>...</textarea>, generada por la etiqueta JSF <h:inputTextArea>
Ahora, a continuación, introduzcamos un valor en el campo de entrada [1] y validemos el formulario con el botón [Valider] [2]. Obtenemos como respuesta la página [3]:
![]() |
El valor del campo [1] enviado es el siguiente:
La validación del formulario mediante [2] ha provocado la actualización de la plantilla [Form.java] mediante la entrada [1]. El campo textArea de [Form.java] recibió entonces el valor «Tutorial JSF\nparte1». Al volver a visualizar [index.xhtml], se comprueba que el campo textArea de la plantilla se ha actualizado correctamente a [3].
2.5.14. Etiqueta <h:selectOneListBox>
La etiqueta <h:selectOneListBox> genera una etiqueta HTML <select>...</select>. Visualmente, genera una lista desplegable o una lista con barra de desplazamiento.
Veamos el siguiente código:
<!-- línea 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}"/>
y su plantilla en [Form.java]:
private String selectOneListBox1="2";
Cuando se solicita la página [index.xhtml] por primera vez, la página obtenida es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1]
- El texto [2] se genera mediante la línea 4. El menú desplegable [3] se genera mediante las líneas [5-9]. Es el valor del atributo size= «1» el que hace que la lista solo muestre un elemento. Si falta este atributo, el valor por defecto del atributo size es 1. Los elementos de la lista se han generado mediante las etiquetas <f:selectItem> de las líneas 6-8. Estas etiquetas tienen la siguiente sintaxis:
<f:selectItem itemValue="valeur" itemLabel="texte"/>
El valor del atributo itemLabel es lo que se muestra en la lista. El valor del atributo itemValue es el valor del elemento. Este es el valor que se enviará al controlador [Faces Servlet] si se selecciona el elemento en la lista desplegable.
El elemento que se muestra en [3] se ha determinado mediante la llamada al método getSelectOneListBox1() (línea 5). El resultado «2» obtenido (línea 1 del código Java) ha hecho que se muestre el elemento de la línea 7 de la lista desplegable, ya que su atributo itemValue tiene el valor «2»,
- la línea 11 del código XHTML genera [4]. Aquí se ha vuelto a utilizar el método getSelectOneListBox1 de [Form.java].
El flujo HTML generado por la página XHTML es el siguiente:
<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>
- líneas 3 y 7: la etiqueta HTML <select ...>...</select> generada por la etiqueta JSF <h:selectOneListBox>,
- líneas 4-6: las etiquetas HTML <option ...> ... </option> generadas por las etiquetas JSF <f:selectItem>,
- línea 5: el hecho de que el elemento con valor="2" esté seleccionado en la lista se traduce en la presencia del atributo selected="selected".
Ahora, a continuación, seleccionemos un nuevo valor de la lista y validemos el formulario con el botón. Obtenemos como respuesta la página:
![]() |
El valor del campo [1] enviado es el siguiente:
La validación del formulario mediante [2] ha provocado la actualización del modelo [Form.java] mediante la entrada [1]. El elemento HTML
<option value="3">trois</option>
ha sido seleccionado. El navegador ha enviado la cadena «3» como valor del componente JSF que ha generado la lista desplegable:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
El controlador JSF utilizará el método setSelectOneListBox1("3") para actualizar el modelo de la lista desplegable. Así pues, tras esta actualización, el campo del modelo [Form.java]
private String selectOneListBox1;
contiene ahora el valor «3».
Cuando se vuelve a mostrar la página [index.xhtml] al finalizar su procesamiento, este valor provoca que se muestre [3,4], tal y como se ve arriba:
- determina el elemento de la lista desplegable que debe mostrarse ([3]),
- el valor del campo selectOneListBox1 se muestra en [4].
Consideremos una variante de la etiqueta <h:selectOneListBox>:
<!-- línea 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}"/>
La plantilla en [Form.java] de la etiqueta <h:selectOneListBox> de la línea 5 es la siguiente:
private String selectOneListBox2="3";
Cuando se solicita la página [index.xhtml] por primera vez, la página obtenida es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1],
- el texto [2] se genera mediante la línea 4. La lista con barra de desplazamiento [3] se genera mediante las líneas [5-11]. Es el valor del atributo size= «3» el que hace que tengamos una lista con barra de desplazamiento en lugar de una lista desplegable. Los elementos de la lista se han generado mediante las etiquetas <f:selectItem> de las líneas 6-8,
El elemento seleccionado en [3] se determinó mediante la llamada al método getSelectOneListBox2() (línea 5). El resultado «3» obtenido (línea 1 del código Java) ha provocado que se muestre el elemento de la línea 8 de la lista, ya que su atributo itemValue tiene el valor «3»,
- la línea 13 del código XHTML genera [4]. Aquí se ha vuelto a utilizar el método getSelectOneListBox2 de [Form.java].
El flujo HTML generado por la página XHTML es el siguiente:
<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>
- línea 6: el hecho de que el elemento con valor="3" esté seleccionado en la lista se traduce en la presencia del atributo selected="selected".
Ahora, a continuación, seleccionemos un nuevo valor de la lista y validemos el formulario con el botón. Obtenemos como respuesta la página:
![]() |
El valor enviado para el campo [1] es el siguiente:
La validación del formulario mediante [2] ha provocado la actualización del modelo [Form.java] mediante la entrada [1]. El elemento HTML
<option value="5">cinq</option>
ha sido seleccionado. El navegador ha enviado la cadena «5» como valor del componente JSF que ha generado la lista desplegable:
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
El controlador JSF utilizará el método setSelectOneListBox2("5") para actualizar la plantilla de la lista. Así pues, tras esta actualización, el campo
private String selectOneListBox2;
contiene ahora el valor «5».
Cuando se vuelve a mostrar la página [index.xhtml] al finalizar su procesamiento, este valor provoca que se muestre [3,4], tal y como se ve arriba:
- determina el elemento de la lista que debe seleccionarse ([3]),
- el valor del campo selectOneListBox2 se muestra en [4].
2.5.15. Etiqueta <h:selectManyListBox>
La etiqueta <h:selectmanyListBox> genera una etiqueta <select multiple="multiple">...</select> que permite al usuario seleccionar varios elementos de una lista.
Veamos el siguiente código:
<!-- línea 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}"/>
y su plantilla en [Form.java]:
private String[] selectManyListBox=new String[]{"1","3"};
Cuando se solicita la página [index.xhtml] por primera vez, la página que se obtiene es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1]
- El texto [2] se genera mediante la línea 4. La lista [3] se genera mediante las líneas [5-11]. El atributo size="3" hace que la lista muestre, en un momento dado, tres de estos elementos. Los elementos seleccionados de la lista se han determinado mediante la llamada al método getSelectManyListBox() (línea 5) del modelo Java. El resultado obtenido {"1","3"} (línea 1 del código Java) es una matriz de elementos de tipo String. Cada uno de estos elementos sirve para seleccionar uno de los elementos de la lista. En este caso, se seleccionarán los elementos de las líneas 6 y 10 cuyo atributo itemValue figure en la matriz {"1","3"}. Esto es lo que muestra [3].
- La línea 14 del código XHTML genera [4]. Aquí no se invoca el método getSelectManyListBox del modelo Java de la lista, sino el siguiente método getSelectManyListBoxValue:
private String[] selectManyListBox=new String[]{"1","3"};
...
// getters y setters
public String getSelectManyListBoxValue(){
return getValue(selectManyListBox);
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
Si se hubiera llamado al método getSelectManyListBox, se habría obtenido una matriz de String. Para incluir este elemento en el flujo HTML, el controlador habría llamado a su método toString. Sin embargo, este método, al tratarse de un array, solo devuelve el «hashcode» del mismo y no la lista de sus elementos, tal y como deseamos. Por ello, utilizamos el método getSelectManyListBoxValue mencionado anteriormente para obtener una cadena de caracteres que represente el contenido del array,
- la línea 12 del código XHTML genera el botón [5]. Al hacer clic en este botón, se ejecuta el código JavaScript del atributo onclick. Este se integrará en la página HTML, que será generada por el código JSF. Para comprenderlo, necesitamos conocer la naturaleza exacta de dicha página.
El flujo HTML generado por la página XHTML es el siguiente:
<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>
- líneas 3 y 9: la etiqueta HTML <select multiple="multiple"...>...</select> generada por la etiqueta JSF <h:selectManyListBox>. Es la presencia del atributo «multiple» lo que indica que se trata de una lista de selección múltiple,
- el hecho de que el modelo de la lista sea la matriz de cadenas {"1","3"} hace que los elementos de la lista de las líneas 4 (value="1") y 6 (value="3") tengan el atributo selected="selected",
- línea 10: al hacer clic en el botón [Raz], se ejecuta el código JavaScript del atributo onclick. La página se representa en el navegador mediante un árbol de objetos que a menudo se denomina DOM (Document Object Model). El código JavaScript puede acceder a cada objeto del árbol a través de su atributo name. La lista de la línea 3 del código HTML anterior se denomina formulario:selectManyListBox. El formulario en sí mismo puede designarse de diversas formas. En este caso, se designa mediante la notación this.form, donde «this» hace referencia al botón [Raz] y this.form al formulario en el que se encuentra dicho botón. La lista formulario:selectManyListBox se encuentra en este mismo formulario. Por lo tanto, la notación this.form['formulaire:selectManyListBox'] designa la ubicación de la lista en el árbol de componentes del formulario. El objeto que representa una lista tiene un atributo selectedIndex cuyo valor es el número del elemento seleccionado en la lista. Este número comienza en 0 para designar el primer elemento de la lista. El valor -1 indica que no hay ningún elemento seleccionado en la lista. El código JavaScript que asigna el valor -1 al atributo selectedIndex tiene como efecto deseleccionar todos los elementos de la lista, si los hubiera.
Ahora, a continuación, seleccionemos nuevos valores de la lista (para seleccionar varios elementos de la lista, mantén pulsada la tecla Ctrl mientras haces clic) y enviemos el formulario con el botón [Valider] [2]. Como respuesta, obtenemos la página [3,4]:
![]() |
El valor del campo [1] enviado es el siguiente:
La validación del formulario mediante [2] ha provocado la actualización del modelo [Form.java] mediante la entrada [1]. Los elementos HTML
<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
han sido seleccionados. El navegador ha enviado las tres cadenas «3», «4» y «5» como valores del componente JSF que ha generado la lista desplegable:
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
Se utilizará el método setSelectManyListBox de la plantilla para actualizarla con los valores enviados por el navegador:
private String[] selectManyListBox;
....
public void setSelectManyListBox(String[] selectManyListBox) {
this.selectManyListBox = selectManyListBox;
}
En la línea 3, vemos que el parámetro del método es un array de String. En este caso, será el array {"3","4","5"}. Tras esta actualización, el campo
private String[] selectManyListBox;
contiene ahora el array {"3","4","5"}.
Cuando se vuelve a mostrar la página [index.xhtml] al finalizar su procesamiento, este valor provoca que se muestre [3,4], tal y como se ve arriba:
- determina los elementos de la lista que deben seleccionarse ([3]),
- el valor del campo selectManyListBox se muestra en [4].
2.5.16. Etiqueta <h:selectOneMenu>
La etiqueta <h:selectOneMenu> es idéntica a la etiqueta <h:selectOneListBox size="1">. En el ejemplo, el código JSF que se ejecuta es el siguiente:
<!-- línea 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}"/>
La plantilla de la etiqueta <h:selectOneMenu> en [Form.java] es la siguiente:
private String selectOneMenu="1";
Al realizar la solicitud inicial de la página [index.xhtml], el código anterior genera la vista:
![]() |
Un ejemplo de ejecución podría ser el siguiente:
![]() |
El valor enviado para el campo [1] es el siguiente:
2.5.17. Etiqueta <h:selectManyMenu>
La etiqueta <h:selectManyMenu> es idéntica a la etiqueta <h:selectManyListBox size="1">. El código JSF ejecutado en el ejemplo es el siguiente:
<!-- línea 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"/>
La plantilla de la etiqueta <h:selectManyMenu> en [Form.java] es la siguiente:
private String[] selectManyMenu=new String[]{"1","2"};
Al realizar la solicitud inicial de la página [index.xhtml], el código anterior genera la página:
![]() |
La lista [1] contiene los textos «uno», ..., «cinco», con los elementos «uno» y «dos» seleccionados. El código HTML generado es el siguiente:
<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 puede ver arriba, en las líneas 4 y 5, los elementos «uno» y «dos» están seleccionados (presencia del atributo selected).
Es difícil ofrecer una captura de pantalla de un ejemplo de ejecución, ya que no se pueden mostrar los elementos seleccionados en el menú. Se invita al lector a que realice la prueba por sí mismo (para seleccionar varios elementos de la lista, mantenga pulsada la tecla Ctrl mientras hace clic).
2.5.18. Etiqueta <h:inputHidden>
La etiqueta <h:inputHidden> no tiene representación visual. Solo sirve para insertar una etiqueta <input type="hidden" value="..."/> en el flujo de la página. Al estar incluidos dentro de una etiqueta <h:form>, sus valores forman parte de los datos que se envían al servidor cuando se envía el formulario. Dado que son campos de formulario y el usuario no los ve, se denominan campos ocultos. La utilidad de estos campos es conservar información en la memoria entre los distintos ciclos de solicitud/respuesta de un mismo cliente:
- el cliente solicita un formulario F. El servidor se lo envía e introduce una información I en un campo oculto C, con el formato <h:inputHidden id="C" value="I"/>,
- cuando el cliente ha rellenado el formulario F y lo envía al servidor, el valor I del campo C se devuelve al servidor. Este puede entonces recuperar la información I que había almacenado en la página. De este modo, se ha creado una memoria entre los dos ciclos de solicitud/respuesta,
- JSF utiliza esta técnica. La información I que almacena en el formulario F es el valor de todos sus componentes. Para ello, utiliza el siguiente campo oculto:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...8PswawAA" />
El campo oculto se llama javax.faces.ViewState y su valor es una cadena que representa, en forma codificada, el valor de todos los componentes de la página enviada al cliente. Cuando este devuelve la página tras haber introducido datos en el formulario, el campo oculto javax.faces.ViewState se devuelve junto con los valores introducidos. Esto es lo que permite al controlador JSF reconstruir la página tal y como se envió inicialmente. Este mecanismo se ha explicado en la página 72.
El código JSF del ejemplo es el siguiente:
<!-- línea 9 -->
<h:outputText value="inputHidden" styleClass="info"/>
<h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
<h:outputText value="#{form.inputHidden}"/>
La plantilla de la etiqueta <h:inputHidden> en [Form.java] es la siguiente:
private String inputHidden="initial";
Lo que da como resultado la siguiente visualización al solicitar por primera vez la página [index.xhtml]:
- La línea 2 genera [1], la línea 4, [2]. La línea 3 no genera ningún elemento visual.
El código HTML generado es el siguiente:
<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>
Al enviar el POST del formulario, el valor «inicial» del campo denominado formulario:inputHidden de la línea 3 se enviará junto con los demás valores del formulario. El campo
private String inputHidden;
se actualizará con este valor, que es el que ya tenía inicialmente. Este valor se incluirá en la nueva página que se envía al cliente. Por lo tanto, siempre se obtiene la captura de pantalla anterior.
El valor enviado para el campo oculto es el siguiente:
2.5.19. Etiqueta <h:selectBooleanCheckBox>
La etiqueta <h:selectBooleanCheckBox> genera una etiqueta HTML <input type="checkbox" ...>.
Consideremos el siguiente código JSF:
<!-- línea 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}"/>
La plantilla de la etiqueta <h:selectBooleanCheckbox> de la línea 5 anterior en [Form.java] es la siguiente:
private boolean selectBooleanCheckbox=true;
Cuando se solicita la página [index.xhtml] por primera vez, la página obtenida es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1],
- el texto [2] se genera mediante la línea 4. La casilla de selección [3] se genera mediante la línea [5]. En este caso, se ha utilizado el método getSelectBooleanCheckbox de [Form.java] para marcar o no la casilla. Al establecer el método el valor booleano en «true» (véase el código Java), la casilla se ha marcado,
- la línea 7 del código XHTML genera [4]. De nuevo, se utiliza el método getSelectBooleanCheckbox de [Form.java] para generar el texto [4].
El flujo HTML generado por el código JSF anterior es el siguiente:
<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>
En [4], se puede ver la etiqueta HTML <input type="checkbox"> que se ha generado. El valor true de la plantilla asociada ha hecho que se añada el atributo checked="checked" a la etiqueta. Esto hace que la casilla esté marcada.
Ahora, más abajo, desmarquemos la casilla [1], validemos el formulario [2] y veamos el resultado obtenido [3, 4]:
![]() |
Como la casilla está desmarcada, no hay ningún valor registrado para el campo [1].
La validación del formulario mediante [2] ha provocado la actualización del modelo [Form.java] mediante la entrada [1]. A continuación, el campo selectBooleanCheckbox de [Form.java] recibió el valor «false». Al volver a visualizar [index.xhtml], se observa que el campo selectBooleanCheckbox del modelo se ha actualizado correctamente a [3] y [4]. Cabe destacar aquí que fue gracias al campo oculto javax.faces.ViewState que JSF pudo determinar que el usuario había desmarcado la casilla que inicialmente estaba marcada. De hecho, el valor de una casilla desmarcada no forma parte de los valores enviados por el navegador. Gracias al árbol de componentes almacenado en el campo oculto javax.faces.ViewState, JSF detecta que había una casilla de selección llamada «selectBooleanCheckbox» en el formulario y que su valor no forma parte de los valores enviados por el navegador del cliente. De ello puede deducir que estaba desmarcada en el formulario enviado, lo que le permite asignar el valor booleano false al modelo Java asociado:
private boolean selectBooleanCheckbox;
2.5.20. Etiqueta <h:selectManyCheckBox>
La etiqueta <h:selectManyCheckBox> genera un grupo de casillas de selección y, por lo tanto, varias etiquetas HTML <input type="checkbox" ...>. Esta etiqueta es el equivalente a la etiqueta <h:selectManyListBox>, salvo que los elementos que se deben seleccionar se presentan en forma de casillas de selección contiguas en lugar de en forma de lista. Lo dicho anteriormente sobre la etiqueta <h:selectManyListBox> sigue siendo válido aquí.
Consideremos el siguiente código JSF:
<!-- línea 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}"/>
La plantilla de la etiqueta <h:selectManyCheckbox> de la línea 5 anterior en [Form.java] es la siguiente:
private String[] selectManyCheckbox=new String[]{"1","3"};
Cuando se solicita la página [index.xhtml] por primera vez, la página obtenida es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1],
- el texto [2] se genera mediante la línea 4. Las casillas de selección [3] se generan mediante las líneas 5-10. Para cada una de ellas:
- el atributo itemLabel define el texto que se muestra junto a la casilla de selección;
- el atributo «itemvalue» define el valor que se enviará al servidor si la casilla está marcada;
El modelo de las cuatro casillas es el siguiente campo Java:
private String[] selectManyCheckbox=new String[]{"1","3"};
Esta tabla define:
- cuando se muestra la página, las casillas que deben estar marcadas. Esto se hace a través de su valor, c.a.d, y su campo itemValue. En el ejemplo anterior, se marcarán las casillas cuyos valores figuren en la tabla {"1", "3"}. Esto es lo que se ve en la captura de pantalla anterior;
- cuando se envía la página, la plantilla selectManyCheckbox recibe la matriz con los valores de las casillas que el usuario ha marcado. Esto es lo que veremos a continuación,
- la línea 12 del código XHTML genera [4]. Fue el siguiente método getSelectManyCheckboxValue el que generó [4]:
public String getSelectManyCheckboxValue(){
return getValue(getSelectManyCheckbox());
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
El flujo HTML generado por el código JSF anterior es el siguiente:
<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>
Se han generado cuatro etiquetas HTML <input type="checkbox" ...>. Las etiquetas de las líneas 3 y 7 tienen el atributo checked="checked", lo que hace que aparezcan marcadas. Cabe destacar que todas tienen el mismo atributo name="formulario:selectManyCheckbox"; es decir, los cuatro campos HTML tienen el mismo nombre. Si el usuario marca las casillas de las líneas 5 y 9, el navegador enviará los valores de las cuatro casillas de selección en el siguiente formato:
y la plantilla de las cuatro casillas
private String[] selectManyCheckbox=new String[]{"1","3"};
recibirá la tabla {"2","4"}.
Comprobémoslo a continuación. En [1], realizamos el cambio; en [2], validamos el formulario. En [3], el resultado obtenido es:
![]() |
Los valores enviados para los campos de [1] son los siguientes:
2.5.21. Etiqueta <h:selectOneRadio>
La etiqueta <h:selectOneRadio> genera un grupo de botones de opción mutuamente excluyentes.
Consideremos el siguiente código JSF:
<!-- línea 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}"/>
La plantilla de la etiqueta <h:selectOneRadio> de la línea 5 anterior es la siguiente en [Form.java]:
private String selectOneRadio="2";
Cuando se solicita la página [index.xhtml] por primera vez, la vista obtenida es la siguiente:
![]() |
- la línea 2 del código XHTML genera [1],
- El texto [2] se genera a partir de la línea 4. Los botones de opción [3] se generan a partir de las líneas 5 a 10. Para cada uno de ellos:
- el atributo itemLabel define el texto que se muestra junto al botón de opción;
- el atributo itemvalue define el valor que se enviará al servidor si se marca el botón;
El modelo de los cuatro botones de opción es el siguiente campo Java:
private String selectOneRadio="2";
Este modelo define:
- cuando se muestra la página, el único botón de opción que debe estar marcado. Esto se hace a través de su valor, c.a.d, y de su campo itemValue. En el ejemplo anterior, se marcará el botón de opción con el valor «2». Esto es lo que se ve en la captura de pantalla anterior;
- cuando se envía la página, la plantilla selectOneRadio recibe el valor del botón de opción que se ha marcado. Esto es lo que veremos a continuación,
- la línea 12 del código XHTML genera [4].
El flujo HTML generado por el código JSF anterior es el siguiente:
<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>
Se han generado cuatro etiquetas HTML <input type="radio" ...>. La etiqueta de la línea 8 tiene el atributo checked="checked", lo que hace que el botón de opción correspondiente aparezca marcado. Cabe señalar que todas las etiquetas tienen el mismo atributo name="formulario:selectOneRadio"; es decir, los cuatro campos HTML tienen el mismo nombre. Esta es la condición necesaria para tener un grupo de botones de opción excluyentes: cuando uno está marcado, los demás no lo están.
A continuación, en [1], marcamos uno de los botones de opción; en [2], enviamos el formulario; y en [3], el resultado obtenido:
![]() |
El valor enviado para el campo [1] es el siguiente:
2.6. Ejemplo mv-jsf2-04: listas dinámicas
2.6.1. La aplicación
La aplicación es la misma que la anterior:
![]() |
Los únicos cambios se deben a la forma en que se generan los elementos de las listas de las zonas [1] y [2]. Aquí se generan dinámicamente mediante código Java, mientras que en la versión anterior estaban escritos «de forma fija» en el código de la página JSF.
2.6.2. El proyecto NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
El proyecto [mv-jsf2-04] es idéntico al proyecto [mv-jsf2-03], salvo por las siguientes diferencias:
- en [1], en la página JSF, los elementos de las listas ya no se escribirán «de forma fija» en el código,
- en [2], se modificará la plantilla de la página JSF [1],
- en [3], se modificará uno de los mensajes.
2.6.3. La página [index.xhtml] y su plantilla [Form.java]
La página JSF [index.xhtml] queda así:
<?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">
...
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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"/>
...
<!-- línea 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}"/>
<!-- línea 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>
Los cambios realizados se muestran en las líneas 26-28. Donde antes teníamos el 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>
ahora aparece este:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
Las tres etiquetas <f:selectItem> de las líneas 2 a 4 se han sustituido por la única etiqueta <f:selectItems> de la línea b. Esta etiqueta tiene un atributo «value» cuyo valor es una colección de elementos de tipo javax.faces.model.SelectItem. En el ejemplo anterior, el valor del atributo «value» se obtendrá llamando al siguiente 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;
}
- En la línea 1, el método getSelectOneListbox1Items devuelve una matriz de elementos de tipo javax.faces.model.SelectItem construida por el método privado getItems de la línea 5. Cabe señalar que el método getSelectOneListbox1Items no es el getter de un campo privado selectOneListBox1Items,
- la clase javax.faces.model.SelectItem tiene varios constructores.

Utilizamos la línea 8 del método getItems, el constructor SelectItem(Object value, String label), que corresponde a la etiqueta JSF
<f:selectItem itemValue="value" labelValue="label"/>
- líneas 5-10: el método getItems(String label, int qte) crea un array de qte elementos de tipo SelectItem, donde el elemento i se obtiene mediante el constructor SelectItem(i, label+i).
El código JSF
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
se convierte entonces en funcionalmente equivalente al siguiente 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>
Se hace lo mismo con el resto de listas de la página JSF. Así, en la plantilla [Form.java] se encuentran los siguientes métodos nuevos:
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. El archivo de mensajes
Solo se modifica un mensaje:
[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. Pruebas
Se invita al lector a probar esta nueva versión.
En la mayoría de los casos, los elementos dinámicos de un formulario son el resultado de un proceso de negocio o proceden de una base de datos:
![]() |
Analicemos la solicitud inicial de la página JSF [index.xhtml] mediante un GET del navegador:
- se solicita la página JSF a través de [1],
- el controlador [Faces Servlet] solicita su visualización en [3]. El motor JSF que procesa la página recurre a la plantilla [Form.java] de la misma, por ejemplo, al método getSelectOneListBox1Items. Este método podría muy bien devolver una matriz de elementos de tipo SelectItem, a partir de la información almacenada en una base de datos. Para ello, recurriría a la capa [métier] [2b].
2.7. Ejemplo mv-jsf2-05: navegación – sesión – gestión de excepciones
2.7.1. La aplicación
La aplicación es la misma que la anterior, salvo que el formulario se presenta ahora en forma de asistente de varias páginas:
![]() |
- en [1], la página 1 del formulario, a la que también se puede acceder mediante el enlace 1 de [2]
- en [2], un grupo de 5 enlaces.
- en [3], la página 2 del formulario, a la que se accede mediante el enlace 2 de [2]
![]() |
![]() |
- en [4], la página 3 del formulario a la que se accede mediante el enlace 3 de [2]
- en [5], la página a la que se accede mediante el enlace «Lanzar una excepción» de [2]
![]() |
- en [6], la página a la que se accede mediante el enlace 4 de [2]. En ella se resumen los datos introducidos en las páginas 1 a 3.
2.7.2. El proyecto de NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
El proyecto [mv-jsf2-05] introduce dos novedades:
- en [1], la página JSF [index.xhtml] se divide en tres páginas [form1.xhtml, form2.xhtml, form3.xhtml] en las que se han distribuido los datos introducidos. La página [form4.xhtml] es una copia de la página [index.xhtml] del proyecto anterior. En [2], la clase [Form.java] permanece sin cambios. Servirá de plantilla para las cuatro páginas JSF anteriores,
- en [3], se añade una página [exception.xhtml]: se utilizará cuando se produzca una excepción en la aplicación.
2.7.3. Las páginas [form.xhtml] y su plantilla [Form.java]
2.7.3.1. El código de las páginas XHTML
La página JSF [form1.xhtml] es la siguiente:
<?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">
<!-- enlaces -->
<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"/>
<!-- línea 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<!-- línea 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<!-- línea 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>
<!-- enlaces -->
<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>
y corresponde a la siguiente visualización:
![]() |
Cabe destacar lo siguiente:
- en la línea 16, la tabla, que antes tenía tres columnas, ahora solo tiene dos. Se ha eliminado la columna 3, que mostraba los valores del modelo. Será [form4.xhtml] la que los muestre,
- líneas 40-46: una tabla con seis enlaces. Los enlaces de las líneas 44 y 46 tienen una navegación estática: su atributo «action» está codificado de forma fija. Los demás enlaces tienen una navegación dinámica: su atributo «action» apunta a un método del bean de formulario encargado de devolver la clave de navegación. Los métodos a los que se hace referencia en [Form.java] son los siguientes:
// eventos
public String doAction2(){
return "form2";
}
public String doAction4(){
return "form4";
}
public String doAlea(){
// un número aleatorio entre 1 y 3
int i=1+(int)(3*Math.random());
// se devuelve la clave de navegación
return "form"+i;
}
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
Por el momento, ignoraremos el método throwException de la línea 17. Volveremos sobre él más adelante. Los métodos doAction2 y doAction4 se limitan a devolver la clave de navegación sin realizar ningún procesamiento. Por lo tanto, también podríamos haber 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}"/>
El método doAlea, por su parte, genera una clave de navegación aleatoria cuyo valor se toma del conjunto {"form1", "form2", "form3"}.
El código de las páginas [form2.xhtml, form3.xhtml, form3.xhtml] es similar al de la página [form1.xhtml].
2.7.3.2. Vida útil de la plantilla [Form.java] de las páginas [form*.xhtml]
Consideremos la siguiente secuencia de acciones:
![]() |
- en [1], se rellena la página 1 y se pasa a la página 3,
- en [2], se rellena la página 3 y se vuelve a la página 1,
![]() |
- en [3], se vuelve a encontrar la página 1 tal y como se introdujo. A continuación, se vuelve a la página 3,
- en [4], la página 3 aparece tal y como se introdujo.
El mecanismo del campo oculto [javax.faces.ViewState] no basta para explicar este fenómeno.
Al pasar de [1] a [2], se producen varios pasos:
- la plantilla [Form.java] se actualiza con el valor de POST, que a su vez procede de [form1.jsp]. En concreto, el campo inputText recibe el valor «otro texto»,
- la clave de navegación «form3» provoca la visualización de [form3.xhtml]. El ViewState incluido en [form3.xhtml] muestra únicamente el estado de los componentes de [form3.xhtml], no los de [form1.xhtml].
Al pasar de [2] a [3]:
- la plantilla [Form.java] se actualiza con la POST de [form3.xhtml]. Si se solicita la vida útil de la plantilla [Form.java], se crea un objeto [Form.java] completamente nuevo antes de que se actualice con el POST de [form3.xhtml]. En este caso, el campo inputText de la plantilla recupera su valor por defecto:
private String inputText="texte";
y lo mantiene: de hecho, en el POST derivado del [form3.xhtml], no hay nada que actualice el campo inputText, que forma parte de la plantilla de [form1.xhtml] y no de la de [form3.xhtml],
- la clave de navegación «form1» provoca que se muestre [form1.xhtml]. La página muestra su plantilla. En nuestro caso, el campo de entrada login vinculado a la plantilla inputText mostrará texte y no el valor «otro texto» introducido en [1]. Para que el campo inputText conserve el valor introducido en [1], la duración de la plantilla [Form.java] debe ser «sesión» y no «solicitud». En este caso,
- una vez finalizado el POST de [form1.xhtml], la plantilla se colocará en la sesión del cliente. El campo inputText tendrá el valor «otro texto»,
- en el momento de la ejecución de POST a partir de [form3.xhtml], se buscará la plantilla en dicha sesión y se actualizará mediante la ejecución de POST a partir de [form3.xhtml]. El campo inputText no se actualizará mediante este POST, sino que mantendrá el valor «otro texto» obtenido al finalizar el proceso POST a partir de [form1.xhtml] y [1].
Por lo tanto, la declaración del bean [Form.java] es la siguiente:
package forms;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
@ManagedBean
@SessionScoped
public class Form {
La línea 8 asigna al bean un ámbito de sesión.
2.7.4. Gestión de excepciones
Volvamos a la arquitectura general de una aplicación JSF:
![]() |
¿Qué ocurre cuando un gestor de eventos o un modelo detecta una excepción procedente de la capa de negocio, como por ejemplo una desconexión imprevista de una base de datos?
- Los gestores de eventos [2a] pueden interceptar cualquier excepción que provenga de la capa [métier] y proporcionar al controlador [Faces Servlet] una clave de navegación hacia una página de error específica para dicha excepción,
- En el caso de las plantillas, esta solución no es viable, ya que cuando se solicitan [3,4], nos encontramos en la fase de renderización de una página concreta XHTML y ya no en la fase de selección de la misma. ¿Cómo se puede cambiar de página cuando se está en la fase de renderización de una de ellas? Una solución sencilla, aunque no siempre adecuada, consiste en no gestionar la excepción, que entonces se propagará hasta el contenedor de servlets que ejecuta la aplicación. Este puede configurarse para mostrar una página concreta cuando una excepción llega hasta el contenedor de servlets. Esta solución siempre es viable y la analizaremos a continuación.
2.7.4.1. Configuración de la aplicación web para la gestión de excepciones
La configuración de una aplicación web para la gestión de excepciones se realiza en su archivo [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>
En las líneas 32-39 se encuentra la definición de dos páginas de error. Se pueden incluir tantas etiquetas <error-page> como sea necesario. La etiqueta <location> indica la página que se debe mostrar en caso de error. El tipo de error asociado a la página se puede definir de dos maneras:
- mediante la etiqueta <exception-type>, que define el tipo Java de la excepción gestionada. Así, la etiqueta <error-page> de las líneas 36-39 indica que, si el contenedor de servlets detecta una excepción de tipo [java.lang.Exception] o derivada (línea 37) durante la ejecución de la aplicación, debe mostrar la página [/faces/exception.xhtml] (línea 38). Al utilizar aquí el tipo de excepción más genérico, [java.lang.Exception], nos aseguramos de gestionar todas las excepciones,
- mediante la etiqueta <error-code> (línea 33), que define un código de error HTTP. Por ejemplo, si un navegador solicita el URL [http://machine:port/contexte/P] y la página P no existe en el contexto de la aplicación, esta no interviene en la respuesta. Es el contenedor de servlets el que genera esta respuesta enviando una página de error por defecto. La primera línea del flujo HTTP de su respuesta contiene un código de error 404 que indica que la página P solicitada no existe. Es posible que se desee generar una respuesta que, por ejemplo, respete la identidad visual de la aplicación o que proporcione enlaces para resolver el problema. En ese caso, se utilizará una etiqueta <error-page> con una etiqueta <error-code>404</error-code>.
En el ejemplo anterior, el código de error 500 HTTP es el que se devuelve en caso de que la aplicación «se cuelgue». Es el código que se devolvería si una excepción llegara hasta el contenedor de servlets. Por lo tanto, las dos etiquetas <error-page> de las líneas 28-35 son probablemente redundantes. Se han incluido ambas para ilustrar las dos formas de gestionar un error.
2.7.4.2. Simulación de la excepción
El enlace [Lancer une exception] genera una excepción de forma artificial:
![]() |
![]() |
Al hacer clic en el enlace [Lancer une exception] [1], se muestra la página [2].
En el código de las páginas [formx.xhtml], el enlace [Lancer une exception] se genera de la siguiente manera:
<!-- enlaces -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
...
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>
En la línea 5 se observa que, al hacer clic en el enlace, se ejecutará el método [form].throwException. Este es el siguiente:
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
En él se lanza una excepción de tipo [java.lang.Exception]. Esta se propagará hasta el contenedor de servlets, que a continuación mostrará la página [/faces/exception.xhtml].
2.7.4.3. La información relacionada con una excepción
Cuando una excepción llega hasta el contenedor de servlets, este muestra la página de error correspondiente y le transmite información sobre la excepción. Dicha información se incluye como nuevos atributos de la solicitud que se está procesando. La solicitud de un navegador y la respuesta que va a recibir se encapsulan en objetos Java de tipo [HttpServletRequest request] y [HttpServletResponse response]. Estos objetos están disponibles en todas las etapas del procesamiento de la solicitud del navegador.
![]() |
Al recibir la solicitud HTTP del navegador, el contenedor de servlets la encapsula en el objeto Java [HttpServletRequest request] y crea el objeto [HttpServletResponse response], que permitirá generar la respuesta. En este objeto se encuentra, entre otros, el canal TCP-IP que se utilizará para el flujo HTTP de la respuesta. Todas las capas t1, t2, ..., tn que intervendrán en el procesamiento del objeto request tienen acceso a estos dos objetos. Cada una de ellas puede acceder a los elementos de la solicitud inicial request y preparar la respuesta completando el objeto response. Una capa de localisation podrá, por ejemplo, establecer el localisation de la respuesta mediante el método response.setLocale(Locale l).
Las diferentes capas pueden intercambiar información a través del objeto request. Este objeto cuenta con un diccionario de atributos, vacío en el momento de su creación, que puede ser enriquecido por las sucesivas capas de procesamiento. Estas pueden introducir en los atributos del objeto request la información necesaria para la siguiente capa de procesamiento. Existen dos métodos para gestionar los atributos del objeto request:
- void setAttribute(String s, Object o), que permite añadir a los atributos un objeto o identificado por la cadena s,
- Object getAttribute(String s), que permite obtener el atributo o identificado por la cadena s.
Cuando una excepción llega hasta el contenedor de servlets, este incluye los siguientes atributos en la solicitud que se está procesando:
clave | valor |
el código de error HTTP que se enviará al cliente | |
el tipo Java de la excepción, junto con el mensaje de error. | |
la URL solicitada cuando se produjo la excepción | |
el servlet que estaba procesando la solicitud cuando se produjo la excepción |
Utilizaremos estos atributos de la solicitud en la página [exception.xhtml] para mostrarlos.
2.7.4.4. La página de error [exception.xhtml]
Su contenido es el siguiente:
<?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>
<!-- enlaces -->
<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. Las expresiones de la página de excepción
En la cadena de procesamiento de la solicitud del cliente, la página XHTML suele ser el último eslabón de la cadena:
![]() |
Todos los elementos de la cadena son clases Java, incluida la página XHTML. De hecho, esta se transforma en un servlet mediante el contenedor de servlets, c.a.d, en una clase Java normal. Más concretamente, la página XHTML se transforma en código Java que se ejecuta dentro del siguiente 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 de la línea 14, se encuentra el código Java correspondiente a la página XHTML. Este código dispondrá de una serie de objetos inicializados por el método _jspService, línea 1 anterior:
- línea 1: HttpServletRequest request: la solicitud que se está procesando,
- línea 1: HttpServletResponse response: la respuesta que se va a enviar al cliente,
- línea 7: ServletContext application: un objeto que representa la propia aplicación web. Al igual que el objeto request, el objeto application puede tener atributos. Estos son compartidos por todas las solicitudes de todos los clientes. Por lo general, son atributos de solo lectura,
- línea 6: HTTPSession sesión: representa la sesión del cliente. Al igual que los objetos request y application, el objeto session puede tener atributos. Estos son compartidos por todas las solicitudes de un mismo cliente,
- línea 9: JspWriter out: un flujo de escritura hacia el navegador del cliente. Este objeto resulta útil para la depuración de una página XHTML. Todo lo que se escriba a través de out.println(texto) se mostrará en el navegador del cliente.
Cuando en la página JSF se escribe #{expresión}, la expresión puede ser la clave de un atributo de los objetos request, session o application mencionados anteriormente. El atributo correspondiente se busca sucesivamente en estos tres objetos. Así, #{clave} se evalúa de la siguiente manera:
- request.getAttribute(clave)
- session.getAttribute(clave)
- application.getAttribute(clave)
En cuanto se obtiene un valor que no sea null, se detiene la evaluación de #{clave}. Es posible que se desee ser más preciso indicando el contexto en el que debe buscarse el atributo:
- #{requestScope['clé']} para buscar el atributo en el objeto «request»,
- #{sessionScope['clé']} para buscar el atributo en el objeto «session»,
- #{applicationScope['clé']} para buscar el atributo en el objeto «application».
Esto es lo que se ha hecho en la página [exception.xhtml], página 116. Los atributos utilizados son los siguientes:
clave | dominio | valor |
solicitud | véase el apartado 2.7.4.3. | |
ídem | ídem | |
ídem | ídem | |
ídem | ídem |
Los distintos mensajes necesarios para la página JSF [exception.xhtml] se han añadido a los archivos de mensajes ya 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. Ejemplo mv-jsf2-06: validación y conversión de los datos introducidos
2.8.1. La aplicación
La aplicación presenta un formulario de entrada de datos. Al validarlo, se devuelve el mismo formulario como respuesta, junto con posibles mensajes de error si se detectan datos incorrectos.
![]() |
![]() |
2.8.2. El proyecto NetBeans
El proyecto de NetBeans de la aplicación es el siguiente:
![]() |
El proyecto [mv-jsf2-06] se basa de nuevo en una única página [index.html] [1] y su plantilla [Form.java] [2]. Sigue utilizando mensajes extraídos de [messages.properties], pero únicamente en francés ([3]). No se ofrece la opción de cambiar de idioma.
2.8.3. El entorno de la aplicación
A continuación se muestra el contenido de los archivos que configuran la aplicación, sin ofrecer explicaciones específicas. Estos archivos permiten comprender mejor lo que viene a continuación.
[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>
La línea 17 es nueva. Se explicará más adelante.
El archivo de mensajes [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="!"
La hoja de estilo [styles.css] es la siguiente:
.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. La página [index.xhtml] y su plantilla [Form.java]
La página [index.xhtml] es la siguiente:
<?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">
<!-- línea 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"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 6 -->
...
<!-- línea 7 -->
...
<!-- línea 8 -->
...
<!-- línea 9 -->
...
<!-- línea 10 -->
...
<!-- línea 11 -->
...
<!-- línea 12 -->
...
<!-- línea 13 -->
...
</h:panelGrid>
<!-- botones de control -->
<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>
La principal novedad es la presencia de las etiquetas:
- para mostrar mensajes de error <h:messages> (línea 14), <h:message> (líneas 24, 29, 34),
- que establecen restricciones de validez sobre los datos introducidos <f:validateLongRange> (línea 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
- que definen un convertidor entre la entrada y su modelo como <f:convertDateTime>.
La plantilla de esta página es la siguiente clase [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 = "";
// acciones
public String submit() {
...
}
public String cancel() {
...
}
// validadores
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// getters y setters
...
}
La novedad aquí es que los campos de la plantilla ya no son únicamente de tipo String, sino de diversos tipos.
2.8.5. Los distintos campos del formulario
A continuación, analizaremos sucesivamente los distintos campos del formulario.
2.8.5.1. Campos 1 a 4: introducción de un número entero
La página [index.xhtml] presenta el campo 1 con el siguiente formato:
<!-- línea 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}"/>
La plantilla form.saisie1 se define de la siguiente manera en [Form.java]:
private Integer saisie1 = 0;
En un GET del navegador, la página [index.xhtml] asociada a su plantilla [Form.java] se muestra visualmente de la siguiente manera:
- la línea 2 genera [1],
- la línea 3 genera [2],
- la línea 4 genera [3],
- la línea 5 genera [4].
Supongamos que se introduce y se valida la siguiente entrada:
![]() |
Entonces se obtiene el siguiente resultado en el formulario devuelto por la aplicación:
![]() |
- en [1], la entrada errónea,
- en [2], el mensaje de error que lo indica,
- en [3], se observa que el valor del campo «Integer saisie1» del modelo no ha cambiado.
Expliquemos lo que ha ocurrido. Para ello, volvamos al ciclo de procesamiento de una página JSF:
![]() |
Analizamos este ciclo para el componente:
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
y su modelo:
private Integer saisie1 = 0;
- En [A], se restaura la página [index.xhtml] enviada durante el GET del navegador. En [A], la página se muestra tal y como la recibió el usuario. El componente id="saisie1" recupera su valor inicial «0»,
- en [B], los componentes de la página reciben como valores los enviados por el navegador. En [B], la página se muestra tal y como la ha introducido y validado el usuario. El componente id="saisie1" recibe como valor el valor enviado «x»,
- en [C], si la página contiene validadores y convertidores explícitos, estos se ejecutan. También se ejecutan los convertidores implícitos si el tipo del campo asociado al componente no es del tipo String. Este es el caso aquí, donde el campo form.saisie1 es de tipo Integer. JSF intentará transformar el valor «x» del componente id="saisie1" en un tipo Integer. Esto provocará un error que detendrá el ciclo de procesamiento [A-F]. Este error se asociará al componente id="saisie1". A través de [D2], se pasa a continuación directamente a la fase de generación de la respuesta. Se devuelve la misma página [index.xhtml],
- la fase [D] solo tiene lugar si todos los componentes de una página han superado la fase de conversión/validación. Es en esta fase cuando el valor del componente id="saisie1" se asignará a su modelo form.saisie1.
Si la fase [C] falla, la página se vuelve a mostrar y se vuelve a ejecutar el siguiente código:
<!-- línea 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}"/>
![]() |
El mensaje que se muestra en [2] procede de la línea 4 de [index.xhtml]. La etiqueta <h:message for="idComposant"/> muestra el mensaje de error asociado al componente designado por el atributo «for», en caso de que se produzca un error. El mensaje que aparece en [2] es estándar y se encuentra en el archivo [javax/faces/Messages.properties] del archivo [jsf-api.jar]:
![]() |
En [2], se observa que el archivo de mensajes existe en varias variantes. Examinemos el contenido de [Messages_fr.properties]:
El archivo contiene mensajes divididos en categorías:
- errores en un componente, línea 3,
- errores de conversión entre un componente y su modelo, línea 12
- errores de validación cuando hay validadores en la página, línea 23.
El error que se ha producido en el componente id="saisie1" es un error de conversión de un tipo String a un tipo Integer. El mensaje de error asociado es el de la línea 18 del archivo de mensajes.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}
A continuación se reproduce el mensaje de error que se ha mostrado:
![]() |
Se observa que en el mensaje:
- el parámetro {2} ha sido sustituido por el identificador del componente para el que se ha producido el error de conversión,
- el parámetro {0} se ha sustituido por la entrada realizada en [1] para el componente,
- el parámetro {1} se ha sustituido por el número 9346.
La mayoría de los mensajes relacionados con los componentes tienen dos versiones: una versión resumida (summary) y una versión detallada (detail). Este es el caso de las líneas 16-18:
El mensaje con la clave _detail (línea 2) es el denominado mensaje detallado. El otro es el denominado mensaje resumido. La etiqueta <h:message> muestra por defecto el mensaje detallado. Este comportamiento se puede modificar mediante los atributos showSummary y showDetail. Esto es lo que se hace para el componente con id «saisie2»:
<!-- línea 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}"/>
En la línea 2, el componente saisie2 está vinculado al siguiente campo form.saisie2:
private Integer saisie2 = 0;
El resultado obtenido es el siguiente:
![]() |
- en [1], el mensaje detallado; en [2], el mensaje resumido.
La etiqueta <h:messages> muestra en forma de lista todos los mensajes de error resumidos de todos los componentes, así como los mensajes de error no relacionados con ningún componente. También en este caso, hay atributos que pueden modificar este comportamiento por defecto:
- showDetail: true / false para solicitar o no los mensajes detallados,
- showSummary: true / false para solicitar o no los mensajes resumidos,
- globalOnly: true / false para solicitar que solo se muestren los mensajes de error no relacionados con componentes. Un mensaje de este tipo podría, por ejemplo, ser creado por el desarrollador.
El mensaje de error asociado a una conversión se puede modificar de diversas formas. En primer lugar, se puede indicar a la aplicación que utilice otro archivo de mensajes. Esta modificación se realiza en [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>
Las líneas 3-8 definen un archivo de mensajes, pero no es este el que utilizan las etiquetas <h:message> y <h:messages>. Para definirlo, hay que utilizar la etiqueta <message-bundle> de la línea 9. La línea 9 indica a las etiquetas <h:message(s)> que el archivo [messages.properties] debe explorarse antes que el archivo [javax.faces.Messages.properties]. Así pues, si añadimos las siguientes líneas al archivo [messages_fr.properties]:
# conversiones
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e
el error devuelto para los componentes saisie1 y saisie2 pasa a ser:

Otra forma de modificar el mensaje de error de conversión es utilizar el atributo converterMessage del componente, tal y como se muestra a continuación para el componente saisie3:
<!-- línea 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}"/>
El componente saisie3 está vinculado al siguiente campo form.saisie3:
private Integer saisie3 = 0;
- En la línea 3, el atributo converterMessage establece explícitamente el mensaje que se mostrará en caso de error de conversión;
- línea 3, el atributo required="true" indica que el campo es obligatorio. El campo no puede quedar vacío. Se considera que un campo está vacío si no contiene ningún carácter o si contiene una secuencia de espacios. También en este caso, en [javax.faces.Messages.properties] hay un mensaje por defecto:
El atributo requiredMessage permite sustituir este mensaje predeterminado. Si el archivo [messages.properties] contiene los siguientes mensajes:
...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
se obtendrá el siguiente resultado:
![]() |
o también este:
![]() |
Comprobar que un valor introducido sea un número entero no siempre es suficiente. A veces hay que comprobar que el número introducido se encuentre dentro de un intervalo determinado. Para ello se utiliza un validador. La entrada n.º 4 ofrece un ejemplo de ello. Su código en [index.xhtml] es el siguiente:
<!-- línea 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}"/>
En la línea 3, el componente saisie4 está vinculado al siguiente modelo form.saisie4:
private Integer saisie4 = 0;
En las líneas 3-5, la etiqueta <h:inputText> tiene una etiqueta hija <f:validateLongRange> que admite dos atributos opcionales: «minimum» y «maximum». Esta etiqueta, también denominada «validador», permite añadir una restricción al valor introducido: no solo debe ser un número entero, sino un número entero comprendido en el intervalo [minimum, maximum] si están presentes los dos atributos minimum y maximum, mayor o igual que minimum si solo está presente el atributo minimum, y menor o igual que maximum si solo está presente el atributo maximum. El validador <f:validateLongRange> tiene mensajes de error predeterminados en [javax.faces.Messages.properties]:
De nuevo, es posible sustituir estos mensajes por otros. Existe un atributo validatorMessage que permite definir un mensaje específico para el componente. Así, con el siguiente 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>
y el siguiente mensaje en [messages.properties]:
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
se obtiene el siguiente resultado:

2.8.5.2. Ejercicios 5 y 6: introducción de un número real
La introducción de números reales sigue reglas similares a las de la introducción de números enteros. El código XHTML de las entradas 5 y 6 es el siguiente:
<!-- línea 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}"/>
<!-- línea 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}"/>
Los elementos de la plantilla [Form.java] relacionados con los componentes saisie5 y saisie6:
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
Mensajes de error asociados a los convertidores y validadores de los componentes saisie5 y saisie6, en [messages.properties]:
double.required=Vous devez entrer un nombre
saisie6.error=6-Vous devez entrer un nombre >=0
A continuación se muestra un ejemplo de ejecución:

2.8.5.3. Entrada 7: introducción de un valor booleano
La introducción de un valor booleano debería realizarse normalmente mediante una casilla de selección. Si se realiza mediante un campo de entrada, la cadena «true» se convierte en el valor booleano «true» y cualquier otra cadena en el valor booleano «false».
El código XHTML del ejemplo:
<!-- línea 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}"/>
La plantilla del componente saisie7:
private Boolean saisie7 = true;
A continuación se muestra un ejemplo de entrada y su respuesta:
![]() |
En [1], el valor introducido. Tras la conversión, esta cadena «x» se convierte en el valor booleano «false». Esto es lo que muestra [2]. El valor [3] de la plantilla no ha cambiado. Solo cambia cuando todas las conversiones y validaciones de la página se han realizado con éxito. Este no era el caso en este ejemplo.
2.8.5.4. Entrada 8: introducción de una fecha
En el ejemplo, la introducción de una fecha se realiza con el siguiente código XHTML:
<!-- línea 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>
El componente saisie8 de la línea 3 utiliza un convertidor java.lang.String <--> java.util.Date. La plantilla form.saisie8 asociada al componente saisie8 es la siguiente:
private Date saisie8 = new Date();
El componente definido por las líneas 7-9 también utiliza un convertidor, pero únicamente en la dirección java.util.Date --> java.lang.String.
El convertidor <f:convertDateTime> admite diversos atributos, entre ellos el atributo «pattern», que establece el formato de la cadena de caracteres que debe transformarse en fecha o con el que debe mostrarse una fecha.
Al realizar la solicitud inicial de la página [index.xhtml], la línea 8 anterior se muestra de la siguiente manera:
Los campos [1] y [2] muestran ambos el valor de la plantilla form.saisie8:
private Date saisie8 = new Date();
donde saisie8 toma como valor la fecha de hoy. El convertidor utilizado en ambos casos para mostrar la fecha es el siguiente:
<f:convertDateTime pattern="dd/MM/yyyy"/>
donde dd (day) indica el número del día, MM (Month) el número del mes y yyyy (year) el año. En [1], el convertidor se utiliza para la conversión inversa java.lang.String --> java.util.Date. Por lo tanto, para que sea válida, la fecha introducida deberá seguir el formato «dd/MM/yyyy».
Existen mensajes predeterminados para las fechas no válidas en [javax.faces.Messages.properties]:
que se pueden sustituir por mensajes propios. Así, en el ejemplo:
<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>
el mensaje que se mostrará en caso de error de conversión será el siguiente mensaje de clave saisie8.error:
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
He aquí un ejemplo:
![]()
2.8.5.5. Entrada 9: introducción de una cadena con longitud restringida
La entrada 9 muestra cómo imponer que una cadena introducida tenga un número de caracteres comprendido dentro de un intervalo:
<!-- línea 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}"/>
En la línea 4, el validador <f:validateLength minimum="4" maximum="4"/> exige que la cadena introducida tenga exactamente 4 caracteres. Solo se puede utilizar uno de los atributos: «minimum» para un número mínimo de caracteres y «maximum» para un número máximo.
El modelo form.saisie9 del componente saisie9 de la línea 3 es el siguiente:
private String saisie9 = "";
Existen mensajes de error predeterminados para este tipo de validación:
que se pueden sustituir utilizando el atributo validatorMessage, tal y como se muestra en la línea 3 anterior. El mensaje de clave saisie9.error es el siguiente:
saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement
A continuación se muestra un ejemplo de ejecución:
![]()
2.8.5.6. Entrada 9B: introducción de una cadena que debe ajustarse a un patrón
La entrada 9B muestra cómo obligar a que una cadena introducida tenga un número de caracteres comprendido en un intervalo:
<!-- línea 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}"/>
En la línea 4, el validador <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> exige que la cadena introducida se ajuste al patrón de una expresión regular, en este caso: una secuencia de 0 o más espacios, 2 dígitos, el signo :, 2 dígitos, una secuencia de 0 o más espacios.
El patrón form.saisie9B del componente saisie9B de la línea 3 es el siguiente:
private String saisie9B;
Existen mensajes de error predeterminados para este tipo de validación:
que se pueden sustituir utilizando el atributo validatorMessage, tal y como se muestra en la línea 3 anterior. El mensaje de clave saisie9.error es el siguiente:
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
A continuación se muestra un ejemplo de ejecución:
![]()
2.8.5.7. Ejercicio 10: escribir un método de validación específico
Resumamos: JSF permite verificar, entre los valores introducidos, la validez de los números (enteros, reales), las fechas, la longitud de las cadenas y la conformidad de una entrada con respecto a una expresión regular. JSF permite añadir a los validadores y convertidores existentes los propios validadores y convertidores. Este punto no se aborda aquí, pero se puede consultar [ref2] para profundizar en él.
Aquí presentamos otro método: el que consiste en validar un dato introducido mediante un método del modelo del formulario. Este es el siguiente ejemplo:
<!-- línea 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}"/>
El modelo form.saisie10 asociado al componente saisie10 de la línea 3 es el siguiente:
private Integer saisie10 = 0;
Queremos que el número introducido sea <1 o >7. No se puede comprobar con los validadores básicos de JSF. Por lo tanto, escribimos nuestro propio método de validación del componente saisie10. Lo indicamos con el atributo «validator» del componente que se va a validar:
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
El componente saisie10 se valida mediante el método form.validateSaisie10. Este es el siguiente:
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);
}
}
La firma de un método de validación debe ser necesariamente la de la línea 1:
- FacesContext context: contexto de ejecución de la página; da acceso a diversa información, en particular a los objetos HttpServletRequest request y HttpServletResponse response,
- UIComponent component: el componente que hay que validar. La etiqueta <h:inputText> está representada por un componente de tipo UIInput derivado de UIComponent. En este caso, es este componente UIInput el que se recibe como segundo parámetro,
- Valor del objeto: el valor introducido que se va a verificar, convertido al tipo de su modelo. Es importante comprender que, si la conversión de String al tipo del modelo ha fallado, el método de validación no se ejecuta. Cuando se llega al método validateSaisie10, significa que la conversión de String a Integer se ha realizado correctamente. El tercer parámetro es, por tanto, de tipo Integer.
- línea 2: el valor introducido se transforma en el tipo int,
- línea 3: se comprueba si el valor introducido es <1 o >7. Si es así, la validación ha finalizado. Si no es así, el validador debe señalar el error lanzando una excepción de tipo ValidatorException.
La clase ValidatorException tiene dos constructores:
![]() |
- el constructor [1] tiene como parámetro un mensaje de error de tipo FacesMessage. Este tipo de mensaje es el que muestran las etiquetas <h:messages> y <h:message>,
- el constructor [2] permite, además, encapsular la causa de tipo Throwable o derivada del error.
Tenemos que crear un mensaje de tipo FacesMessage. Esta clase tiene varios constructores:
![]() |
El constructor [1] define las propiedades de un objeto FacesMessage:
- FacesMessage.Severity severity: un nivel de gravedad tomado de la siguiente enumeración: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
- Cadena de resumen: la versión resumida del mensaje de error, que se muestra mediante las etiquetas <h:message showSummary="true"> y <h:messages>,
- Cadena de detalle: la versión detallada del mensaje de error, que se muestra mediante las etiquetas <h:message> y <h:messages showDetail="true">.
Se puede utilizar cualquiera de los constructores, ya que los parámetros que falten se pueden establecer posteriormente mediante los métodos set.
El constructor [1] no permite especificar un mensaje que se encuentre en un archivo de mensajes internacionalizado. Es, evidentemente, una lástima. David Geary y Cay Horstmann [ref2] subsanan esta carencia en su libro «Core JavaServer Faces» con la clase de utilidades com.corejsf.util.Messages. Es esta clase la que se utiliza en la línea 4 del código Java para crear el mensaje de error. Solo contiene métodos estáticos, entre ellos el método getMessage utilizado en la línea 4:
public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)
El método getMessage admite tres parámetros:
- String bundleName: el nombre de un archivo de mensajes sin su sufijo .properties, pero con su nombre de paquete. En este caso, nuestro primer parámetro podría ser messages para designar el archivo [messages.properties]. Antes de utilizar el archivo designado por el primer parámetro, getMessage intenta utilizar el archivo de mensajes de la aplicación, si lo hay. Así, si en [faces-config.xml] se ha declarado un archivo de mensajes con la etiqueta:
<application>
...
<message-bundle>messages</message-bundle>
</application>
se puede pasar null como primer parámetro al método getMessage. Esto es lo que se ha hecho aquí (véase [web.xm], página 120),
- Cadena resourceId: la clave del mensaje que se va a procesar en el archivo de mensajes. Hemos visto que un mensaje puede tener tanto una versión resumida como una versión detallada. resourceId es el identificador de la versión resumida. La versión detallada se buscará automáticamente con la clave resourceId_detail. De este modo, tendremos dos mensajes en [messages.properties] correspondientes al error de la entrada n.º 10:
saisie10.incorrecte=10-Saisie n° 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
El mensaje de tipo FacesMessage generado por el método Messages.getMessage incluye tanto la versión resumida como la detallada, si se han encontrado. Ambas versiones deben estar presentes; de lo contrario, se produce una excepción de tipo [NullPointerException],
- Object[] params: los parámetros efectivos del mensaje si este tiene parámetros formales {0}, {1}, ... Estos parámetros formales se sustituirán por los elementos del array params.
Volvamos al código del método de validación del 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);
}
}
- en [4], el mensaje de tipo FacesMessage se crea mediante el método estático Messages.getMessage,
- en [5], se establece el nivel de gravedad del mensaje,
- en [6], se lanza una excepción de tipo ValidatorException con el mensaje creado anteriormente. El método de validación ha sido invocado por el siguiente código XHTML:
<!-- línea 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}"/>
En la línea 3, se ejecuta el método de validación para el componente con id saisie10. Por lo tanto, el mensaje de error generado por el método validateSaisie10 se asocia a este componente y, por lo tanto, se muestra en la línea 4 (atributo for="saisie10"). La etiqueta <h:message> muestra por defecto la versión detallada.
A continuación se muestra un ejemplo de ejecución:
![]()
2.8.5.8. Entradas 11 y 12: validación de un grupo de componentes
Hasta ahora, los métodos de validación que hemos visto solo validaban un único componente. ¿Qué hay que hacer si la validación deseada afecta a varios componentes? Eso es lo que vamos a ver ahora. En el formulario:

queremos que las entradas 11 y 12 sean dos números enteros cuya suma sea igual a 10.
El código JSF será el siguiente:
<!-- línea 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}"/>
<!-- línea 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}"/>
y la plantilla asociada:
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
En la línea 3 del código JSF, se utilizan las técnicas ya presentadas para comprobar que el valor introducido para el componente saisie11 es efectivamente un número entero. Lo mismo ocurre, en la línea 11, con el componente saisie12. Para comprobar que saisie11 + saisie12 = 10, se podría crear un validador específico. Esta es la solución más recomendable. De nuevo, consultaremos [ref2] para descubrirla. Aquí seguimos otro enfoque.
La página [index.xhtml] se valida mediante un botón [Valider] cuyo código JSF es el siguiente:
<!-- botones de control -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
...
</h:panelGrid>
donde el mensaje msg['submit'] es el siguiente:
submit=Valider
En la línea 3 se ve que se ejecutará el método form.submit para gestionar el clic en el botón [Valider]. Este es el siguiente:
// acciones
public String submit() {
// últimas validaciones
validateForm();
// se vuelve a enviar el mismo formulario
return null;
}
// validaciones globales
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
...
}
Es importante comprender que, cuando se ejecuta el método «submit»:
- se habrán ejecutado con éxito todos los validadores y convertidores del formulario,
- los campos del modelo [Form.java] han recibido los valores enviados por el cliente.
De hecho, volvamos al ciclo de procesamiento de un POST JSF:
![]() |
El método submit es un gestor de eventos. Gestiona el evento clic del botón [Valider]. Al igual que todos los controladores de eventos, se ejecuta en la fase [E], una vez que se han ejecutado con éxito todos los validadores y convertidores ([C]) y se ha actualizado la plantilla con los valores enviados ([D]). Por lo tanto, ya no se trata aquí de lanzar excepciones del tipo [ValidatorException] como hemos hecho anteriormente. Nos limitaremos a devolver el formulario con mensajes de error:
![]() |
En [1], avisaremos al usuario y, en [2] y [3], mostraremos un indicador de error. En el código JSF, el mensaje [1] se obtendrá de la siguiente manera:
<h:form id="formulaire">
<h:messages globalOnly="true" />
<h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
<!-- línea 1 -->
...
En la línea 2, la etiqueta <h:messages> muestra por defecto la versión resumida de los mensajes de error de todas las entradas erróneas de los componentes del formulario, así como todos los mensajes de error no relacionados con componentes. El atributo globalOnly="true" limita la visualización a estos últimos.
Los mensajes [2] y [3] se muestran con simples etiquetas <h:outputText>:
<!-- línea 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}"/>
<!-- línea 13 -->
...
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
...
En las líneas 4-7, el componente saisie11 tiene dos posibles mensajes de error:
- el que indica una conversión errónea o la ausencia de datos. Este mensaje, generado por el propio JSF, se incluirá en un tipo FacesMessage y se mostrará mediante la etiqueta <h:message> de la línea 5,
- el que vamos a generar si entrada11 + entrada12 no es igual a 10. Se mostrará en la línea 6. El mensaje de error estará contenido en la plantilla form.errorSaisie11.
Ambos mensajes corresponden a errores que no pueden producirse al mismo tiempo. La comprobación «entrada11 + entrada12 = 10» se realiza en el método submit, que solo se ejecuta si no queda ningún error en el formulario. Cuando se ejecute, el componente saisie11 ya se habrá comprobado y su modelo form.saise11 habrá recibido su valor. El mensaje de la línea 5 ya no podrá mostrarse. Por el contrario, si se muestra el mensaje de la línea 5, significa que queda al menos un error en el formulario y el método submit no se ejecutará. El mensaje de la línea 6 no se mostrará. Para que los dos posibles mensajes de error aparezcan en la misma columna de la tabla, se han agrupado en una etiqueta <h:panelGroup> (líneas 4 y 7).
El método submit es el siguiente:
// acciones
public String submit() {
// últimas validaciones
validateForm();
// se vuelve a enviar el mismo formulario
return null;
}
// validaciones globales
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
// mensaje global
FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
// mensajes relacionados con los campos
message = Messages.getMessage(null, "error.sign", null);
setErrorSaisie11(message.getSummary());
setErrorSaisie12(message.getSummary());
} else {
setErrorSaisie11("");
setErrorSaisie12("");
}
}
- línea 4: el método submit llama al método validateForm para realizar las últimas validaciones,
- línea 11: se comprueba si saisie11+saisie12 = 10;
- si no es así, en las líneas 13-14, se crea un mensaje de tipo FacesMessage con el mensaje de ID saisies11et12.incorrectes. Este es el siguiente:
saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
- el mensaje así creado se añade (líneas 15-16) a la lista de mensajes de error de la aplicación. Este mensaje no está vinculado a ningún componente concreto. Se trata de un mensaje global de la aplicación. Se mostrará mediante la etiqueta <h:messages globalOnly="true"/> presentada anteriormente,
- línea 18: se crea un nuevo mensaje de tipo FacesMessage con el mensaje de ID error.sign. Este es el siguiente:
error.sign="!"
Ya hemos dicho que el método estático [Messages.getMessage] construye un mensaje de tipo FacesMessage con una versión resumida y una versión detallada, si existen. En este caso, solo existe la versión resumida del mensaje error.sign. La versión resumida de un mensaje m se obtiene mediante m.getSummary(). En las líneas 19 y 20, la versión resumida del mensaje error.sign se introduce en los campos errorSaisie11 y errorSaisie12 de la plantilla. Se mostrarán mediante las siguientes etiquetas JSF:
<h:outputText value="#{form.saisie11}"/>
...
<h:outputText value="#{form.saisie12}"/>
- líneas 22-23: si se cumple la propiedad saisie11+saisie12=10, se vacían los dos campos errorSaisie11 y errorSaisie12 de la plantilla para borrar cualquier mensaje de error anterior. Hay que tener en cuenta que el modelo se conserva entre las solicitudes, en la sesión del cliente.
A continuación se muestra un ejemplo de ejecución:
![]() |
Se observará en la columna [1] que la plantilla ha recibido los valores enviados, lo que demuestra que todas las operaciones de validación y conversión entre los valores enviados y la plantilla se han realizado con éxito. De este modo, se ha podido ejecutar el gestor de eventos form.submit, que gestiona el clic en el botón [Valider]. Es este el que ha generado los mensajes mostrados en [2] y [3]. Se observa que la plantilla se ha actualizado a pesar de que el formulario ha sido rechazado y devuelto al cliente. Podría ser deseable que la plantilla no se actualizara en un caso así. De hecho, suponiendo que el usuario cancele su actualización con el botón [Annuler] [4], no se podrá volver a la plantilla inicial a menos que se haya guardado.
2.8.5.9. POST de un formulario sin validación de los datos introducidos
Consideremos el formulario anterior y supongamos que el usuario, al no comprender sus errores, desea abandonar la cumplimentación del formulario. En ese caso, utilizará el botón [Annuler] generado por el siguiente código JSF:
<!-- botones 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>
En la línea 4, el mensaje msg['cancel'] es el siguiente:
cancel=Annuler
El método form.cancel asociado al botón [Annuler] solo se ejecutará si el formulario es válido. Esto es lo que hemos mostrado para el método form.submit asociado al botón [Valider]. Si el usuario desea cancelar la cumplimentación del formulario, por supuesto no tiene sentido comprobar la validez de los datos introducidos. Este resultado se obtiene con el atributo immediate="true", que indica a JSF que ejecute el método form.cancel sin pasar por la fase de validación y conversión. Volvamos al ciclo de procesamiento de POST JSF:
![]() |
Los eventos de los componentes de acción <h:commandButton> y <h:commandLink> que tienen el atributo immediate="true" se procesan en la fase [C] y, a continuación, el ciclo JSF pasa directamente a la fase [E] de generación de la respuesta.
El método form.cancel es el siguiente:
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;
}
Si se utiliza el botón [Annuler] en el formulario anterior, se obtiene como resultado la siguiente página:
![]() |
- se vuelve a obtener el formulario porque el gestor de eventos form.cancel devuelve la clave de navegación null. Por lo tanto, se devuelve la página [index.xhtml],
- la plantilla [Form.java] ha sido modificada por el método form.cancel. Esto queda reflejado en la columna [2], que muestra dicha plantilla,
- mientras que la columna [3] refleja el valor contabilizado para los componentes.
Volvamos al código JSF del componente saisie1 [4];
<!-- línea 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}"/>
En la línea 4, el valor del componente saisie1 está vinculado al modelo form.saisie1. Esto tiene varias consecuencias:
- al realizar un GET de [index.xhtml], el componente saisie1 mostrará el valor de la plantilla form.saisie1,
- al realizar un POST a partir de [index.xhtml], el valor enviado para el componente saisie1 solo se asigna a la plantilla form.saisie1 si todas las validaciones y conversiones del formulario se realizan correctamente. Independientemente de si el modelo se ha actualizado o no con los valores enviados, si el formulario se reenvía al finalizar el POST, los componentes muestran el valor que se ha enviado y no el valor del modelo que tienen asociado. Así lo muestra la captura de pantalla anterior, en la que las columnas [2] y [3] no tienen los mismos valores.
2.9. Ejemplo mv-jsf2-07: eventos relacionados con el cambio de estado de los componentes JSF
2.9.1. La aplicación
La aplicación muestra un ejemplo de POST realizado sin necesidad de un botón ni de un enlace. El formulario es el siguiente:
![]() |
El contenido de la lista combo2 [2] está vinculado al elemento seleccionado en combo1 [1]. Al cambiar la selección en [1], se ejecuta un POST del formulario, durante el cual se modifica el contenido de combo2 para reflejar el elemento seleccionado en [1], y a continuación se devuelve el formulario. Durante este POST, no se realiza ninguna validación.
2.9.2. El proyecto NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
Hay un único formulario [index.xhtml] con su plantilla [Form.java].
2.9.3. El entorno de la aplicación
El archivo de mensajes [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
La hoja 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. El formulario [index.xhtml]
El formulario [index.xhtml] es el siguiente:
<?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">
<!-- encabezados -->
<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"/>
<!-- línea 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}"/>
<!-- línea 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}"/>
<!-- línea 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>
<!-- botones de comando -->
<h:panelGrid columns="2" border="0">
<h:commandButton value="#{msg['submit']}"/>
...
</h:panelGrid>
</h:form>
</h:body>
</html>
La novedad reside en el código de la lista combo1, líneas 24-26. Aparecen nuevos atributos:
- onchange: atributo HTML —declara una función o código JavaScript que debe ejecutarse cuando cambia el elemento seleccionado en combo1. En este caso, el código JavaScript submit() envía el formulario al servidor,
- valueChangeListener: atributo de JSF — especifica el nombre del método que se debe ejecutar en el servidor cuando cambia el elemento seleccionado en combo1. En total, se ejecutan dos métodos: uno en el lado del cliente y otro en el lado del servidor,
- immediate=true: atributo JSF: establece el momento en el que debe ejecutarse el controlador de eventos del lado del servidor: después de que el formulario se haya reconstruido tal y como lo ha introducido el usuario, pero antes de las comprobaciones de validez de los datos introducidos. Lo que se pretende aquí es rellenar la lista combo2 en función del elemento seleccionado en la lista combo1, aunque por lo demás pueda haber entradas erróneas en el formulario. He aquí un ejemplo:
![]() |
- en [1], una primera entrada,
- en [2], se cambia el elemento seleccionado de combo1 de A a B.
El resultado obtenido es el siguiente:
![]() |
Se ha producido el POST. El contenido de combo2 y [2] se ha adaptado alelemento seleccionado en combo1 y [1], aunque la entrada de [3] era incorrecta. El atributo immediate=true fue el que provocó que se ejecutara el método form.combo1ChangeListener antes de los controles de validez. Sin este atributo, no se habría ejecutado, ya que el ciclo de procesamiento se habría detenido en los controles de validez debido al error en [3].
Los mensajes asociados al formulario son los siguientes en [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
El ciclo de vida de [Form.java] se establece en «request»:
package forms;
...
@ManagedBean
@RequestScoped
public class Form {
En la línea 6, se establece el ámbito del bean en «request».
2.9.5. La plantilla [Form.java]
El modelo [Form.java] es el siguiente:
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 del formulario
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// campos de trabajo
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(){
// inicialización del combo2 en función del 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;
}
// escuchas
public void combo1ChangeListener(ValueChangeEvent event){
// seguimiento
logger.info("combo1ChangeListener");
// se recupera el valor enviado por combo1
combo1Label=(String)event.getNewValue();
// se devuelve la respuesta porque se quiere saltarse las validaciones
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// seguimiento
logger.info("raz");
// borrado del formulario
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
// getter y setter
...
}
Vinculamos el formulario [index.xhtml] a su modelo [Form.java]:
La lista combo1 se genera mediante el siguiente código JSF:
<h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
<f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>
Obtiene sus elementos mediante el método getCombo1Items de su plantilla (línea 2). Este método se define en las líneas 28-35 del código Java. Genera una lista de tres elementos {"A", "B", "C"}.
La lista combo2 se genera mediante el siguiente código JSF:
<h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
<f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>
Obtiene sus elementos mediante el método getCombo2Items de su modelo (línea 2). Este se define en las líneas 37-44 del código Java. Genera una lista de cinco elementos {"X1", "X2", "X3", "X4", "X5"}, donde X es el elemento combo1Label de la línea 16. Por lo tanto, al generar el formulario por primera vez, la lista combo2 contiene los elementos {"A1","A2","A3", "A4", "A5"}.
Cuando el usuario cambie el elemento seleccionado en la lista combo1,
- el navegador del cliente procesará el evento onchange="submit();". De este modo, el formulario se enviará al servidor,
- en el lado del servidor, JSF detectará que el componente combo1 ha cambiado de valor. Se ejecutará el método combo1ChangeListener de las líneas 47-54. Un método de tipo ValueChangeListener recibe como parámetro un objeto de tipo javax.faces.event.ValueChangeEvent. Este objeto permite obtener el valor anterior y el nuevo del componente cuyo valor ha cambiado mediante los siguientes métodos:

En este caso, el componente es la lista combo1 de tipo UISelectOne. Su valor es de tipo String.
- línea 51 del modelo Java: el nuevo valor de combo1 se almacena en combo1Label, que se utiliza para generar los elementos de la lista combo2,
- línea 53: se devuelve la respuesta. Hay que recordar aquí que el gestor combo1ChangeListener se ejecuta con el atributo immediate="true". Por lo tanto, se ejecuta después de la fase en la que se ha actualizado el árbol de componentes de la página con los valores enviados y antes del proceso de validación de dichos valores. Sin embargo, queremos evitar este proceso de validación porque la lista combo2 debe actualizarse aunque, por otra parte, en el formulario queden entradas erróneas. Por lo tanto, se solicita que la respuesta se envíe inmediatamente sin pasar por la fase de validación de las entradas.
- El formulario se reenviará tal y como se ha rellenado. Sin embargo, los elementos de las listas combo1 y combo2 no son valores registrados. Se volverán a generar mediante la llamada a los métodos getCombo1Items y getCombo2Items. Este último método utilizará entonces el nuevo valor de combo1Label establecido por combo1ChangeListener, y los elementos de la lista combo2 cambiarán.
2.9.6. El botón [Raz]
Con el botón [Raz] queremos restablecer el formulario a su estado inicial, tal y como se muestra a continuación:
![]() |
![]() |
![]() |
En [1], el formulario antes de POST del botón [Raz]; en [2], el resultado de POST.
Aunque es sencillo desde el punto de vista funcional, la gestión de este caso de uso resulta bastante compleja. Se pueden probar diversas soluciones, en particular la utilizada para el botón [Annuler] del ejemplo anterior:
<h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>
donde el método form.raz es el siguiente:
public String raz(){
// borrado del formulario
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
El resultado obtenido con el botón [Raz] del ejemplo anterior es, por tanto, el siguiente:
![]() |
La columna [1] muestra que se ha ejecutado el método form.raz. Sin embargo, la columna [1] sigue mostrando los valores registrados:
- para combo1, el valor contabilizado era «B». Por lo tanto, este elemento se selecciona en la lista;
- para combo2, el valor registrado era «B5». Debido a la ejecución de form.raz, los elementos {«B1», ..., «B5»} de combo2 se han cambiado por {«A1», ..., «A5»}. El elemento «B5» ya no existe y, por lo tanto, no se puede seleccionar. En ese caso, se muestra el primer elemento de la lista,
- en el caso de saisie1, el valor enviado era 10.
Este es el comportamiento normal con el atributo immediate="true". Para obtener un resultado diferente, hay que enviar los valores que queremos que aparezcan en el nuevo formulario, aunque el usuario haya introducido otros valores. Esto se consigue con un poco de código JavaScript del lado del cliente. El formulario queda así:
<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}"/>
- en la línea 10, el atributo onclick='raz()' indica que se ejecute la función de JavaScript raz cuando el usuario haga clic en el botón [Raz];
- línea 3: se asigna el valor «A» al elemento HTML con el nombre «formulario:combo1». Los distintos elementos de la línea 3 son los siguientes:
- documento: página mostrada por el navegador,
- document.forms: conjunto de formularios del documento,
- document.forms['formulaire']: el formulario con el atributo name="formulaire",
- documents.forms['formulaire'].elements: conjunto de elementos del formulario que tienen el atributo name="formulaire",
- document.forms['formulaire'].elements['formulaire:combo1']: elemento del formulario que tiene el atributo name="formulaire:combo1"
- document.forms['formulaire'].elements['formulaire:combo1'].value: valor que enviará el elemento del formulario con el atributo name="formulaire:combo1".
Para conocer los atributos name de los distintos elementos de la página mostrada por el navegador, se puede consultar su código fuente (a continuación con IE7):
![]() |
<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>
Una vez explicado esto, se entiende que, en el código JavaScript de la función raz:
- la línea 3 hace que el valor enviado para el componente combo1 sea la cadena A,
- la línea 4 hace que el valor enviado al componente combo2 sea la cadena A1,
- la línea 5 hace que el valor enviado al componente saisie1 sea la cadena 0.
Una vez hecho esto, se ejecutará el POST del formulario, asociado a cualquier botón de tipo <h:commandButton> (línea 10). Se ejecutará el método form.raz y se devolverá el formulario tal y como se ha enviado. De este modo, se obtiene el siguiente resultado:
![]() |
Este resultado esconde muchas cosas. Los valores «A», «A1» y «0» de los componentes combo1, combo2 y saisie1 se envían al servidor. Supongamos que el valor anterior de combo1 era «B». En ese caso, se produce un cambio en el valor del componente combo1 y el método form.combo1ChangeListener también debería ejecutarse. Tenemos dos controladores de eventos con el atributo immediate="true". ¿Se ejecutarán ambos? En caso afirmativo, ¿en qué orden? ¿Solo uno? En caso afirmativo, ¿cuál?
Para obtener más información, creamos registros en la aplicación:
package forms;
import java.util.logging.Logger;
...
public class Form {
...
// campos del formulario
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// campos de trabajo
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// escucha
public void combo1ChangeListener(ValueChangeEvent event){
// seguimiento
logger.info("combo1ChangeListener");
// se recupera el valor enviado de combo1
combo1Label=(String)event.getNewValue();
// se devuelve la respuesta porque se quiere eludir las validaciones
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// continuación
logger.info("raz");
// borrado del formulario
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
...
}
- línea 16: se crea un generador de registros. El parámetro de getLogger permite diferenciar los orígenes de los registros. En este caso, el generador de registros se llama forms.Form,
- línea 21: se registra la entrada en el método combo1ChangeListener,
- línea 30: se registra la entrada en el método raz.
¿Qué registros se generan al pulsar el botón [Raz] o al cambiar el valor de combo1? Consideremos varios casos:
- se utiliza el botón [Raz] cuando el elemento seleccionado en combo1 es «A». Por lo tanto, «A» es el último valor del componente combo1. Hemos visto que el botón [Raz] ejecutaba una función de JavaScript que enviaba el valor «A» para el componente combo1. Por lo tanto, el componente combo1 no cambia de valor. Los registros muestran entonces que solo se ejecuta el método form.raz:
- se utiliza el botón [Raz], aunque el elemento seleccionado en combo1 no es «A». Por lo tanto, el componente combo1 cambia de valor: su último valor no era «A» y el botón [Raz] le enviará el valor «A». Los registros muestran entonces que se ejecutan dos métodos. En este orden: combo1ChangeListener, raz:
![]() |
- Se cambia el valor de combo1 sin utilizar el botón [Raz]. Los registros muestran que solo se ejecuta el método combo1ChangeListener:
![]() |
2.10. Ejemplo mv-jsf2-08: la etiqueta <h:dataTable>
2.10.1. La aplicación
La aplicación muestra una lista de personas con la posibilidad de eliminarlas:
![]() |
- en [1], una lista de personas,
- en [2], los enlaces que permiten eliminarlas.
2.10.2. El proyecto de NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
Tenemos un único formulario [index.xhtml] con su plantilla [Form.java].
2.10.3. El entorno de la aplicación
El archivo de configuración [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>
El archivo de mensajes [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
La hoja 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. El formulario [index.xhtml] y su plantilla [Form.java]
Recordemos la vista asociada a la página [index.xhtml]:
![]() |
El formulario [index.xhtml] es el siguiente:
<?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>
En la línea 14, la etiqueta <h:dataTable> utiliza el campo #{form.personnes} como fuente de datos. Es el siguiente:
private List<Persona> personas;
La clase [Personne] es la siguiente:
package forms;
public class Personne {
// datos
private int id;
private String nom;
private String prénom;
// fabricantes
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);
}
// getter y setters
...
}
Volvamos al contenido de la etiqueta <h:dataTable>:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
- el atributo var="personne" establece el nombre de la variable que representa a la persona actual dentro de la etiqueta <h:datatable>,
- el atributo headerClass="headers" establece el estilo de los encabezados de las columnas de la tabla,
- el atributo columnClasses="...." establece el estilo de cada una de las columnas de la tabla.
Analicemos una de las columnas de la tabla y veamos cómo está construida:
![]() |
El código XHTML de la columna Id es el siguiente:
<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>
- líneas 3-7: la columna «id» de la tabla,
- líneas 8-13: la columna «nombre» de la tabla,
- líneas 14-19: la columna «nombre» de la tabla.
Ahora, veamos la columna de enlaces [Retirer]:
![]() |
Esta columna se genera mediante el siguiente código:
<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>
El enlace [Retirer] se genera mediante las líneas 4-6. Al hacer clic en el enlace, se ejecutará el método [Form].retirerPersonne. Es el momento de examinar la clase [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;
// constructor
public Form() {
// inicialización de la lista de personas
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() {
// se busca la persona seleccionada
int i = 0;
for (Personne personne : personnes) {
// ¿Persona actual = persona seleccionada?
if (personne.getId() == personneId) {
// se elimina la persona actual de la lista
personnes.remove(i);
// Se ha completado
break;
} else {
// persona siguiente
i++;
}
}
// se comprueba en la misma página
return null;
}
// getters y setters
...
}
- líneas 18-24: el constructor inicializa la lista de personas de la línea 14,
- línea 10: dado que esta lista debe mantenerse activa a lo largo de las consultas, el ámbito del bean es la sesión.
Cuando se ejecuta el método [retirerPersonne] de la línea 26, el campo de la línea 15 se ha inicializado con el ID de la persona cuyo enlace [Retirer] se ha pulsado:
<h:commandLink value="Retirer" action="#{form.retirerPersonne}">
<f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>
La etiqueta <f:setPropertyActionListener> permite transferir información al modelo. En este caso, el valor del atributo «value» se copia en el campo del modelo identificado por el atributo «target». De este modo, el ID de la persona actual, aquella que hay que eliminar de la lista de personas, se copia en el campo [Form].personneId a través del getter de dicho campo. Esto se realiza antes de la ejecución del método al que hace referencia el atributo «action» de la línea 1.
Líneas 26-43: el método [supprimerPersonne] elimina a la persona cuyo id es igual a personneId.
2.11. Ejemplo o mv-jsf2-09: diseño de una aplicación JSF
2.11.1. La aplicación
La aplicación muestra cómo diseñar una aplicación JSF con dos vistas:
![]() |
La aplicación tiene dos vistas:
- en [1], la página 1,
- en [2], la página 2.
Se puede navegar entre ambas páginas. Lo que queremos mostrar aquí es que las páginas 1 y 2 comparten un formato común, tal y como se aprecia en las capturas de pantalla anteriores.
2.11.2. El proyecto NetBeans
El proyecto NetBeans de la aplicación es el siguiente:
![]() |
La aplicación solo tiene páginas XHTML. No hay ninguna plantilla Java asociada.
2.11.3. La página [layout.xhtml]
La página [layout.xhtml] define el formato de las páginas de la aplicación:
<?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>
En la línea 7 aparece un nuevo espacio de nombres «ui». Este espacio de nombres contiene las etiquetas que permiten dar formato a las páginas de una aplicación. Las etiquetas de este espacio se utilizan en las líneas 17, 22, 25 y 32.
La página [layout.xhtml] muestra información en una tabla HTML (línea 14). Se puede acceder a esta página con un navegador:
![]() |
- en [1], la página URL solicitada.
El campo [2] se ha generado mediante el siguiente código XHTML:
<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>
La etiqueta <ui:include> de la línea 6 permite incluir en la página un código XHTML externo. El archivo [entete.xhtml] es el siguiente:
<?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 el código de las líneas 3 a 8 se insertará en [layout.xhtml]. De este modo, las etiquetas <html> y <body> se insertarán dentro de una etiqueta <td>. Esto no provoca ningún error. Por lo tanto, las páginas insertadas mediante <ui:include> son páginas XHTML completas. Desde un punto de vista visual, solo la línea 6 tendrá algún efecto. Las etiquetas <html> y <body> están presentes por razones sintácticas.
El área [3] se ha generado mediante el siguiente 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>
La etiqueta <ui:include> de la línea 5 incluye el siguiente archivo [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>
El campo [4] se ha generado a partir del siguiente código XHTML:
<h:form id="formulaire">
<table style="width: 400px">
...
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
La etiqueta <ui:include> de la línea 6 incluye el siguiente archivo [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>
El campo [5] se ha generado a partir del siguiente código XHTML:
<h:form id="formulaire">
...
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
...
</table>
</h:form>
La etiqueta <ui:insert> de la línea 5 define un área denominada «contenido». Se trata de un área que puede albergar contenido variable. Veremos cómo. Cuando solicitamos la página [layout.xhtml], no se había definido ningún contenido para la zona denominada «contenido». En este caso, se utiliza el contenido de la etiqueta <ui:insert> de las líneas 4-6. Por lo tanto, se muestra la línea 5.
2.11.4. La página [page1.xhtml]
La página [layout.xhtml] no está destinada a ser visualizada. Sirve de plantilla para las páginas [page1.xhtml] y [page2.xhtml]. Se habla de plantillas de páginas. La página [page1.xhtml] es la siguiente:
<?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>
- en la línea 6, se utiliza el espacio de nombres ui;
- en la línea 7, se indica que la página está asociada a la plantilla [layout.xhtml] mediante una etiqueta <ui:composition>,
- en la línea 8, esta asociación hace que cada etiqueta <ui:define> se asocie a una etiqueta <ui:insert> de la plantilla utilizada, en este caso [layout.xhtml]. La vinculación se realiza mediante el atributo «name» de ambas etiquetas, que deben ser idénticos.
La página que se muestra es [layout.xhtml], donde el contenido de cada etiqueta <ui:insert> se sustituye por el contenido de la etiqueta <ui:define> de la página solicitada. En este caso, es como si la página mostrada fuera:
<?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>
Las líneas 25-26 de [page1.xhtml] se han insertado en lugar de la etiqueta <ui:insert> de [layout.xml].
La página [page2.xhtml] es análoga a [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
El análisis que acabamos de realizar de JSF 2 dista mucho de ser exhaustivo. Sin embargo, es suficiente para comprender los ejemplos que vendrán a continuación. Para profundizar en el tema, se puede consultar [ref2].
2.13. Las pruebas con Eclipse
Veamos cómo realizar pruebas en proyectos Maven con SpringSource Tool Suite:
![]() |
- En [1], se importa un proyecto Maven [2] que se selecciona con el botón [3]. Aquí se toma el proyecto Maven [mv-jsf2-09] para Eclipse
- en [4], el proyecto importado se ha reconocido correctamente como un proyecto Maven [5],
![]() |
- en [6]; el proyecto se ha importado al explorador de proyectos,
- en [7], se ejecuta en un servidor [8] Tomcat [9],
![]() |
- en [10], se ha iniciado Tomcat 7,
- en [11], la página de inicio del proyecto [mv-jsf2-09] [11] se muestra en un navegador interno de Eclipse.

























































































































































