JSF2 Flash scope.
0. Índice de contenidos.
1. Introducción
El ámbito de flash en JSF2 proporciona un modo estandar de pasar objetos temporales entre distintas vistas,
generadas por el propio ciclo de vida.
Es un concepto tomado de Ruby On Rails
que permite a cualquiera añadir un objeto en el ámbito de flash y este será expuesto en la siguiente vista con la misma
sesión de usuario; para ello, la siguiente vista debe tener el mismo viewId que la anterior.
Podemos jugar con el ámbito de flash para intentar emular un wizard de registro en el que entre varias vistas se necesita la gestión de un único objeto,
el bean de usuario. De este modo, en cada vista se mantiene información parcial del objeto.
Tenemos muchas opciones para realizar un wizard de registro o un carrito de la compra en el ámbito de JSF, la más inmediata es acudir
al ámbito de sesión, pero en ese escenario tenemos que encargarnos manualmente de sacar el objeto de la sesión; otras opciones son:
- usar el ámbito conversacional si tenemos el soporte de Jboss Seam,
- usar CDI, estándar en JEE6, para gestionar el ámbito de los ManagedBean o una extensión del mismo como
Myfaces CODI, - Spring Web Flow integrado con JSF,
- hacer uso de un componente de wizard como el que tiene Primefaces,
- …
Con el soporte de JSF2, sin necesidad de ninguna extensión, ahora tenemos disponible del ámbito de flash y, en este tutorial, vamos a jugar con él para
mantener el ámbito de un objeto entre distintas vistas sin necesidad de acudir al ámbito de sesión.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.4 GHz Intel Core i7, 8GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Lion 10.7.4
- JSF 2.1.12
3. Escenario.
El escenario que vamos a proponer es el de un registro de usuarios en el que necesitaremos, al menos tres pasos:
- introducción de datos personales,
- introducción de login y contraseña,
- confirmación de registro.
Se podría realizar en dos pasos, por supuesto, pero no tendríamos escenario para nuestro tutorial 😉
4. Implementación.
Lo primero es disponer de un bean de usuario que podría tener la siguiente información:
public class Usuario { private String nombre; private String apellidos; @Email private String email; private String password; // TODO: getters & setters... }
Con el soporte de Bean Validator, las anotaciones de validación las podemos incluir en la propia entidad.
El primer paso del registro es crear una instancia nueva de Usuario y añadirla al ámbito de flash, para ello podríamos disponer de un primer controlador como el que sigue:
import javax.faces.bean.ManagedBean; import com.autentia.training.core.persistence.entities.Usuario; import com.sun.faces.context.flash.ELFlash; @ManagedBean public class RegistrationStep1 { private static final String BEAN_KEY = "usuario"; public void initUser(){ ELFlash.getFlash().put(BEAN_KEY, new Usuario()); } public String next(){ final Usuario usuario = (Usuario) ELFlash.getFlash().get(BEAN_KEY); System.out.println(usuario); return "registration-step2?faces-redirect=true"; } }
Para añadir objetos al ámbito de flash usamos ELFlash.getFlash().put("", "");
aunque también podemos acceder a través de FacesContext.getCurrentInstance().getExternalContext().getFlash().put(key, value);
La primera vista (register-step1.xhtml) que programa la ejecución del método initUser antes de renderizar la página y hace uso del objeto añadido al ámbito de flash tendría un código como el que sigue:
<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <ui:composition template="/WEB-INF/templates/defaultLayout.xhtml"> <ui:define name="title">Paso 1 del registro</ui:define> <ui:define name="content"> <f:metadata> <f:event type="preRenderView" listener="#{registrationStep1.initUser}" /> </f:metadata> <h:form> <h:panelGrid columns="3"> <h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" /> <h:inputText id="nombre" value="#{flash.usuario.nombre}" required="true" /> <h:message for="nombre" /> <h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" /> <h:inputText id="apellidos" value="#{flash.usuario.apellidos}" required="true" /> <h:message for="apellidos" /> </h:panelGrid> <h:commandButton value="#{msg['action.next']}" action="#{registrationStep1.next}" /> </h:form> </ui:define> </ui:composition> </html>
Esta basada en plantillas de facelets y, a nivel visual, tendríamos algo similar a este formulario:
El segundo paso, accesible tras pulsar el botón, por convención de reglas de navegación buscará una página register-step2.xhtml y además realizará un redirect de modo que cambiará la url, con ello comprobamos que el ámbito del objeto se propaga a la siguiente vista.
En el método next obtenemos el usuario para comprobar que efectivamente se dispone de las propiedades pobladas del mismo al imprimirlo por consola.
El código de la segunda vista (registration-step2.xhtml) también invocará a un método de evento que accederá al bean en el ámbito de flash para retenerlo a la siguiente vista en el método keepUser:
<?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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <ui:composition template="/WEB-INF/templates/defaultLayout.xhtml"> <ui:define name="title">Paso 2 del registro</ui:define> <ui:define name="content"> <f:metadata> <f:event type="preRenderView" listener="#{registrationStep2.keepUser}" /> </f:metadata> <h:form> <h:panelGrid columns="3"> <h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" /> <h:outputText id="nombre" value="#{flash.usuario.nombre}" /> <h:message for="nombre" /> <h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" /> <h:outputText id="apellidos" value="#{flash.usuario.apellidos}" /> <h:message for="apellidos" /> <h:outputLabel for="email" value="#{msg['Usuario.email']}" /> <h:inputText id="email" value="#{flash.usuario.email}" required="true" /> <h:message for="email" /> <h:outputLabel for="password" value="#{msg['Usuario.password']}" /> <h:inputSecret id="password" value="#{flash.usuario.password}" /> <h:message for="password" /> </h:panelGrid> <h:commandButton value="#{msg['action.previous']}" action="#{registrationStep2.previous}" immediate="true" /> <h:commandButton value="#{msg['action.next']}" action="#{registrationStep2.next}" /> </h:form> </ui:define> </ui:composition> </html>
El código de este segundo controlador podría ser el siguiente:
import javax.faces.bean.ManagedBean; import com.autentia.training.core.persistence.entities.Usuario; import com.sun.faces.context.flash.ELFlash; @ManagedBean public class RegistrationStep2 { private static final String BEAN_KEY = "usuario"; public void keepUser(){ ELFlash.getFlash().put(BEAN_KEY, ELFlash.getFlash().get(BEAN_KEY)); } public String previous(){ final Usuario usuario = (Usuario) ELFlash.getFlash().get(BEAN_KEY); System.out.println(usuario); return "registration-step1?faces-redirect=true"; } public String next(){ final Usuario usuario = (Usuario) ELFlash.getFlash().get(BEAN_KEY); System.out.println(usuario); return "registration-confirmation?faces-redirect=true"; } }
En el método keepUser se accede al objeto en el ámbito de flash para volver a asignarlo en el mismo ámbito.
A nivel visual, tendríamos una segunda interfaz como la que sigue:
La página de confirmación (register-confirmation.xhtml), podría tener el siguiente código:
<?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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <ui:composition template="/WEB-INF/templates/defaultLayout.xhtml"> <ui:define name="title">Confirmación del registro</ui:define> <ui:define name="content"> <h:form> <h:panelGrid columns="2"> <h:outputLabel for="nombre" value="#{msg['Usuario.nombre']}" /> <h:outputText id="nombre" value="#{flash.usuario.nombre}" /> <h:outputLabel for="apellidos" value="#{msg['Usuario.apellidos']}" /> <h:outputText id="apellidos" value="#{flash.usuario.apellidos}" /> <h:outputLabel for="email" value="#{msg['Usuario.email']}" /> <h:outputText id="email" value="#{flash.usuario.email}" /> </h:panelGrid> </h:form> </ui:define> </ui:composition> </html>
Y, a nivel visual, tendríamos una última interfaz como la que sigue:
Si en este punto, volvemos hacia atrás en la navegación o cambiamos de página el objeto se destruye.
5. Referencias.
6. Conclusiones.
El ámbito de flash no es más que un saco que se vacía automáticamente o en el que podemos ir manteniendo un objeto entre distintas vistas.
¿Necesitamos algo más elaborado o con mayor granularidad?, pronto veremos alguna otra opción más para manejar el ámbito de los objetos en la vista.
Un saludo.
Jose
Muy buenas Jose Manuel,
Estoy intentando usar el Flash Scope como describes, en un ejemplo en el que tengo un ManagedBean de consultas que da como resultado un listado y al seleccionar un registro redirijo a otra pagina para modificar con otro manageBean (ambos de scope View)… aparentemente funciona bien pero en la consola del Tomcat me aparece esta molesta traza
04-feb-2013 11:44:30 com.sun.faces.context.flash.ELFlash setCookie
ADVERTENCIA: JSF1095: The response was already committed by the time we tried to set the outgoing cookie for the flash. Any values stored to the flash will not be available on the next request.
Sabes a que puede ser debido o como evitarlo ???
Mi version de JSF es 2.0.6 y primefaces 2.2.1
Gracias