Implementación de la capa de presentación con JSF

RECU-0819 (Recurso Referencia)

Descripción

Se detallan distintos conceptos sobre la capa de presentación para la tecnología JSF.

Buenas prácticas y recomendaciones de uso

Al crear una nueva funcionalidad

  • Existirá una única etiqueta <f:view /> en el cuerpo de funcionalidad.
  • En la etiqueta anterior irá incluido un único formulario <h:form />.
  • Todos los controles de la página, tanto de salida como de entrada/salida, irán incluidos en el <h:form />.

Grupos de controles

  • Mensajes de error JSF: <h:messages /> con característica de tabla, no de lista, de tal forma que sea mejor configurable vía estilos CSS.
  • Cuerpo del formulario: <h:panelGrid />.

Detalles al definir estilos cuando se usa JSF

Los controles mostrados en la página JSF tendrán una asignación directa de la clase CSS que se vaya a usar para su formato visual (cadena de texto estática) o, en caso necesario, estarán ligados a una propiedad del bean de respaldo que devuelva la clase a aplicar.

Etiquetas en los campos de entrada/salida

A cada campo de entrada/salida se le añadirá una etiqueta <h:outputLabel/> y una etiqueta <h:message/>, que serán respectivamente, una etiqueta identificativa del campo y un lugar para mostrar los mensajes de error que se puedan producir en relación con el campo al que acompaña (propiedad for de la etiqueta).

La etiqueta <h:message/> deberá tener sus propiedades ShowDetail a false y ShowSummary a true.

Propiedades de elementos de acción en JSF

En la propiedad action del control se tendrá una expresión EL que apunte a una propiedad de tipo String del bean de respaldo. Esta propiedad devolverá el resultado que se pasará al sistema de gestión de la navegación de JSF.

En la propiedad actionListener del control se encontrará la invocación al método del bean de respaldo que ejecute la lógica de negocio y que además, modificará de forma conveniente la propiedad del bean de respaldo que luego será usada para gestionar la navegación.

Templatings en JSF

Facelets es un sistema de templating que nos permite definir los layouts de nuestras páginas y reutilizar componentes (cabeceras, pies, menús) de forma muy sencilla. Además nos permite utilizar código XHTML en vez de JSP. Gracias a su orientación a componentes es más fácil modificar o ampliar las páginas.

Beans de respaldo

Existirá un único bean de respaldo para una determinada página JSF. Cada bean de respaldo constará entre otros, de los siguientes elementos relacionados con la construcción de la página JSF:

  • Propiedades: Accedidas vía get/set, almacenarán los objetos cuyo valor será introducido por el usuario en el formulario o sean enviados por la capa superior de la aplicación a la capa de presentación. Los controles de la página JSF se asociarán a estas propiedades mediante el parámetro value o el parámetro action, en el que se indicará, mediante el uso de expresiones EL, la correspondiente propiedad.
  • Propiedades de rendering: Existen momentos en los que algunos controles pueden ser visibles o no según las necesidades de la funcionalidad subyacente. Para ello se realizará exclusivamente asignando, mediante una expresión EL, la propiedad adecuada (de tipo boolean) en el bean de respaldo al parámetro Rendered del control JSF correspondiente.
  • Propiedades de formato: si es necesario, también se usarán propiedades del bean de respaldo para almacenar el nombre de las clases CSS que se tengan que aplicar a un control determinado.
  • Controles: Propiedades cuya clase se sitúa en la jerarquía JSF como herederas de javax.faces.component.UIComponent, que almacenarán la representación interna de los controles de la página JSF correspondiente (binding), sólo en el caso de que sean necesarios para manipular alguna propiedad del control.

Nomenclatura de componentes

  • Para los botones , delimitados en JSF por la etiqueta, h:commandButton se recomienda seguir la nomenclatura BOTON_(Funcionalidad del boton)
  • Para los formularios , delimitados en JSF por la etiqueta, h:form se recomienda seguir la nomenclatura FORM_(funcionalidad del formulario)
  • Para las imagenes, delimitadas en JSF por la etiqueta, h:graphicImage se recomienda seguir la nomenclatura IMAGEN_(descripción de la imagen)
  • Para las tablas generadas, delimitadas en JSF por la etiqueta, h:dataTable se recomienda seguir la nomenclatura TABLAGEN_(elemento generador)
  • Para los mensajes, delimitados en JSF por la etiqueta, h:message/s se recomienda seguir la nomenclatura MENSAJE_(Funcionalidad del mensaje)
  • Para las tablas, delimitadas en JSF por la etiqueta, h:graphicImage se recomienda seguir la nomenclatura TABLA_(descripción de la tabla)
  • Para las entradas de datos, delimitadas en JSF por la etiqueta, h:input* se recomienda seguir la nomenclatura INPUT_(Campo de datos)
  • Para las salidas de datos, delimitadas en JSF por la etiqueta, h:output* se recomienda seguir la nomenclatura OUTPUT_(Campo de datos)
  • Para los conversores de datos, delimitados en JSF por la etiqueta, f:convert* se recomienda seguir la nomenclatura CONVERSOR_(TIpo de conversor)
  • Para los validadores, delimitados en JSF por la etiqueta, f:validate* se recomienda seguir la nomenclatura VALIDADOR_(Funcionalidad del validador)
  • Para los listener, delimitados en JSF por la etiqueta, f:*Listener se recomienda seguir la nomenclatura LISTENER_(Funcionalidad)

Ciclo de vida del listener

Puede ser útil realizar un debug poniendo un breakpoint en las diferentes etapas del ciclo de vida de una página JSF, así se puede conocer la cadena de acciones que hace tras bambalinas.

Las 6 fases de la vida JSF son:

  • Restaurar vista
  • Asignar valores de petición
  • Realizar validaciones
  • Actualizar los valores del modelo
  • Invocar la aplicación
  • Presentar las respuestas

Internacionalización. Soporte de cambio de idioma

Desde la etapa de diseño de la aplicación debe considerarse que sean páginas que sin necesidad de realizar cambios en el código, puedan adaptarse a mostrar el texto en otro idioma, esta característica es denominada internacionalización, también conocida como i18N.

@PostConstruct

Es muy recomendable el uso de la anotación @PostConstruct. Esta anotación se pone en un método y le indica a JSF que debe llamar a ese método después de crear e inyectar los valores al backbean. Esta anotación es especialmente útil para hacer tareas de inicialización del backbean usando las dependencias que nos inyecta JSF.

@PreDestroy

La anotación @PreDestroy le indica a JSF que debe llamar a ese método justo antes de que la instancia sea destruida. Es útil para la limpieza de recursos.

Separación de capas

Por ejemplo:

  • Las vistas sólo pueden contener lógica de visualización. Al no permitirse scriptlet en las Vistas, la posibilidad de inyectar lógica de negocio o persistencia en estas disminuye.
  • Las clases de acción o controladores (Action en Struts, Controller en JSF) deben contener sólo lógica de presentación. Por ello, no deberían tener una gran carga de desarrollo. Invocarán a clases de la lógica de negocio que encapsulen buena parte del código de la aplicación. En ningún caso debería haber lógica de persistencia en los Action o Controller.

Archivo de configuración

Las reglas de navegación se incluyen en el fichero de configuración faces-config.xml, junto a la declaración de los managed beans. En ocasiones la interpretación es compleja y afecta a la legibilidad.

Ejemplos

Paso de parámetros a los actions

El tag f:attribute se usará para pasar parámetros en las llamadas a métodos del bean de respaldo.

Con los tags h:commandLink y h:commandButton se puede invocar un método del backing bean utilizando el atributo action o actionListener, pero no se le puede pasar un parámetro directamente, por lo que el tag f:attribute puede resultar útil usándolo junto con el actionListener. Un ejemplo:

<h:form>
    <h:commandLink actionListener="#{miBean.action}">
        <f:attribute name="nombreAtributo1" value="valorAtributo1" />
        <f:attribute name="nombreAtributo2" value="valorAtributo2" />
       
        <h:outputText value="De click aquí" />
    </h:commandLink>
    <h:commandButton value="Click" actionListener="#{miBean.action}">
        <f:attribute name="nombreAtributo1" value="valorAtributo1" />
        <f:attribute name="nombreAtributo2" value="valorAtributo2" />
       
    </h:commandButton>
</h:form>

Luego, estos atributos pueden ser recuperados utilizando el método getAttributes() del componente que implementa el ActionEvent que manejó el actionListener.

package ejemplo;
import javax.faces.event.ActionEvent;
import es.juntadeandalucia.cice.util.FacesUtil;
public class MyBean {
     public void action(ActionEvent event) {
        String strAtributo1 = FacesUtil.getActionAttribute(event, "nombreAtributo1");
        String strAtributo2= FacesUtil.getActionAttribute(event, "nombreAtributo2");
        ...
    }
}
package es.juntadeandalucia.cice.util;
import javax.faces.event.ActionEvent;
public class FacesUtil {
    public static String getActionAttribute(ActionEvent event, String name) {
        return (String) event.getComponent().getAttributes().get(name);
    }
}

Por lo que las variables strAtributo1 y strAtributo2 ahora ya tienen los valores asignados de nombreAtributo1 y nombreAtributo2 respectivamente. Se recomienda tener en cuenta que cada nombre de atributo debe ser único y no sobrescribir atributos por defecto del componente como "id", "name", "value", "binding", "rendered", etc.

Paso de objetos de request en request

Si se tiene un bean con scope de request y se quiere reutilizar una propiedad, parámetro u objeto en la siguiente petición, sin tener que reinicializarlo otra vez, se puede utilizar el tag h:inputhidden para guardar el objeto:

<h:form>
    ...
    <h:inputHidden value="#{miBean.value}" />
    ...
</h:form>

Etiquetas de importación para los prefijos "f" y "h"

Prefijo "f" para hacer referencia a etiquetas del núcleo de la implementación y prefijo "h" para hacer referencia a etiquetas de componentes HTML:

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

Mensajes de error

Para personalizar los mensajes de error de la aplicación, en JSF, se pueden configurar paquetes de recursos y personalizar los mensajes de error para convertidores y validadores. El paquete de recursos se configura dentro de faces-config.xml:

<message-bundle>catalog.view.bundle.Messages</message-bundle>

Las parejas clave-valor de los mensajes de error se añaden al fichero Message.properties:

#conversion error messages
javax.faces.component.UIInput.CONVERSION=Input data is not in the correct type.
#validation error messages
javax.faces.component.UIInput.REQUIRED=Required value is missing.

Comunicación entre Managed Beans

Si es posible tener más de un managed bean en un ámbito, cuando es absolutamente necesario por diseño, entonces se puede utilizar el método getSessionMap() de FacesContext para comunicar los beans durante una sesión de navegador. Un ejemplo de dos managed beans declarados en el fichero faces-config.xml:

<managed-bean>
    <managed-bean-name>miBean1</managed-bean-name>
    <managed-bean-class>ejemplo.MiRequestBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
    <managed-bean-name>miBean2</managed-bean-name>
    <managed-bean-class>ejemplo.MiSessionBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

Tanto miBean1 como miBean2 serán accesibles desde cualquier página JSF, no importando que el ámbito de ambos sea distinto. El ámbito de miBean1 es request por lo que en cada petición se creará una nueva instancia mientras que el de miBean2 está puesto en session, en cuyo caso la misma instancia del bean será utilizada durante toda la sesión.

Con la finalidad de obtener y asignar los valores en el SessionMap, podría ser útil crear una superclase con algunos métodos protected que fueran heredados por cada backing bean, o sólo poner el mapa en una clase utility como la que se está viendo en los ejemplos, FacesUtil, de cualquier manera sería:

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(key);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(key, value);

Ciclo de vida del listener

Este sería un ejemplo de un LifeCycleListener:

package ejemplo;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
public class LifeCycleListener implements PhaseListener {
    public void beforePhase(PhaseEvent event) {
        System.out.println("Fase Anterior: " + event.getPhaseId());
    }
    public void afterPhase(PhaseEvent event) {
        System.out.println("Fase Posterior: " + event.getPhaseId());
    }
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }
}

Se agrega al fichero faces-config.xml

<lifecycle>
    <phase-listener>mypackage.LifeCycleListener</phase-listener>
</lifecycle>

Obteniendo por resultado la siguiente salida

Fase Anterior: RESTORE_VIEW 1
Fase Posterior: RESTORE_VIEW 1
Fase Anterior: APPLY_REQUEST_VALUES 2
Fase Posterior: APPLY_REQUEST_VALUES 2
Fase Anterior: PROCESS_VALIDATIONS 3
Fase Posterior: PROCESS_VALIDATIONS 3
Fase Anterior: UPDATE_MODEL_VALUES 4
Fase Posterior: UPDATE_MODEL_VALUES 4
Fase Anterior: INVOKE_APPLICATION 5
Fase Posterior: INVOKE_APPLICATION 5
Fase Anterior: RENDER_RESPONSE 6
Fase Posterior: RENDER_RESPONSE 6

Internacionalización. Soporte de cambio de idioma

En el fichero faces-config.xml indicamos que la aplicación soporta el idioma Español e Inglés

<application>
        <el-resolver>
            org.springframework.web.jsf.el.SpringBeanFacesELResolver
        </el-resolver>
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
        <locale-config>
            <default-locale>es</default-locale>
            <supported-locale>en</supported-locale>
        </locale-config>
        <message-bundle>mensajes</message-bundle>
</application>


Y luego en el mismo fichero faces-config.xml indicamos el bean que se encargará de cambiar el Locale.


<managed-bean>
        <managed-bean-name>i18nBean</managed-bean-name>
        <managed-bean-class>
            com.endesa.sistema.controller.I18nBackingBean
        </managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

El método cambiarIngles() del backing bean sería como este:

public String cambiarIngles() {
            FacesContext context = FacesContext.getCurrentInstance();
            context.getViewRoot().setLocale( Locale.ENGLISH);
            log.debug("Cambiando el locale a Ingles");
            return null;
        }

El código en la página .xhtml con el enlace para cambiar de idioma sería como este:

<h:commandLink action="#{i18nBean.cambiarIngles}" immediate="true">
  <h:graphicImage id="imagenIng" border="0" alt="#{m.ingles}"    url="/imagenes/bandera-uk.gif"/>
</h:commandLink>

Contenidos relacionados

Recursos
Área: Desarrollo » Construcción de Aplicaciones por Capas » Capa de Presentación
Código Título Tipo Carácter
RECU-0131 JSF2 Referencia Recomendado
RECU-0130 Manual de JSF Manual Recomendado