Tomahawk

RECU-0137 (Recurso Referencia)

Descripción

Apache Tomahawk es un conjunto de componentes JSF que van más allá de la especificación JSF. Son totalmente compatibles con la Implementación de Referencia de SUN (SUN IR) versión 1.1 así como con cualquier otra implementación compatible con la versión 1.1 de la especificación JSF. Por supuesto, es totalmente compatible con la implementación de Apache MyFaces ya que es un subproyecto de MyFaces.

Características

Todos los componentes estándar tienen una versión equivalente en Tomahawk. Estas versiones extendidas se caracterizan por mejorar el rendimiento de los componentes estándar y por tener nuevos atributos que le proporcionan características adicionales entre las que se pueden destacar:

  • Conciencia de Rol de Usuario: El renderizador permite ver al usuario ciertos componentes en función de su rol en la aplicación.
  • Mostrar Sólo el Valor: Capacidad de intercambiar el estado de los componentes entre modo de entrada y de salida.
  • Forzar Id: No permite a JSF generar un identificador para el atributo id del componente y de sus padres sino que en su lugar utiliza uno proporcionado por el desarrollador.

Ejemplos

Para la aplicación de ejemplo veremos el uso de las etiquetas t:inputDate y t:inputCalendar. Veamos en primer lugar que hacen cada una de ellas:

  • <t:inputDate> Control de entrada personalizado para fechas y horas. Todos los atributos aceptan valores estáticos o expresiones EL.
  • <t:inputCalendar> Proporciona un control de calendario. Al igual que la etiqueta anterior, todos sus atributos aceptan valores estáticos o expresiones EL.

La aplicación será muy sencilla, tendremos una página JSP donde se muestran estas dos etiquetas en un formulario y al enviar el formulario el usuario será llevado a otra página JSP donde se muestran las fechas introducidas. Para mostrar el uso de validadores, se insertarán dos campos inputDate (fecha1 y fecha2) obligando al usuario a que la fecha del primer campo sea anterior a la del segundo campo.

Implementando el Bean

Lo primero es implementar el bean que dará soporte al formulario. Este bean será muy simple, contendrá tres campos de tipo java.util.Date que almacenarán tres fechas. Veamos el código:

package org.javacenter.jsf;

import java.util.Date;

public class Bean {   
    private Date fecha1;
    private Date fecha2;
    private Date fecha3;
   
    public Date getFecha1() { return fecha1; }   
    public void setFecha1(Date fecha1) { this.fecha1 = fecha1; }
   
    public Date getFecha2() { return fecha2; }   
    public void setFecha2(Date fecha2) { this.fecha2 = fecha2; }
   
    public Date getFecha3() { return fecha3; }   
    public void setFecha3(Date fecha3) { this.fecha3 = fecha3;}   
}

Ahora tenemos que añadir el bean al contexto de la aplicación. Para ello insertamos lo siguiente en el fichero faces-config.xml:

<managed-bean>
    <managed-bean-name>bean</managed-bean-name>
    <managed-bean-class>org.javacenter.jsf.Bean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

Implementando las Vistas

Ahora vamos a crear las dos páginas JSP. Una para el formulario de entrada (fechas.jsp) y otra para representar los datos (resultado.jsp). Lo primero es crear la regla de navegación, para ello añadimos lo siguiente al fichero faces-config.xml:

<navigation-rule>
    <from-view-id>/fechas.jsp</from-view-id>
    <navigation-case>
        <from-outcome>enviar</from-outcome>
        <to-view-id>/resultado.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Si desde la página fechas.jsp realizamos la acción "enviar" y no hay ningún error seremos enviados a la página resultado.jsp. Ambas páginas comparten el siguiente código:

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<f:loadBundle basename="org.javacenter.jsf.Mensajes" var="msg"/>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Ejemplos de Fechas</title>
        <link rel="stylesheet" type="text/css" href="/servicios/madeja/css/estilos.css" />
    </head>
    <body>      
        <f:view> <%-- Código de la Página --%> </f:view>
    </body>
</html>

Aquí hay que destacar que hemos cargado un fichero de mensajes (Mensajes.properties) y hemos añadido una hoja de estilos. Luego se verá cómo crear el fichero de propiedades y el código

<h1><h:outputText value="#{msg.titulo}"/></h1>
<h:form id="miForm">
    <fieldset>
        <legend><h:outputText value="#{msg.subtitulo}"/></legend>
        <p>
  <label for="fecha1"><h:outputText value="#{msg.fecha1}"/></label>
  <t:inputDate id="fecha1" styleClass="campoColor" popupCalendar="true"
               required="true" value="#{bean.fecha1}"/>
  <t:message styleClass="error" for="fecha1"/>
        </p> <br/> <p>
  <label for="fecha2"><h:outputText value="#{msg.fecha2}"/></label>
  <t:inputDate id="fecha2" styleClass="campoColor" popupCalendar="true"
               required="true" value="#{bean.fecha2}">
      <f:validator validatorId="validaFecha"/>
      <f:attribute name="fecha1" value="miForm:fecha1" />
  </t:inputDate>
  <t:message styleClass="error" for="fecha2"/>
        </p> <br/> <p>
  <label for="fecha3"><h:outputText value="#{msg.fecha3}"/></label>
  <t:inputCalendar id="fecha3" styleClass="campoColor" value="#{bean.fecha3}"
                   renderAsPopup="true"/>
  <t:message styleClass="error" for="fecha3"/>
        </p>
  <p class="submit"> <h:commandButton action="enviar" value="Enviar"/> </p>
    </fieldset>
</h:form>

En negrita aparecen resaltadas las tres etiquetas principales. Las dos primeras se han marcado obligatorias con el atributo "required". Esto significa que si el usuario no proporciona un valor para estos campos el formulario no se enviará y se mostrará el correspondiente error. También se han incluido las etiquetas para mostrar los mensajes de error para cada campo, determinado por el atributo "for" que debe ser igual al atributo "id" del componente.

Otro detalle importante es la inclusión de un validador. Primero veamos el código fuente de resultado.jsp y luego explicaremos el uso de validadores.

<h1><t:outputText value="#{msg.tituloPresenta}" /></h1>
<h:form id="miForm">
    <fieldset>
        <legend><h:outputText value="#{msg.subtituloPresenta}"/></legend>
        <p><label for="fecha1"><h:outputText value="#{msg.fecha1}"/></label>
           <t:inputDate id="fecha1" styleClass="campoColor"
                        disabled="true" value="#{bean.fecha1}"/> </p> <br/>
        <p><label for="fecha2"><h:outputText value="#{msg.fecha2}"/></label>
           <t:inputDate id="fecha2" styleClass="campoColor"
                        disabled="true" value="#{bean.fecha2}"/> </p> <br/>
        <p><label for="fecha3"><h:outputText value="#{msg.fecha3}"/></label>
         <t:inputCalendar id="fecha3" styleClass="campoColor" disabled="true"
                          renderAsPopup="true" value="#{bean.fecha3}" /> </p>
    </fieldset>
</h:form>

Para representar los valores se han usado los mismos componentes que en el formulario de entrada con la excepción de tener el atributo disabled="true". Esto hace que el usuario no pueda cambiar el valor del campo. Veamos ahora el código de la hoja de estilos "css/estilos.css":

label { width: 15em; float: left; text-align: right; margin-right: 0.5em;
        display: block }

.submit input { margin-left: 4.5em; }

input { color: #781351; background: #fee3ad; border: 1px solid #781351; }

.submit input { color: #000; background: #ffa20f; border: 2px outset #d7b9c9;
}

fieldset { border: 1px solid #781351; width: 50em; }

legend { color: #fff; background: #ffa20c; border: 1px solid #781351; 
         padding: 2px 6px; }

.campoColor { background: #FEE3AD none repeat scroll 0%;
              border: 1px solid #781351; color: #781351; }

.error { font-weight: bold; color: red; }

Fichero de Mensajes

En las páginas JSP hemos incluido una sentencia que importaba un fichero de mensajes:

<f:loadBundle basename="org.javacenter.jsf.Mensajes" var="msg"/>

Esto indica que se debe cargar el fichero "Mensajes.properties" que se encuentra en el paquete "org.javacemter.jsf". A continuación, en el resto de la página web podemos acceder a los mensajes mediante una etiqueta . Por ejemplo, de la siguiente forma:

<h:outputText value="#{msg.titulo}"/>

El código completo del fichero de propiedades es el siguiente:

#Mensajes de la Aplicación
titulo=Ejemplo de Etiquetas inputDate e inputCalendar
subtitulo=Formulario de Entrada
fecha1=Fecha 1
fecha2=Fecha 2 (Mayor que Fecha1)
fecha3=Fecha 3
tituloPresenta=Presentación de los Datos Insertados
subtituloPresenta=Formulario de Salida

Validaciones

Como sabemos, en JSF existen varias formas de validar los datos de entrada de un formulario. En esta aplicación se han utilizado tres formas que explicaremos a continuación.

Validación Automática

Cada campo tiene un convertidor que transforma la cadena introducida por el usuario al tipo de dato dato por el bean de soporte. En esta aplicación, los campos inputDate producen un error de fecha inválida cuando la fecha introducida no es correcta. Por otro lado, el campo inputCalendar producirá un error de conversión cuando se introduzca una cadena que no corresponda con ninguna fecha. La diferencia es la siguiente:

  • inputDate: El campo tiene día, mes y año. Si introducimos una fecha incorrecta (por ejemplo día 30 para febrero) el formato de la fecha es correcto (día/mes/año) pero la fecha no lo es. Por lo tanto el error es de "Fecha no Válida".
  • inputCalendar: El valor del campo viene dado por una cadena. Cuando se intenta transformar la cadena a un objeto Date saltará un error de conversión, tanto si se trata de una cadena que no se parece a una fecha (por ejemplo "aaa") como si se trata de una fecha incorrecta (día 30 para febrero).

Estas validaciones son automáticas y cuando se produce un error generan un mensaje por defecto. Este mensaje suele ser poco descriptivo por lo que se recomienda proporcionar un mensaje personalizado. Para ello simplemente sobrescribimos el par clave/valor correspondiente en un mensaje de propiedades. Para este ejemplo usaremos el mismo fichero de propiedades comentado anteriormente (Mensajes.properties). Los pasos son:

  1. Añadimos los mensajes al fichero:

    #Mensajes de Error
    org.apache.myfaces.calendar.CONVERSION = Error de Conversión
    org.apache.myfaces.calendar.CONVERSION_detail = El Valor "{1}" no puede convertirse a un objeto Date
    org.apache.myfaces.Date.INVALID = Fecha no Válida
    org.apache.myfaces.Date.INVALID_detail = El Valor introducido no es una fecha válida

    Los mensajes tienen la clave org.apache.myfaces.calendar.CONVERSION para los errores de conversión del componente inputCalendar y org.apache.myfaces.Date.INVALID cuando se inserta una fecha incorrecta en un campo inputDate.

  2. Indicar la carga del fichero de propiedades en el fichero de contexto de JSF. Para ello añadimos la siguiente entrada a faces-config.xml:

    <application>
        <message-bundle>org.javacenter.jsf.Mensajes</message-bundle>
    </application>

Atributo required

En JSF, todos los componentes de entrada tienen un atributo "required" que le indica al motor de JSF la necesidad de rellenar dicho campo. Si el usuario deja el campo vacío se producirá un error y el formulario no se enviará, mostrándose el mensaje de error correspondiente.

En nuestra aplicación hemos marcado como required los dos primeros campos inputDate. Si el usuario no rellena estos campos no podrá enviar el formulario, mostrándose un mensaje de error por defecto en donde está situado el elemento <t:message/>.

Lo único que debemos hacer es crear un mensaje de error personalizado. Para ello, puesto que ya hemos añadido el fichero de mensajes al contexto de la aplicación, simplemente tenemos que crear las entradas para el mensaje. Por ejemplo:

javax.faces.component.UIInput.REQUIRED=Inserta un Valor
javax.faces.component.UIInput.REQUIRED_detail=El Campo es obligatorio. Inserta un Valor

Validación Personalizada

Otra forma de validar un formulario consiste en crear una clase personalizada que se encargue de validar los datos. En este ejemplo vamos a implementar un validador que compruebe que la fecha introducida en el primer campo inputDate sea menor que la introducida en el segundo campo inputDate.

El validador lo asociaremos al campo donde se inserta la segunda fecha por lo que necesitamos alguna forma de pasar el valor de la primera fecha al validador. Si nos fijamos en el código fuente de fechas.jsp, el validador está registrado de la siguiente forma:

<t:inputDate id="fecha2" styleClass="campoColor" popupCalendar="true" 
             required="true" value="#{bean.fecha2}">
    <f:validator validatorId="validaFecha"/>
    <f:attribute name="fecha1" value="miForm:fecha1" />
</t:inputDate>

Para crear un validador lo primero que hay que hacer es crear una clase que implemente la interfaz javax.faces.validator.Validator. Esta interfaz define un único método validate que recibe el contexto, el componente asociado y el valor del campo y lanza una javax.faces.validator.ValidatorException si la validación no es correcta. como hemos visto en la definición de la etiqueta del validador, recibimos un parámetro (fecha1) cuyo valor es el identificador del campo "fecha1". Esto se indica mediante la siguiente signatura:

  • <id del Formulario>:
package org.javacenter.jsf;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class ValidaFecha implements Validator {   
    public void validate(FacesContext context, UIComponent component,
                         Object value) throws ValidatorException {
        // Obtener el ID del campo con la primera Fecha
        String fecha1Id = (String)
                          component.getAttributes().get("fecha1");
        // Encontrar el componente actual para dicho ID
        UIInput dateInput = (UIInput)
                          context.getViewRoot().findComponent(fecha1Id);
        // Obtener su valor
        Date fecha1 = (Date) dateInput.getValue();
        if(fecha1==null) {
            FacesMessage message = new FacesMessage();
            message.setDetail("El Valor introducido no es una " +
                              "fecha válida");
            message.setSummary("Error en Fecha1");
            message.setSeverity(FacesMessage.SEVERITY_ERROR);
            // Añadimos el error al campo de la fecha 1
            context.addMessage(fecha1Id, message);
            throw new ValidatorException(new FacesMessage("Error en la primera fecha"));
        } else {
            Date fecha2 = (Date) value;
            if (fecha2.compareTo(fecha1)<0) {
                FacesMessage message = new FacesMessage();
                message.setDetail("La segunda Fecha debe ser " +
                                  "posterior a la primera");
                message.setSummary("Error en Fecha2");
                message.setSeverity(FacesMessage.SEVERITY_ERROR);
                throw new ValidatorException(message);
            }
        }
    }
}

Una vez creado el validador sólo tenemos que añadir una entrada al fichero de configuración de JSF para poder invocarlo desde la etiqueta JSF. A continuación se muestra la parte del código que debemos añadir a faces-config.xml:

<validator>        
    <validator-id>validaFecha</validator-id>       
    <validator-class>org.javacenter.jsf.ValidaFecha</validator-class>       
</validator>

Si nos fijamos bien, el valor del elemento <validator-id> coincide con el valor del atributo validatorId dentro del elemento <f:validator/> en el JSP.