29 de noviembre de 2013

Struts 2 - Parte 7: Tags

Cuando desarrollamos aplicaciones es importante tener un buen back-end que soporte la lógica de la aplicación, y del mismo modo es importante tener un buen front-end, ya que será esta con la que nuestros usuarios interactuarán introduciendo y viendo información. Es por esto que para crear la vista (las páginas web) debemos usar un conjunto de elementos que nos permitan mostrar la información correcta, darle el formato adecuado, y realizar pequeños bloques de lógica en caso de ser necesario (ciclos, condiciones, constantes, etc.).

En el mundo web de Java estos componentes generalmente son implementados en forma de etiquetas (tags). Prácticamente todos los frameworks web de Java incluyen su propio conjunto de etiquetas que se integran perfectamente con los motores de negocio, para hacer uso de las ventajas particulares del framework. Struts 2 no es la excepción, y presenta un conjunto amplio de etiquetas para manejar la información que será presentada al cliente, y además ayudar a dar formato a los componentes de la interfaz de usuario, dándonos la libertad de elegir entre varios "modos".

En este tutorial aprenderemos a usar todas las etiquetas que Struts 2 nos proporciona actualmente, junto con algunos detalles de cada uno de los temas o layouts que nos proporciona.


Struts 2 divide sus etiquetas más importantes en 4 categorías:

Lo primero que haremos es crear un nuevo proyecto en NetBeans. Vamos al menú "File -> New Project...". En la ventana que aparece seleccionamos la categoría "Java Web" y en el tipo de proyecto "Web Application". Presionamos el botón "Next >" y le damos un nombre y una ubicación a nuestro proyecto; presionamos nuevamente el botón "Next >" y en este punto se nos preguntará el servidor que queremos usar. En nuestro caso usaremos el servidor "Tomcat 7.0", con la versión 5 de JEE. También podemos cambiar el context path de la aplicación web, yo la cambiaré a "/tags" para que sea más fácil escribir la dirección en el navegador. Presionamos el botón "Finish".

Una vez que tengamos nuestro proyecto debemos recordar agregar la biblioteca "Struts2" (o "Struts2Anotaciones" si van a hacer uso de anotaciones, como es mi caso ^_^), que creamos en el primer tutorial de la serie de Struts 2. Hacemos clic derecho sobre el nodo "Libraries" del proyecto. En el menú que aparece seleccionamos la opción "Add Library...". En la ventana que aparece seleccionamos la biblioteca "Struts2" o "Struts2Anotaciones" y presionamos "Add Library". Con esto ya tendremos los jars de Struts 2 en nuestro proyecto.

Ahora configuramos el filtro "struts2" en el deployment descriptor. Abrimos el archivo "web.xml" y colocamos el siguiente contenido, como se explicó en el primer tutorial de la serie:

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Creamos también un modelo sencillo de datos para tener la información que se mostrará. Haremos uso de dos clases muy sencillas, "Usuario" y "Direccion". Primero creamos dos nuevos paquetes, el primero llamado "com.javatutoriales.struts2.tags.actions", que de momento quedará vacío, y uno segundo llamado "com.javatutoriales.struts2.tags.modelo". Dentro de este paquete creamos primero la clase "Direccion" la cual tendrá sólo 2 campos:
  • "calle" de tipo String
  • "codigoPostal" de tipo String
Junto con un par de constructores y los getters y setters de las propiedades anteriores.

El código completo de la clase "Direccion" queda de la siguiente forma:

public class Direccion implements Serializable {

    private String calle;
    private String codigoPostal;

    public Direccion() {
    }

    public Direccion(String calle, String codigoPostal) {
        this.calle = calle;
        this.codigoPostal = codigoPostal;
    }

    public String getCalle() {
        return calle;
    }

    public void setCalle(String calle) {
        this.calle = calle;
    }

    public String getCodigoPostal() {
        return codigoPostal;
    }

    public void setCodigoPostal(String codigoPostal) {
        this.codigoPostal = codigoPostal;
    }
}

La clase "Usuario" también será muy sencilla, tendrá las siguientes propiedades:
  • "nombre" de tipo String
  • "edad" de tipo int
  • "direccion" de tipo Direccion

Al igual que en el caso anterior, agregamos un par de constructores, getters y setters.

El código completo de la clase "Usuario" queda de la siguiente forma:

public class Usuario implements Serializable {

    private String nombre;
    private int edad;
    private Direccion direccion;

    public Usuario() {
    }

    public Usuario(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }

    public Direccion getDireccion() {
        return direccion;
    }

    public void setDireccion(Direccion direccion) {
        this.direccion = direccion;
    }
}

Ya con nuestro modelo de datos, podemos comenzar a hablar de lleno sobre las etiquetas.

Como dijimos antes: Struts 2 proporciona etiquetas para varios propósitos:
  • De control: Permiten indicar qué y cómo deben dibujarse los componentes de la interfaz de usuario. Básicamente control de flujo y un poco de manejo de datos.
  • De datos: Manejar algunos valores de información, así como creación y manipulación de datos.
  • De formulario: Los componentes que pueden formar parte de un formulario.
  • UI de no formulario: Información extra que se muestra pero que no pertenece a un formulario (mensajes de error o de resultados de las acciones).

Existen un par de tipos más (Ajax y de temas y plantillas) pero no son importantes para el desarrollo del día a día. Para los que pregunten por las etiquetas de Ajax, estas son un legado que permanece de las primeras versiones de Struts 2, donde se incluía como parte del core la biblioteca dojo. Sin embargo al momento de tratar de combinarlas con una versión más nueva de dojo o con otro framework javascript esta entraba en conflicto llegando a resultados inesperados, por lo que fue eliminada; aunque se puede descargar como un plugin adicional.

*Nota: Todas estas etiquetas se encuentran dentro de una misma biblioteca, llamada convenientemente "struts-tags". Si recuerdan de los tutoriales anteriores, hemos estado importando esta biblioteca en cada una de las páginas que hemos creado.


Cuando trabajamos en un proyecto web, y queremos usar una biblioteca de etiquetas en una página, debemos importarla usando la directiva "taglib", indicando un prefijo que no es otra cosa que un namespace para diferenciar las etiquetas de una biblioteca de la de otra (en caso de que haya una etiqueta con el mismo nombre en más de una biblioteca), y la uri de la biblioteca de etiquetas. En el caso de la etiquetas de Struts 2, se colocan en la uri "/struts-tags", y por convención usamos el prefijo "s" para hacer referencia a ellas. Por lo que la directiva taglib que usamos queda de la siguiente forma:

<%@taglib uri="/struts-tags" prefix="s" %>

Ahora que sabemos cómo llamar a las etiquetas de Struts 2, hablemos un poco de los temas antes de entrar de lleno en cada uno de los tipos de etiquetas.

Struts 2 proporciona un par de temas integrados para el acomodo o layout de los componentes. Cada uno de los temas podemos indicarlo en dos niveles: aplicación y componente. Si colocamos un tema a nivel de componente, este se aplicará a ese componente y todos sus componentes hijos. Si lo aplicamos a nivel de aplicación todos los componentes de nuestro sistema tendrán dicho tema.

Los tres temas que ofrece Struts 2 son los siguientes:
  • css_xhtml: Coloca los componentes agregando algunos divs.
  • xhtml: Este es el tema por default. Coloca los componentes en dos columnas (etiqueta y componente) usando tablas para acomodarlos.
  • simple: el tema más sencillo, sin nada extra más que el componente que se quiere mostrar.

Veamos la diferencia del HTML generado con cada uno de los temas.

Comencemos con el tema por default (xhtml). Si colocamos el siguiente formulario en una página (por favor noten el valor del atributo "theme" en la etiqueta "s:form"):

<s:form theme="xhtml">
    <s:textfield label="Nombre" name="nombre" />
    <s:textfield label="Edad" name="edad" />
    <s:submit value="Enviar" />
</s:form>

Obtendremos la siguiente salida en HTML:

<form id="" action="/tags/" method="post">
    <table class="wwFormTable">
        <tr>
            <td class="tdLabel">
                <label for="_nombre" class="label">Nombre:</label>
            </td>
            <td>
                <input type="text" name="nombre" value="" id="_nombre"/>
            </td>
        </tr>
        <tr>
            <td class="tdLabel">
                <label for="_edad" class="label">Edad:</label>
            </td>
            <td>
                <input type="text" name="edad" value="" id="_edad"/>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <div align="right">
                    <input type="submit" id="_0" value="Enviar"/>
                </div>
            </td>
        </tr>
    </table>
</form>

Como podemos ver, hace uso de tablas (una técnica nada recomendada) para el acomodo de componentes. También agrega algunas clases de CSS.

Si ahora cambiamos el valor del atributo "theme" por "css_xhtml" la salida será:

<form id="" action="/tags/" method="post">
    <div id="wwgrp__nombre" class="wwgrp">
        <div id="wwlbl__nombre" class="wwlbl">
            <label for="_nombre" class="label">Nombre:</label>
        </div>
        <br />
        <div id="wwctrl__nombre" class="wwctrl">
            <input type="text" name="nombre" value="" id="_nombre"/>
        </div> 
    </div>
    <div id="wwgrp__edad" class="wwgrp">
        <div id="wwlbl__edad" class="wwlbl">
            <label for="_edad" class="label">Edad:</label>
        </div> 
        <br />
        <div id="wwctrl__edad" class="wwctrl">
            <input type="text" name="edad" value="" id="_edad"/>
        </div> 
    </div>
    <div align="right" id="wwctrl__0">
        <input type="submit" id="_0" value="Enviar"/>
    </div> 
</form>

Ahora se han eliminado las tablas y en su lugar se colocan varios divs.

Finalmente, si ponemos el "theme" con el valor "simple" obtenemos el siguiente HTML:

<form id="" action="/tags/" method="post">
    <input type="text" name="nombre" value="" id="_nombre"/>
    <input type="text" name="edad" value="" id="_edad"/>
    <input type="submit" id="_0" value="Enviar"/>
</form>

Como vemos, lo de simple no es broma, en realidad es prácticamente lo mismo que colocamos en las etiquetas de Struts 2.

En mi caso me gusta agregar de forma manual los divs o elementos para el acomodo y las clases CSS, así que el tema que usaré para los tutoriales será el tema "simple". Hay que escribir un poco más, pero se tiene más control sobre el resultado final.

Ahora, en el ejemplo anterior indicamos el tema a nivel de componente. Si creamos un segundo formulario o agregamos más componentes fuera de este formulario, estos tendrán el tema por default (xhtml). ¿Cómo hacer entonces que todos los componentes del sitio tengan el mismo tema sin tener que agregarlo en cada elemento? Struts 2 permite hacer esto agregando la propiedad "struts.ui.theme" con el nombre del tema que queramos usar (en este caso "simple") en el archivo "struts.properties".

Agregamos un nuevo archivo de propiedades en la raíz de los paquetes de la aplicación. Para esto hacemos clic derecho sobre el nodo "Source Packages" y en el menú que se abre seleccionamos la opción "New" -> "Properties File" (si no encuentran esta opción tendrán que ir a "Other…" y en la ventana que se abre seleccionar la categoría "Other" y como tipo de archivo "Properties File".



Le damos como nombre a este archivo "struts" (NetBeans se encargará de colocar la extensión) y hacemos clic en el botón "Finish". Borramos el contenido del archivo que aparece y colocamos la siguiente línea:

struts.ui.theme = simple

Con esto indicamos a Struts 2 que el tema por default para nuestra aplicación debe ser el tema "simple".

Ahora comencemos a ver cómo funcionan las etiquetas, para esto tenemos que generar un conjunto de datos de prueba, haciendo uso de las dos clases del modelo de datos que creamos anteriormente.

La primera cosa importante que debemos saber es que los objetos que pueden mostrarse en las tags de Struts 2 deben estar en el "ActionContext" (pueden revisar el segundo tutorial de la serie donde se explica qué es), hay varias formas de colocar objetos en este contexto. La más sencilla es con los objetos que Struts 2 regresa después de la ejecución de un Action (los atributos de los cuales hemos colocado getters).

Además hacemos uso de OGNL para pasar valores a las etiquetas (también pueden revisar el segundo tutorial de la serie como recordatorio de OGNL).

Primero veremos cómo funcionan las etiquetas de control.

Etiquetas de Control

Nos sirven para realizar un control básico de flujo (condiciones y ciclos) y para el trabajo con colecciones.

Las etiquetas de esta categoría son:
  • if: La etiqueta básica de control
  • elseif: Creo que tampoco necesita explicación
  • else: Mismo caso que la etiqueta anterior
  • iterator: La etiqueta de ciclos (similar a un for) que nos permite recorrer los valores de una colección.
  • generator: Se encarga de crear un iterador en base a un conjunto de valores, usando el separador indicado.
  • append: Se encarga de juntar las salidas de varios iteradores, de tal forma que al usar todos los elementos de uno comienza a usar los elementos del siguiente.
  • merge: Trabaja de forma similar a "append" sólo que en vez de usar primero todos los elementos de un iterador y continuar con el siguiente iterador, este expone el primer elemento de todos los iteradores, después el segundo elemento de todos los iteradores, y así sucesivamente.
  • sort: Se encarga de ordenar los elementos de una colección, usando el "Comparator" indicado.
  • subset: Recibe un iterador y expone un subconjunto de sus elementos.

Comencemos entonces creando un Action que llenará una lista con objetos de tipo "Usuario" y "Direccion". En el paquete "com.javatutoriales.struts2.tags.actions" creamos una clase llamada "ControlAction" que extenderá de la clase "ActionSupport":

public class ControlAction extends ActionSupport{

}

Como estamos usando anotaciones, indicaremos que este Action se encontrará en el namespace "/", tendrá el nombre "control", y en el caso de una ejecución exitosa, nos regresará a la página "/control.jsp":

@Namespace(value = "/")
@Action(value = "control", results = {
@Result(name = "success", location = "/control.jsp")})
public class ControlAction extends ActionSupport {
}

Colocaremos como atributo, junto con su setter, una lista de objetos "Usuario" que será con la que trabajaremos usando las etiquetas de control:

private List<Usuario> usuarios = new ArrayList<Usuario>();

public List<Usuario> getUsuarios() {
    return usuarios;
}

Ahora sobre-escribimos el método "execute" para poblar esta lista de la siguiente manera:

@Override
public String execute() throws Exception {

    for (int i = 0; i < 10; i++) {
        Usuario usuario = new Usuario("Usuario " + i, i + 10);

        if (i % 2 == 0) {
            usuario.setDireccion(new Direccion("calle " + i, "ABC" + i));
        }

        usuarios.add(usuario);
    }

    return SUCCESS;
}

El método es muy sencillo, lo único que hace es crear 10 objetos "Usuario", dándoles un nombre y una edad, y a los objetos pares les establece una dirección. Esto quiere decir que tendremos 6 usuarios con una dirección y 4 sin una.

Ahora crearemos un "Comparator" para ordenar los objetos "Usuario". Haremos uso de una clase interna y regresaremos una instancia de esta clase usando un getter. La implementación de la interfaz "Comparator" en este caso no es muy importante, podríamos usar el nombre o la edad, en este caso elegiré el nombre pero haré la comparación de forma inversa, es decir, haremos un ordenamiento de manera ascendente (de mayor a menor). Para esto simplemente debemos comparar el nombre del segundo usuario con el del primero (en vez de hacer la comparación del primero con el segundo):

private Comparator<Usuario> comparadorUsuarios = new ComparadorUsuarios();

public Comparator<Usuario> getComparadorUsuarios() {
        return comparadorUsuarios;
}

class ComparadorUsuarios implements Comparator<Usuario> {

    @Override
    public int compare(Usuario t, Usuario t1) {
        return t1.getNombre().compareTo(t.getNombre());
    }
}

Con esto ya tenemos terminado nuestro Action. El código completo de la clase "ControlAction" queda de la siguiente forma:

@Namespace(value = "/")
@Action(value = "control", results = {
@Result(name = "success", location = "/control.jsp")})
public class ControlAction extends ActionSupport {

    private List<Usuario> usuarios = new ArrayList<Usuario>();
    private Comparator<Usuario> comparadorUsuarios = new ComparadorUsuarios();

    @Override
    public String execute() throws Exception {

        for (int i = 0; i < 10; i++) {
            Usuario usuario = new Usuario("Usuario " + i, i + 10);

            if (i % 2 == 0) {
                usuario.setDireccion(new Direccion("calle " + i, "ABC" + i));
            }

            usuarios.add(usuario);
        }

        return SUCCESS;
    }

    public List<Usuario> getUsuarios() {
        return usuarios;
    }

    public Comparator<Usuario> getComparadorUsuarios() {
        return comparadorUsuarios;
    }

    class ComparadorUsuarios implements Comparator<Usuario> {

        @Override
        public int compare(Usuario t, Usuario t1) {
            return t1.getNombre().compareTo(t.getNombre());
        }
    }
}

Ahora crearemos una nueva JSP llamada "control.jsp". Hacemos clic derecho en el nodo "Web Pages" y seleccionamos la opción "New -> JSP...". En la ventana que se abre le damos como nombre a nuestra página "control" y hacemos clic en el botón "Finish".



Colocaremos todas las etiquetas de esta categoría en esta página. Aunque al final quedará un poco enredado podremos usarla como referencia para cuando tengamos dudas más adelante ^_^.

Primero indicaremos que haremos uso de las etiquetas de Struts 2 colocando la directiva taglib que vimos anteriormente:

<%@taglib uri="/struts-tags" prefix="s" %>


if, elseif y else

Serán las primeras etiquetas que veremos, y en general unas de las que más usaremos durante nuestros desarrollos. No necesitan mucha explicación, ya que son los equivalentes a las sentencias de control de flujo básicas de Java.

if y elseif comparten un atributo, que se muestra en la siguiente tabla:





NombreObligatorioValor DefaultTipoDescripción
testSi Boolean


Expresión que determina si el contenido del cuerpo de la etiqueta será mostrado.


La etiqueta else no tiene atributos.

Veremos un ejemplo muy simple en el que usaremos las tres etiquetas al mismo tiempo.

Si colocamos las etiquetas de la siguiente forma (recuerden que test recibe cualquier expresion que regrese un valor booleano):

<s:if test="%{false}">
    Este mensaje no se vera
</s:if>
<s:elseif test="%{true}">
    Este mensaje si se vera
</s:elseif>
<s:else>
    Este mensaje tampoco se vera
</s:else>


Obtendremos el siguiente código:

 Este mensaje si se vera

Que se ve así en el navegador



Así de simple funciona esta etiqueta.

iterator

Esta es otra etiqueta usaremos mucho durante nuestros desarrollos. Nos permite ciclar a través de una lista de valores, al igual que un ciclo for. Los valores pueden ser indicados a través de números (valor de inicio, valor de fin, incremento) o indicando una colección (la cual será recorrida de inicio a fin).

Los atributos de esta etiqueta se muestran en la siguiente tabla:




NombreObligatorioValor DefaultTipoDescripción
varNo String


El nombre de la variable que será usada para referir al valor actual del ciclo. Esta variable es puesta en la cima del ValueStack.
valueNo StringLa colección sobre la cual se realizará la iteración.
beginNo0IntegerEl índice en el cual empezará la iteración (en caso de indicarse).
endNo IntegerEl índice en el cual terminará la iteración (en caso de indicarse).
stepNo1IntegerEl valor por el cual será incrementado el índice en cada iteración (en caso de indicarse).
statusNo StringEl nombre de la variable que se usará para referir una instancia de IteratorStatus, la cual nos indica el estatus de cada iteración.

Ahora el ejemplo, comenzaremos ciclando a través de una serie de valores estáticos. Vamos a nuestra página "control.jsp" y agregamos la siguiente etiqueta:

<s:iterator begin="1" end="10" step="2" var="valorActual">
</s:iterator>

Con esto indicamos que iteraremos a través de los valores del 1 al 10 haciendo incrementos de 2 en cada iteración. También indicamos que el valor actual se colocará en una variable llamada "valorActual". Hay que recordar que este valor se colocará en la cima del ValueStack... ¿y esto qué quiere decir? Básicamente que podemos obtener el valor sin siquiera indicar su nombre. O sea que podemos hacer esto (recordando que usamos la etiqueta "property" nos sirve para mostrar valores en el HTML de salida):

<s:property />

Si este valor no estuviera en la cima del ValueStack tendríamos que hacer esto:

<s:property value="#valorActual" />

Con esto, la etiqueta queda de esta forma:

<s:iterator begin="1" end="10" step="2" var="valorActual">
    <s:property />
</s:iterator>

Si ahora ejecutamos nuestra aplicación, debemos ver la siguiente salida:


Podemos ver en la salida del HTML los valores esperados.

Ahora juguemos un poco con el valor del estatus. ¿Qué podemos hacer con esta variable? Según la documentación de la clase IteratorStatus, esta clase expone 6 variables:
  • index: el índice actual del iterador, el cual comienza en 0.
  • count: el número de iteraciones que se han dado.
  • first: verdadero en caso de que le índice actual sea 0.
  • even (par): verdadero si el índice actual es par.
  • odd (impar): verdadero si el índice actual es impar.
  • last: verdadero si la iteración actual es la última.

Para el ejemplo, crearemos una tabla que muestre el valor actual de todas las variables anteriores. Lo primero es modificar la etiqueta "iterator" que tenemos para agregar el atributo "status":

<s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
</s:iterator>


Como en la cima del ValueStack se encuentra la variable "valorActual", para leer los valores de "esatus" debemos indicar usando el signo de número (#) el nombre de la variable utilizada (en este caso "estatus"). Por ejemplo, para mostrar el valor del índice, lo mostramos de la siguiente manera:

<s:property value="#estatus.index" />

Hacemos lo mismo para el resto de los valores, por lo que nuestro iterador queda de la siguiente forma:

<s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
    <s:property />
    <s:property value="#estatus.index" />
    <s:property value="#estatus.count" />
    <s:property value="#estatus.first" />
    <s:property value="#estatus.last" />
    <s:property value="#estatus.even" />
    <s:property value="#estatus.odd" />
</s:iterator>

Lo que resta es agregar el formato de la tabla y unos encabezados:

<table>
    <thead>
        <tr>
            <td>Valor</td>
            <td>Indice</td>
            <td>Conteo</td>
            <td>¿Es primero?</td>
            <td>¿Es último?</td>
            <td>¿Es par?</td>
            <td>¿Es impar?</td>
        </tr>
    </thead>
    <tbody>
        <s:iterator begin="1" end="10" step="2" var="valorActual" status="estatus">
            <tr>
                <td><s:property /></td>
                <td><s:property value="#estatus.index" /></td>
                <td><s:property value="#estatus.count" /></td>
                <td><s:property value="#estatus.first" /></td>
                <td><s:property value="#estatus.last" /></td>
                <td><s:property value="#estatus.even" /></td>
                <td><s:property value="#estatus.odd" /></td>
            </tr>
        </s:iterator>
    </tbody>
</table>

Si ejecutamos nuevamente la aplicación, debemos ver la siguiente salida


Ahora veremos cómo iterar a través de una colección de objetos que obtengamos a través de un Action, para esto mostraremos todos los valores de la lista de usuarios del Action "ControlAction". La forma de hacerlo es muy similar a la anterior, sólo que en vez de usar los atributos "begin", "end" y "step" usaremos el atributo "value". Este atributo recibe, como se indica en la lista antes presentada, una colección sobre la cual se realizará la iteración.

Para mostrar todos los valores de la lista "usuarios" debemos colocar la etiqueta como sigue:

<s:iterator value="usuarios">

Con esto iteraremos a través de todos los elementos de la colección. Para mostrar el nombre del usuario actual debemos colocar en nombre del atributo, de la clase "Usuario", que contiene el valor que queremos mostrar, y ponerlo en el atributo "value" de la etiqueta "s:property":

<s:property value="nombre" />

Si dejamos la etiqueta de la siguiente forma:

<s:iterator value="usuarios">
    <s:property value="nombre" /> <br />
</s:iterator>

Obtendremos la siguiente salida:



Cuando trabajamos con colecciones, también es posible indicar el índice de inicio, el índice de fin, y el valor del incremento de los elementos que queremos mostrar. Por ejemplo, si quisiéramos mostrar los elementos cuyo índice se encuentra entre el 3 y el 8, lo colocaríamos de la siguiente forma:

<s:iterator value="usuarios" begin="3" end="8">

Con lo que obtendremos la siguiente salida:


Si estos mismos índices quisiéramos iterarlos de dos en dos, sólo debemos agregar el atributo "step":

<s:iterator value="usuarios" begin="3" end="8" step="2">

Y la salida será la siguiente:


Como ven, usar esta etiqueta es bastante directo.

Ahora veremos la siguiente etiqueta.

generator

Esta etiqueta crea un nuevo iterador, llenándolo con los valores de la cadena que recibe en su atributo "val". Cuando usamos esta etiqueta, el iterador siempre queda en la cima del ValueStack, y es eliminado cuando se encuentra la etiqueta de cierre del iterador.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
varSi StringEl nombre de la variable que será usada para referir al valor actual del ciclo. Esta variable es puesta en la cima del ValueStack.
valSi StringLa cadena que será parseada para crear el iterador.
separatorSi StringLa cadena que se usa para separar cada uno de los valores de "val" y convertirlos en los elementos del iterador.
countNo IntegerEl número máximo de entradas que debe haber en el iterador.
converterNo org.apache.struts2.util.
IteratorGenerator.Converter
El conversor que se usa para transformar de String a un objeto cada uno de los valores del atributo "val"


Una cosa importante que hay que saber de los iteradores que se generan en base a esta etiqueta, es que sólo viven durante el "scope" de la etiqueta, eso quiere decir que en cuanto se encuentre la etiqueta de cierre el iterador desaparecerá.

Comencemos con un ejemplo simple. Generemos un iterador en base a una serie de números, del 1 al 5. Para esto debemos pasar una cadena con los valores al atributo "val" e indicar cuál es el separador de estos valores usando el atributo "separator":

<s:generator val="%{'1,2,3,4,5'}" separator=",">

Como ven, pasamos todos los valores dentro de la misma cadena, separando cada uno con una coma.

Ahora que el iterador está creado, necesitamos usar la etiqueta "<s:iterator>" de la cual ya vimos la forma de trabajo. En este caso como el iterador nuevo está en la cima del ValueStack no hace falta hacer ninguna indicación (al menos que queramos modificar el comportamiento del iterador):

<s:generator val="%{'1,2,3,4,5'}" separator=",">
    <s:iterator>
        <s:property /> 
    </s:iterator>
</s:generator>

El código anterior genera la siguiente salida:



El funcionamiento del iterador generado es el mismo que de cualquier otro iterador, y sus elementos pueden ser de cualquier tipo soportado por OGNL.

append

Esta etiqueta permite juntar los elementos de varios iteradores en un nuevo iterador. En el nuevo iterador primero se encontrarán todos los elementos del primer iterador, luego los elementos del segundo, y así sucesivamente.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
varNo StringEl nombre de la variable que será usada para referir al iterador creado. Esta variable es puesta en la cima del ValueStack.


Para este ejemplo crearemos dos iteradores, uno con la etiqueta "generator" y otro con la lista de usuarios que se obtiene del "ControlAction". Primero crearemos el iterador con la etiqueta "generator":

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
</s:generator>

Podemos ver que en este caso usamos el atributo "var" de la etiqueta "s:generator" para poder hacer referencia posteriormente a este iterador. También debemos recordar que el iterador creado con esta etiqueta sólo estará disponible dentro del scope de la misma.
Ahora usamos la etiqueta "s:append" para crear el nuevo iterador. Usamos el atributo "var" para indicar cuál será el identificador que tendrá este iterador:

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
    <s:append var="iteradorAgregado">
    </s:append>
</s:generator>

El paso final es indicar, usando la etiqueta "s:param" y su atributo "value", los elementos de qué iteradores serán agregados al nuevo iterador:

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
    <s:append var="iteradorAgregado">
        <s:param value="generado" />
        <s:param value="usuarios" />
    </s:append>
</s:generator>

Ahora que se tiene un nuevo iterador, llamado "iteradorAgregado", podemos usarlo en cualquier parte de la página como un iterador normal:

<s:iterator value="%{#iteradorAgregado}">
    <s:property  /> <br />
</s:iterator>

Si ejecutamos la aplicación veremos la siguiente salida:



La salida se ve un poco extraña en la parte que muestra la lista de usuario, pero esto es porque no sobrecargamos el método "toString". Aun así podemos ver que, efectivamente, los elementos de los dos iteradores que indicamos se han agregado al nuevo iterador; primero todos los elementos del primer iterador, y posteriormente todos los elementos del segundo iterador.
Ahora veremos el funcionamiento de la siguiente etiqueta.

merge

Esta etiqueta funciona de forma muy similar a "append", juntando las salidas de varios iteradores en un nuevo iterador. La diferencia principal es que mientras que "append" agrega todos los elementos de un iterador antes de comenzar a agregar los elementos del siguiente, "merge" coloca primero todos los elementos del índice 0 de todos los iteradores, luego los elementos del índice 1, luego todos los elementos del índice 2, así sucesivamente.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
varNo StringEl nombre de la variable que será usada para referir al iterador creado. Esta variable es puesta en la cima del ValueStack.


Nuevamente crearemos, para este ejemplo, dos iteradores, uno con la etiqueta "generator" y otro con la lista de usuarios que se obtiene del "ControlAction". Primero crearemos el iterador con la etiqueta "generator":

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
</s:generator>

Ahora usamos la etiqueta "s:merge" para crear el nuevo iterador. Usamos el atributo "var" para indicar cuál será el identificador que tendrá este iterador:

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
    <s:merge var="iteradorFusionado">
    </s:merge>
</s:generator>

Finalmente, indicamos cuáles iteradores serán fusionados. Para esto usamos la etiqueta "s:param" en cuyo atributo "value" indicamos el nombre del iterador a fusionar:

<s:generator val="%{'a,b,c,d'}" separator="," var="generado" >
    <s:merge var="iteradorFusionado">
        <s:param value="generado" />
        <s:param value="usuarios" />
    </s:merge>
</s:generator>

Ahora con el nuevo iterador, llamado "iteradorFusionado", podemos imprimir los valores contenidos en este:

<s:iterator value="%{#iteradorFusionado}">
    <s:property /><br />
</s:iterator>

Al actualizar la página obtenemos la siguiente salida:



Nuevamente la salida se ve un poco extraña en la parte que muestra la lista de usuario, pero esto es porque no sobrecargamos el método "toString". Pero podemos comprobar que los dos iteradores se han funcionado como lo esperábamos, primero los valores del índice 0 de ambos iteradores, luego los del índice 1, etc.

sort

sort es una de esas etiquetas que nos ahorran mucho esfuerzo, ya que permite ordenar los elementos de una colección haciendo uso de un "Comparator".

La lista ordenada vivirá solo dentro de las etiquetas de apertura y cierre de "s:sort", y será colocada en la variable que indiquemos usando su atributo "var".

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
comparatorSi java.util.ComparatorEl comparador que será usado para ordenar los elementos de la colección.
sourceNo StringLa colección que será ordenada.
varNo StringEl nombre de la variable que será usada para referir al iterador creado que contendrá los elementos ordenados. Esta variable es puesta en la cima del ValueStack.


Como podemos ver en la tabla anterior, para hacer uso de esta etiqueta necesitamos proporcionar un objeto de tipo "java.util.Comparator", el cual contiene las reglas para el ordenamiento de los objetos.

Si recuerdan, cuando creamos el Action "ControlAction" incluimos una clase anidada llamada "ComparadorUsuarios" que implementa la interface "Comparator" y que se encarga de ordenar a los objetos de tipo "Usuario" por orden alfabético usando su nombre. También incluimos una instancia de esta clase dentro de las propiedades de la clase "ControlAction". Pues bien, será esta instancia la que usaremos en el atributo "comparator" de la etiqueta "s:sort":

<s:sort comparator="comparadorUsuarios" source="usuarios">
</s:sort>

En este momento ya se ha creado un nuevo iterador, con sus elementos ordenados (en orden descendente), y ha sido puesto en la cima del "ValueStack", por lo que podemos acceder a él simplemente colocando la etiqueta "s:iterator" sin más parámetros:

<s:sort comparator="comparadorUsuarios" source="usuarios">
    <s:iterator>
    </s:iterator>
</s:sort>

El último paso es indicar qué atributo del iterador de "Usuarios" queremos mostrar, en este caso el "nombre":

<s:sort comparator="comparadorUsuarios" source="usuarios">
    <s:iterator>
        <s:property value="nombre" /><br />
    </s:iterator>
</s:sort>

Si ejecutamos la aplicación deberemos ver la siguiente salida:



Podemos ordenar la salida de varias formas dependiendo del "Comparator" que usemos, pero recuerden que la colección ordenada sólo estará disponible dentro de las etiquetas "s:sort".
Ahora veremos cómo usar la última etiqueta de control.

subset

Esta etiqueta recibe un iterador y regresa un iterador con un subconjunto de elementos del original. Existen varias maneras de indicar cómo debe ser este subconjunto, desde un cierto número de elementos, hasta usando un elemento de decisiones.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
countNo IntegerIndica el número de elementos que debe haber en el subconjunto
startNo IntegerEl índice inicial de las entradas en la fuente, que estarán disponibles en el subconjunto.
sourceNo StringIndica la colección de la cual será tomado el subconjunto.
deciderNo org.apache.struts2.util.
SubsetIteratorFilter.Decider
Determina si un elemento particular debe ser incluido en el subconjunto resultante.
varNo StringEl nombre de la variable que será usada para referir al iterador creado que contendrá los elementos del subconjunto. Esta variable es puesta en la cima del ValueStack.

Comencemos con el ejemplo simple. Nuevamente usaremos la colección de "usuarios" del "ControlAction". Primero limitaremos el número de elementos que tendrá la colección a 3, usando el atributo "count":

<s:subset source="usuarios" count="3">
</s:subset>

Para mostrar los valores del subconjunto, basta con indicar el uso de un iterador y que la propiedad de usuario que queremos mostrar es el nombre:

<s:subset source="usuarios" count="3">
    <s:iterator>
        <s:property value="nombre" /><br />
    </s:iterator>
</s:subset>

Si ejecutamos nuevamente la aplicación debemos ver la siguiente salida:



Ahora, supongamos que no queremos iniciar el subconjunto desde el elemento en el índice 0 de la colección original, sino desde el índice 2 (manteniendo el conteo de 3 elementos en el subconjunto), para esto usamos el atributo "start":

<s:subset source="usuarios" count="3" start="2">
</s:subset>

Nuevamente indicamos que la propiedad de usuario que queremos mostrar es el nombre:

<s:subset source="usuarios" count="3" start="2">
    <s:iterator>
        <s:property value="nombre" /><br />
    </s:iterator>
</s:subset>

Si ejecutamos la aplicación debemos ver la siguiente salida:



Ahora veamos el ejemplo interesante, en el que usaremos un "Decider" para determinar cuáles elementos estarán en la colección. Para esto debemos crear un objeto que implemente la interface "org.apache.struts2.util.SubsetIteratorFilter.Decider". Esta interface sólo tiene un método:

public boolean decide(Object o) throws Exception;

El cual regresa true en caso de que el objeto deba estar en el subconjunto, y false en caso de que no deba estarlo.

Para implementar esta interface crearemos una nueva clase anidada dentro del "ControlAction", yo llamaré a esta clase "FiltroUsuarios":

class FiltroUsuarios implements SubsetIteratorFilter.Decider {
}

Ahora implementamos el método "decide". En este caso la implementación que haremos es sencilla, sólo queremos el subconjunto de todos los "Usuarios" que tienen una "Direccion" establecida (o sea, para los usuarios que su "Direccion" no es null):

public boolean decide(Object o) throws Exception {
    Usuario usuario = (Usuario) o;

    return usuario.getDireccion() != null;
}

La clase "FiltroUsuarios" completa queda de la siguiente forma:

class FiltroUsuarios implements SubsetIteratorFilter.Decider {

    public boolean decide(Object o) throws Exception {
        Usuario usuario = (Usuario) o;

        return usuario.getDireccion() != null;
    }
}

Ahora creamos una nueva instancia de esta clase, dentro del "ControlAction" y agregamos un getter para este nuevo atributo:

private SubsetIteratorFilter.Decider decider = new FiltroUsuarios();

public SubsetIteratorFilter.Decider getDecider() {
    return decider;
}

Dentro de la etiqueta "s:subset" indicamos que usaremos el decider que acabamos de crear:

<s:subset source="usuarios" decider="decider">
</s:subset>

El resto es mostrar los valores del subconjunto:

<s:subset source="usuarios" decider="decider">
    <s:iterator>
        <s:property value="nombre" /><br />
    </s:iterator>
</s:subset>

Si ejecutamos la aplicación debemos ver la siguiente salida:



También podemos usar el resto de los atributos para limitar el número de elementos que se muestran:


<s:subset source="usuarios" decider="decider" count="3" start="2">
    <s:iterator>
        <s:property value="nombre" /><br />
    </s:iterator>
</s:subset>




Con esto terminamos las etiquetas de control, ahora veremos cómo trabajar con las etiquetas de datos.

Etiquetas de Datos

Estas etiquetas permiten mostrar información al usuario de varias maneras y también crear objetos para ejecutar algunas acciones.

Las etiquetas de esta categoría son:
  • a: Crea una etiqueta <a> (un hipervínculo) en HTML
  • url: Se usa para crear una URL.
  • action: Permite llamar a un Action directamente de una JSP.
  • bean: Crea una instancia de una clase dentro de una JSP.
  • date: Da formato una fecha de distintas formas.
  • i18n: Obtiene un ResourceBundle y lo pone en el ValueStack.
  • text: Muestra un texto i18n.
  • include: Incluye la salida del resultado de la ejecución de un Servlet o de una JSP.
  • param: Etiqueta genérica que se usa para pasar valores a otras etiquetas.
  • property: Se usa para mostrar el valor de una propiedad.
  • push: Coloca un valor en la cima del ValueStack.
  • set: Asigna un valor a una variable en un scope especificado.
  • debug: Imprime el contenido del ValueStack.

Para esta categoría no será necesario crear un nuevo Action. Pero para mentener un orden, crearemos una nueva JSP llamada "datos.jsp":



En esta nueva página indicamos que usaremos las etiquetas de Struts 2 usando la directiva "@taglib":

<%@taglib uri="/struts-tags" prefix="s" %>

Comencemos a ver el uso de estas etiquetas:

a

Permite generar un hipervínculo (etiqueta <a> en HTML). Podemos indicar si queremos que el enlace generado sea hacia alguna URL, interna o externa, o para la ejecución de algún Action de nuestra aplicación.

Los atributos de esta etiqueta se muestran a continuación. Como la mayoría de los atributos se derivan directamente de la etiqueta de HTML o son heredados del componente que Struts 2 usa como base, sólo se explicarán los atributos que tengan sentido para esta etiqueta, y los demás se amontonarán en la última fila de la tabla.

NombreObligatorioValor DefaultTipoDescripción
actionNo StringEl Action para el cual se generará el enlace.
encodeNotrueBooleanIndica si es necesario codificar los parámetros que se envían en el enlace.
escapeAmpNotrueBooleanEspecifica si se debe escapar el símbolo ampersand (&) como &amp; o no.
forceAddSchemeHostAndPortNofalseBooleanIndica si se debe forzar el agregar el esquema, el host y el puerto en el enlace que se genera.
hrefNo StringLa URL a la que apuntará el enlace.
includeContextNotrueBooleanSi el contexto actual debe ser incluido en la URL.
includeParamsNononeStringPuede tener los valores "none", "get", y "all", indicando qué parámetros deben ser incluidos en el enlace.
namespaceNo StringEl namespace que se usará al generar el enlace (en caso de que sea para un Action).
methodNo StringEl método del Action que se invocará.
accesskey, anchor, cssClass, cssErrorClass, cssErrorStyle, cssStyle, disabled, errorPosition, javascriptTooltip, key, label, labelSeparator, labelposition, name, onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect, openTemplate, portletMode, portletUrlType, requiredLabel, requiredPosition, scheme, tabindex, template, templateDir, theme, title, tooltip, tooltipCssClass, tooltipDelay, tooltipIconPath, value, windowState

De los atributos anteriores la mayoría sólo tienen sentido cuando se trabaja para generar enlaces a Actions, la cual es la verdadera utilidad de esta etiqueta.

Veamos los ejemplos. Lo primero que haremos es generar dos enlaces, un enlace "normal" a una página, y un enlace a un Action. Como sólo tenemos otra página (control.jsp) y un solo Action (ControlAction) en nuestra aplicación, crearemos los respectivos enlaces a estos.

Colocamos una etiqueta "s:a", en cuyo atributo "href" colocaremos el nombre de la página a la que queremos ir, en este caso "control.jsp":

<s:a href="control.jsp">Página control</s:a>

Hasta aquí tenemos el mismo resultado que si usáramos une etiqueta de hipervínculo normal de HTML (a).

Ahora crearemos el segundo enlace, en este caso al ControlAction. Para crear el enlace, debemos colocar el nombre del Action, que en este caso es "control", en el atributo "Action" de la etiqueta:

<s:a Action="control" >Action control</s:a>

El código de los enlaces, agregando un pequeño salto de línea, queda de la siguiente forma:

<s:a href="control.jsp">Página control</s:a>
<br />
<s:a action="control" >Action control</s:a>

Si ejecutamos la aplicación en entramos a la siguiente dirección:

http://localhost:8080/tags/datos.jsp

Debemos ver la siguiente salida:



Si revisamos el código generado, veremos lo siguiente:

<a href="control.jsp">Página control</a>

<a href="/tags/control.action">Action control</a>

Podemos observar que el primer enlace es idéntico, o casi, al primer enlace que colocamos. El segundo enlace es un poco más interesante, ya que el framework se encarga de buscar cuál Action, dentro del namespace actual (en este caso "tags") tiene el nombre de "control", y coloca la ruta de este Action en el atributo "href" de la etiqueta "a".

Si hacemos clic en cualquiera de los enlaces anteriores, seremos redirigidos a la misma página.
Ahora, ¿qué pasaría si quisiéramos pasar algunos parámetros al enlace generado? Para eso podemos hacer uso de la etiqueta "s:param" y sus atributos "name" y "value". Supongamos que queremos pasar dos parámetros en cada enlace, "param1" y "param2", con los valores "abc" y "123" respectivamente, para eso debemos modificar nuestro código de la siguiente forma:

<s:a href="control.jsp">Página control
    <s:param name="param1" value="%{'abc'}" />
    <s:param name="param2" value="%{'123'}" />
</s:a>
<br />
<s:a action="control">Action control
    <s:param name="param1" value="%{'abc'}" />
    <s:param name="param2" value="%{'123'}" />
</s:a>

Si ejecutamos la aplicación veremos que el código generado para los enlaces es el siguiente:

<a href="control.jsp">Página control</a>
<br />
<a href="/tags/control.action?param1=abc&param2=123">Action control</a>

Aquí podemos ver una de las diferencias entre usar el atributo "href" y el atributo "action". El URL primer caso no se han agregado los parámetros de la petición al enlace, pero en el segundo casi sí. ¿Por qué ocurre esto? La respuesta es muy fácil: no lo sé ^_^!. ¿Qué podemos hacer si queremos pasar parámetros a la etiqueta "s:a" usando el atributo "href"? Para esos casos la única solución es pasar los parámetros directamente en el atributo "href", de la siguiente forma:

<s:a href="control.jsp?param1=%{'abc'}&param2=%{'123'}">Página control</s:a>

Con esto se generará el siguiente enlace:

<a href="control.jsp?param1=abc&param2=123">Página control</a>

El resto de los atributos de la etiqueta "s:a" no funcionan cuando trabajamos con el atributo "href", así que sólo veremos el código para el atributo "action".

Acabamos de ver cómo pasar parámetros a la etiqueta "s:a", pero ¿qué pasa si recibimos los parámetros por la URL o por algún otro medio o si no sabemos qué y cuántos parámetros vamos a recibir, y queremos pasarlos directamente al enlace? Para esos casos podemos usar el atributo "includeParams". Si revisan la tabla anterior podrán ver que este atributo puede recibir tres valores: "none" (su valor por default), "get" y "all". Cuando usamos "get" se agregan todos los parámetros recibidos en la URL (el queryString), y cuando usamos "all" se agregan además los parámetros que se encuentren en el ValueStack. En nuestro ejemplo, usaremos el valor "get":

<s:a Action="control" includeParams="get">Action control</s:a>

Y si entramos en la siguiente URL:

http://localhost:8080/tags/datos.jsp?param4=456&param5=def

Con lo que veremos que el enlace generado queda de la siguiente forma:

<a href="/tags/control.action?param4=456&param5=def">Action control</a>

Vemos que los parámetros se pasan tal cuál al nuevo enlace.

Podemos ver a lo largo de todos los ejemplos anteriores que en el enlace generado siempre es relativo, ¿qué pasaría si quisiéramos que fuera absoluto (que incluya el protocolo, host y puerto)? Para eso podemos usar el atributo "forceAddSchemeHostAndPort" con un valor de "true":

<s:a action="control" forceAddSchemeHostAndPort="true">Action control</s:a>

Con esto, el enlace generado quedará de la siguiente forma:

<a href="http://localhost:8080/tags/control.action">Action control</a>

Podemos ver que, efectivamente, la URL ha pasado de ser relativa a ser absoluta.

Podemos ver también, en los ejemplos anteriores, que el enlace generado incluye el contexto de la aplicación, en este caso "tag". ¿Qué podemos hacer si no queremos que este contexto esté en el enlace? Usamos el atributo "includeContext" con el valor de "false":


<s:a action="control" includeContext="false">Action control</s:a>


Con esto el enlace generado será el siguiente:


<a href="/control.action">Action control</a>


El cual ya no incluye la parte de "tags".

Ahora otra pregunta, ¿si tenemos dos Actions con el mismo nombre, pero en dos namespaces distintos? Para esos casos podemos usar el atributo "namespace". En él indicamos el namespace en el que se encuentra el Action para el que queremos generar el enlace. Si, por ejemplo, quisiéramos un enlace para un Action que se encuentra en el namespace "test", lo haríamos de la siguiente forma:

<s:a action="control" namespace="test">Action control</s:a>

Con lo cual el enlace queda de la siguiente forma:

<a href="test/control.action">Action control</a>

Finalmente, si queremos que se ejecute un método particular del Action, podemos indicarlo usando el atributo "method" de la etiqueta. Para el ejemplo, como sólo tenemos el método "execute" indicaremos que debe ser este método el que se ejecute:

<s:a action="control" method="execute">Action control</s:a>

Con lo que el enlace generado queda de la siguiente forma:

<a href="/tags/control!execute.action">Action control</a>

Podemos ver el uso de esta etiqueta es muy sencillo y tiene muchos atributos que nos permiten indicar exactamente cómo debe ser el enlace que queremos que se genere.

Ahora veremos cómo trabajar con la siguiente etiqueta.

url

Se usa para crear una URL que puede ser usada en... bueno, en cualquier lado donde se use una URL, por ejemplo para indicar dónde se encuentra una imagen, otro sitio web, para crear un enlace, etc.

Al igual que con la etiqueta "s:a", si queremos pasar algún parámetro podemos hacerlo con la etiqueta "s:param".

Muchos de los atributos de esta etiqueta son similares a los de la etiqueta "s:a", ya que el propósito de estos es generar una URL acertada para lo que estemos haciendo. No veremos ejemplos de todos los atributos de esta etiqueta ya que, de alguna manera, ya los vimos en los de la etiqueta "s:a".

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
actionNo StringEl Action para el cual se generará el enlace.
encodeNotrueBooleanIndica si es necesario codificar los parámetros que se envían en el enlace.
escapeAmpNotrueBooleanEspecifica si se debe escapar el símbolo ampersand (&) como &amp; o no.
forceAddSchemeHostAndPortNofalseBooleanIndica si se debe forzar el agregar el esquema, el host y el puerto en el enlace que se genera.
includeContextNotrueBooleanSi el contexto actual debe ser incluido en la URL.
includeParamsNononeStringPuede tener los valores "none", "get", y "all", indicando qué parámetros deben ser incluidos en el enlace.
namespaceNo StringEl namespace que se usará al generar el enlace (en caso de que sea para un Action).
methodNo StringEl método del Action que se invocará.
valueNo StringLa dirección a la que apuntará esta URL (si no se usa el atributo "action").
varNo StringEl nombre de la variable que será usada para referir a la URL. Esta variable es puesta en la cima del ValueStack

anchor, portletMode, portletUrlType, scheme, windowState


Para el primer ejemplo de esta etiqueta necesitaremos una imagen que mostraremos al usuario. En mi caso usaré esta imagen:



Esta imagen debe ser accesible desde el navegador, por lo tanto lo colocaremos dentro del proyecto web, pero fuera del directorio WEB-INF. En mi caso crearé un nuevo directorio llamado "img":



Dentro de este directorio colocamos la imagen de la cual crearemos el enlace.

Ahora que tenemos la imagen, podemos crear una URL que apunte a ella y esta se muestre en el navegador, de la siguiente forma:


<s:url value="/img/JT_logo.png" />


Si colocamos esta etiqueta dentro del atributo "src" de una etiqueta "img" de HTML, de la siguiente forma:


<img src="<s:url value="/img/JT_logo.png" />" alt="logo de Java Tutoriales" />


Esto nos genera la siguiente salida:


<img src="/tags/img/JT_logo.png" alt="logo de Java Tutoriales" />


Que se ve así en el navegador:



Algunos se preguntarán: ¿No es lo mismo que escribir directamente la ruta de la imagen? La respuesta es: en este caso sí, es lo mismo. A lo que se preguntarán: ¿Entonces por qué debería usar la etiqueta "s:url" para esto, qué ventaja tiene? Y la respuesta es: Para este ejemplo que acabamos de ver: ninguna. Sin embargo la ventaja viene cuando debemos hacer algo más complejo, como por ejemplo pasar ciertos parámetros dinámicos a la URL (digamos el id de una sesión o de un usuario).

Para comprobar lo anterior podemos usar el atributo "includeParams". Este actúa exactamente igual que el de la etiqueta "s:a": coloca en el enlace los parámetros que le indiquemos ("none", "get" y "all"). Si cambiamos la etiqueta de esta forma:


<img src="<s:url value="/img/JT_logo.png" includeParams="get" />" alt="logo de Java Tutoriales" />


Y pasamos algunos parámetros a la URL, digamos que entramos de la siguiente manera:


http://localhost:8080/tags/datos.jsp?param1=a&param2=b


La etiqueta generada quedará de la siguiente forma:


<img src="/tags/img/JT_logo.png?param1=a&param2=b" alt="logo de Java Tutoriales" />


Es en estos casos donde la etiqueta "s:url" puede ayudarnos (además de las necesidades particulares que nos resuelven sus demás atributos).

Ahora, de los ejemplos anteriores algunos podrían estarse preguntando, si lo único que hacemos es colocar la etiqueta "s:url" en el atributo "src" de la etiqueta "img" para que este muestre la URL indicada, ¿eso quiere decir que la etiqueta "s:url" imprime la URL en la página de salida? Y la respuesta es: si no especificamos nada en el atributo "var", sí. De lo contrario no mostrará nada y colocará la URL en la variable que le indiquemos. Por lo tanto, si simplemente colocamos la etiqueta de esta forma:


<s:url value="/img/JT_logo.png" />


Veremos la siguiente salida en la pantalla:



Eso quiere decir que podemos usarla para generar un enlace a cualquier recurso que queramos. Imaginemos que queremos generar un enlace a Google para realizar una búsqueda, podemos hacerlo de la siguiente forma:


<s:url value="https://www.google.com">
</s:url>


*Nota: En los ejemplos anteriores, al crear el enlace, la etiqueta "s:url" coloca el contexto de nuestra aplicación (a menos que le indiquemos lo contrario con el parámetro "includeContext"). Sin embargo, cuando en una dirección colocamos "www", "http" o "https", el enlace generado NO incluirá el contexto.

El parámetro con el que Google realiza sus búsquedas es "q", supongo que por "query". Así que colocamos un parámetro, con la etiqueta "s:param", cuyo nombre sea "q" y como valor tenga "java+tutoriales", o cualquier otro valor que deseen:


<s:url value="https://www.google.com">
    <s:param name="q" value="'Java+tutoriales'" />
</s:url>


Si dejamos la etiqueta de la forma anterior terminaremos con la URL impresa en el HTML de salida. Lo que queremos es pasarla a un enlace para que el usuario pueda hacer clic en él, por lo tanto usaremos el atributo "var" para crear una variable que haga referencia a la URL que acabamos de crear:


<s:url value="https://www.google.com"  var="enlace" >
    <s:param name="q" value="'Java+tutoriales'" />
</s:url>


La variable en este caso es "enlace" y podemos pasarla directamente a una etiqueta "a", usando la etiqueta "s:property" o a una etiqueta "s:a" usando su atributo "value". Para ejemplificar lo haremos de las dos formas. Primero usando una etiqueta "a" de HTML. Para eso, debemos colocar en su atributo "href" el valor de la variable "enlace", usando una etiqueta "s:property", de la siguiente forma:


<a href="<s:property value="enlace" />" target="_blank" >Búsqueda en Google</a>


Con el valor "_blank" en el atributo "target", logramos que el enlace se abra en una nueva ventana o pestaña, depende de cómo tengamos configurado el navegador.

La etiqueta anterior genera el siguiente enlace:


<a href="https://www.google.com?q=Java%2Btutoriales" target="_blank" >Búsqueda en Google</a>


Que se ve de la siguiente forma en el navegador:



Si hacemos clic en el enlace se abrirá una nueva pestaña con una búsqueda en Google para Java Tutoriales.

Ahora veremos cómo usar la etiqueta "s:a" para lograr la misma salida. En este caso debemos pasar el valor de la variable "enlace" al atributo "value", y debemos decirle que evalúe el contenido de esa variable, en otras palabras debemos colocarla entre los símbolos "%{" y "}", de la siguiente forma:


<s:a value="%{enlace}">Búsqueda en Google</s:a>


Al hacer esto obtenemos el mismo enlace de la vez anterior:


<a href="https://www.google.com?q=Java%2Btutoriales" target="_blank" >Búsqueda en Google</a>


Y por lo tanto la misma salida en pantalla.

Podemos ver que esta etiqueta es muy flexible en cuanto a la generación de URLs a la medida se refiere. Incluso podemos crear un enlace a un Action de nuestra aplicación (usando el atributo "action" de la etiqueta) y pasarle parámetros a través de la etiqueta "s:param". Como ya vimos la manera de hacer esto con la etiqueta "s:a" no lo volveremos a hacer aquí, pero queda al lector realizar este ejemplo si así lo desea.

Ahora veremos cómo trabajar con la siguiente etiqueta de control.

action

Esta etiqueta permite ejecutar directamente un Action dentro de una página JSP. El método execute se invocará al encontrar la etiqueta de cierre, por lo que tendremos disponibles los valores del Action después de la etiqueta.

Aquí hay algo importante que explicar. Sabemos que todos los Actions tienen un result el cual normalmente nos envía a una nueva JSP. Sin embargo esta etiqueta nos permite ejecutar un Action dentro de una JSP... entonces se preguntarán, ¿qué pasa con el result del Action que estamos ejecutando? Bien pues el result del Action se ignora dentro de la JSP a menos que indiquemos que queremos que este sea mostrado. Para eso tenemos un parámetro llamado "executeResult". Dentro de los ejemplos quedará más claro cómo funciona esto.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameSi StringNombre del Action que será ejecutado (el nombre como está en la anotación o en el archivo de configuración).
namespaceNoEl namespace de la página donde se usa la etiquetaStringNamespace en el que se encuentra el Action.
executeResultNofalseBooleanSi el resultado de este Action debe ser mostrado (la JSP con el resultado).
flushNotrueBooleanSi se le debe hacer flush al buffer de escritura al final de la etiqueta.
ignoreContextParamsNofalseBooleanSi los parámetros de la petición actual deben ser incluidos cuando la acción sea invocada.
rethrowExceptionNofalseBooleanEn caso de que ocurra una excepción, si esta debe ser lanzada nuevamente.
varNo StringEl nombre de la variable que será usada para referir al Action después de su ejecución. Esta variable es puesta en la cima del ValueStack.


Como podemos ver, esta etiqueta tiene pocos atributos, por lo que es muy sencilla de utilizar.

Comencemos con el ejemplo. Como el único Action que tenemos creado en este momento es el "ControlAction", cuyo nombre es "control", así que será este el que usaremos para el ejemplo.

Lo primero que debemos hacer es colocar la etiqueta "<s:action>" indicando que el nombre del Action que ejecutaremos será "control". También debemos indicar el "namespace" en que se encuentra este Action, en este caso "/":


<s:action name="control" namespace="/">
</s:action>


También es necesario que indiquemos el nombre de la variable con la que haremos referencia a este Action, usando el atributo "var". En mi caso la variable será "controlAction":


<s:action name="control" namespace="/" var="controlAction">
</s:action>


Con esto tendremos a nuestra disposición el resultado del Action, a través de la variable "controlAction", pero sólo en el cierre de la etiqueta. Para comprobar esto colocaremos, tanto dentro del cuerpo de la etiqueta como fuera de él, una validación que nos indicará si la variable "controlAction" es nula:


<s:property value="#controlAction == null" />


De esta manera:


<s:action name="control" namespace="/" var="controlAction">
    Dentro: <s:property value="#controlAction == null" /><br />
</s:action>

Fuera: <s:property value="#controlAction == null" />


Al ejecutar la aplicación debemos ver la siguiente salida:



Como podemos ver en la imagen anterior, la variable dentro del cuerpo de la etiqueta es nula, mientras que afuera de este deja de serlo. Por lo tanto, podemos comprobar que podremos hacer uso de esta variable hasta después de la etiqueta de cierre.

Entonces ¿qué es lo que podemos colocar en el cuerpo de la etiqueta? Pues bien, es aquí donde colocamos parámetros que queramos pasar al Action.

Para ver un ejemplo debemos modificar un poco la clase "ControlAction" para agregar un parámetro que podamos establecer. Para no complicar mucho las cosas, agregaremos un atributo, junto con sus setters y getters, de tipo String llamado "parametro":


private String parametro;

public String getParametro() {
    return parametro;
}

public void setParametro(String parametro) {
    this.parametro = parametro;
}


Ahora, en la etiqueta, establecemos el valor de "parametro" usando la etiqueta "s:param". En su atributo "name" colocamos el nombre del parámetro que queremos establecer, en este caso "parametro", y en su atributo "value" el valor que queramos colocarle al parámetro. Recuerden que si le pasamos una cadena, debemos encerrar esta entre comillas (ya sean dobles o simpes):


<s:action name="control" namespace="/" var="controlAction">
    <s:param name="parametro" value="%{'abc123'}" />
</s:action>


Ahora, para mostrar el valor de este parámetro, debemos usar la variable que definimos en el atributo "var", seguido del nombre de atributo que queremos mostrar:


<s:action name="control" namespace="/" var="controlAction">
    <s:param name="parametro" value="%{'abc123'}" />
</s:action>

Parametro: <s:property value="#controlAction.parametro" />


Si ejecutamos la aplicación debemos ver la siguiente salida:



Como podemos ver, de esta forma podemos acceder a cualquier parámetro del Action. Por ejemplo, para obtener la lista de usuarios del Action, lo hacemos de la siguiente forma:


Usuarios: <s:property value="#controlAction.usuarios" />


Incluso podemos pasar estos valores a otras etiquetas:


<s:iterator value="#controlAction.usuarios">
    <s:property value="nombre" />,              
</s:iterator>


Con el código anterior, si ejecutamos la aplicación, debemos ver la siguiente salida:



Habíamos dicho anteriormente que es posible mostrar el resultado original del Action que estamos ejecutando. Para eso debemos colocar el atributo "executeResult" de la etiqueta "s:action". El resultado será colocado en el lugar exacto en el que se haya colocado la etiqueta.


<s:action name="control" namespace="/" var="controlAction" executeResult="true" >
    <s:param name="parametro" value="%{'abc123'}" />
</s:action>


Como el resultado de la página anterior era la salida de la etiqueta "s:subset", si ejecutamos la aplicación debemos ver la siguiente salida:



Hemos visto cómo trabajar con instancias de clases Action de Struts 2, pero ¿qué pasa si queremos crear instancias de otras clases? Para ese caso podemos hacer uso de otra etiqueta: bean

bean

Instancia una clase que siga la especificación JavaBeans.

Esta bean NO se coloca en la cima del stack, por lo que para hacer referencia al bean debemos usar el nombre que le demos, junto con el operador número (#).

Si queremos establecer algún parámetro del bean, podemos hacerlo en el cuerpo de la etiqueta.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameSi StringEl nombre completo (incluyendo paquete) de la clase que será instanciada.
varNo StringEl nombre de la variable que será usada para referir al Bean.


Para ver un ejemplo de esta etiqueta usaremos nuestra clase "Usuario", la cual conforma la especificación JavaBean. En el atributo "name" de la etiqueta "s:bean" debemos colocar el nombre completo de la clase, en este caso "com.javatutoriales.struts2.tags.modelo.Usuario":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario">
</s:bean>


Y en el atributo "var" colocamos el nombre que tendrá la variable con la que haremos referencia a este bean, en mi caso lo llamaré "beanUsuario":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="beanUsuario">
</s:bean>


Esta clase tiene dos atributos: "nombre" y "edad". Establecemos estos atributos dentro del cuerpo de la etiqueta "s:bean" usando la etiqueta "s:param":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="beanUsuario">
    <s:param name="nombre" value="%{'Alex'}" />
    <s:param name="edad" value="%{123}" />
</s:bean>


Ahora, mostramos los valores de los atributos del bean usando la etiqueta "s:property", indicando la variable que hace referencia al bean y el nombre del atributo que queremos mostrar:


Nombre: <s:property value="#beanUsuario.nombre" /> <br />
Edad: <s:property value="#beanUsuario.edad" />


Si ejecutamos el código anterior debemos ver la siguiente salida:



Podemos ver que la etiqueta "s:bean" es muy fácil de utilizar. Podemos usar cualquier clase que siga la especificación JavaBean, como por ejemplo "java.util.Date".

Ahora veremos la siguiente etiqueta

date

Permite formatear los objetos Date de distintas maneras. Es posible especificar un formato propio como por ejemplo "dd/MM/yyyy hh:mm", con una notación agradable (nice attribute) como "en 2 horas, 14 minutos", o podemos usar un formato predefinido para todas las fechas de la aplicación, la cual se coloca en una de las propiedades con prefijo "struts.date.format" (más adelante veremos estas propiedades).

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameSi StringEl nombre del objeto tipo "java.util.Date" al que se le dará formato.
formatNo StringEl formato que se le dará a la fecha.
niceNo BooleanSi se debe imprimir la fecha con un formato "agradable"
timezoneNo StringEl timezone en el que se debe formatear la fecha.
varNo StringEl nombre de la variable que será usada para referir a la fecha con formato. Esta variable es puesta en la cima del ValueStack.


Para mostrar el uso de esta etiqueta, será necesario crear una pequeña clase de utilidad, la cual tendrá la función de regresar objetos de tipo "java.util.Date" a través de sus distintos métodos. Esta case estará el paquete "com.javatutoriales.struts2.tags.util" (el cual aún no existe, por lo que deberán crearlo previamente) y tendrá el nombre de "Fechas".

El primer método que pondremos a esta clase nos permitirá pasar un día, mes y año, y nos regresará un objeto "java.util.Date" que represente esa fecha. Para esto es necesario hacer uso de la clase "java.util.Calendar", de la siguiente forma:


private Date getDate(int anio, int mes, int dia) {
    Calendar calendario = Calendar.getInstance();
    calendario.setTime(new java.util.Date());
    calendario.set(Calendar.YEAR, anio);
    calendario.set(Calendar.MONTH, mes-1);
    calendario.set(Calendar.DAY_OF_MONTH, dia);

    return calendario.getTime();
}


Ahora crearemos un segundo método que nos regrese la fecha "actual", y digo "actual" porque la fecha que regresaremos será el día que se colocó el primer tutorial de Struts 2, o sea el 19 de Junio del 2011:


public Date getFechaActual()
{
    return getDate(2011, 6, 19);
}


La clase "Fecha" de momento se ve así:


public class Fechas {

    public Date getFechaActual() {
        return getDate(2011, 6, 19);
    }

    private Date getDate(int anio, int mes, int dia) {
        Calendar calendario = Calendar.getInstance();
        calendario.setTime(new java.util.Date());
        calendario.set(Calendar.YEAR, anio);
        calendario.set(Calendar.MONTH, mes-1);
        calendario.set(Calendar.DAY_OF_MONTH, dia);

        return calendario.getTime();
    }
}


Iremos agregando más métodos a esta clase conforme avancemos en el uso de la etiqueta "date".

Para usar esta etiqueta haremos uso de OGNL, como lo vimos en el segundo tutorial de la serie.

Ahora veamos cómo trabaja el atributo "format". Este atributo funciona usando los patrones definidos en la clase "SimpleDateFormat" de java. Con estos podemos especificar cualquier formato de fecha y hora que deseemos para que se ajuste a nuestras necesidades.

Primero colocaremos la etiqueta pasando a su parámetro "name" el objeto date al que le daremos formato. Como nosotros no tenemos ningún objeto "Date" en el ValueStack debemos pasar, en vez del nombre del objeto "Date", una expresión en OGNL para crear ese objeto (pueden ver una explicación más detallada de por qué funciona esto en el segundo tutorial de la serie). Nosotros invocaremos al método "getFechaActual" de la clase "Fechas":


<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaActual()"/>


Ahora en el parámetro "format" pasaremos el formato que queramos darle al objeto "Date" (la cual incluye fecha y hora). En este atributo debemos colocar las letras del patrón que queremos poner.

Veremos una serie de pequeños ejemplos para ver las salidas que se generan con los distintos patrones (pueden ver la lista completa de letras de patrones y una explicación de cada uno en la documentación de la clase SimpleDateFormat.

Si queremos mostrar la fecha en un formato de dd/mm/yyyy, en donde el mes se muestre como número, debemos colocar el siguiente patrón:


dd/MM/yyyy


Con lo que la fecha se mostrará de la siguiente forma:


19/06/2011


Si queremos mostrar lo mismo, pero mostrando el nombre del mes, usamos el siguiente patrón:


dd/MMMM/yyyy


Con lo que la fecha se muestra de la siguiente forma:


19/junio/2011


Si en vez de las diagonales queremos usar la palabra "de" como separador, usamos el siguiente patrón:


dd 'de' MMMM 'de' yyyy


La fecha se muestra de la siguiente forma:


19 de junio de 2011


Ahora vamos con la hora. Si queremos mostrar la hora en formato de 0 a 24 hrs, usamos el siguiente patrón:


HH:mm


Con lo que la hora se formateará de la siguiente forma:


21:12


Si queremos mostrarla en formato de 0 a 12, agregando la marca de am/pm, usamos el siguiente patrón:


KK:mm a


Con lo que la fecha se muestra de la siguiente forma:


09:12 PM


También podemos combinar los patrones de fecha y hora. Por ejemplo si usamos el siguiente patrón:


dd/MMM/yy, KK:mm a


Se mostrará la fecha de la siguiente forma:


19/jun/11, 09:22 PM


Creo que la idea ya quedó clara. Ahora podemos ver cómo funciona el atributo "nice".

Este atributo es muy interesante. Permite mostrar la fecha con algo que llaman formato "agradable" pero, ¿qué significa esto? Una forma de entender esto es que (y créanme que no tiene nada que ver con formato) es que muestra el tiempo, que ha paso o que falta, de la fecha a actual a la fecha que recibe como parámetro…. ¿y esto qué quiere decir?

Básicamente lo que hace "nice" es mostrar de una forma clara el resultado de operaciones entre fechas. Por ejemplo, si han pasado sólo unos segundos la cadena que se mostrará será algo como "un instante", si han paso 2 días se mostrará la cadena "2 días", si faltan 3 meses se mostrará la cadena "en 90 días". Esto quedará más claro con un ejemplo.

Primero, crearemos un nuevo método en la clase "Fechas" que nos regresará una fecha en el futuro. En este caso, la fecha del primero de enero del 2100. Agregamos este nuevo método en la clase "Fechas":


public Date getFechaFutura()
{
    return getDate(2100, 10, 19);
}


Ahora, de regreso a la página "datos.jsp", agregaremos dos etiquetas "s:date", a la primera le pasaremos en su atributo "name" una invocación al método "getFechaActual()", y a la segunda la invocación al método "getFechaFutura()". En ambas etiquetas colocaremos el valor del atributo "nice" en "true", de la siguiente forma:


<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaActual()" nice="true" />
<s:date name="new com.javatutoriales.struts2.tags.util.Fechas().getFechaFutura()" nice="true" />


Si ahora actualizamos la página, veremos aparecer una salida más o menos como la que sigue:


2 years, 123 days ago
in 87 years, 21 days


Esto es el tiempo que ha pasado, o que falta, de mi fecha actual (el momento en el que escribo este tutorial) a las dos fechas indicadas. Esto funciona tanto como para años, días, horas, minutos y segundos. Si ven en la lista anterior, no hay "meses", eso quiere decir que si tenemos una diferencia de dos meses, esta se representará como "60 días" (o el número de días correspondientes).
Existen algunas propiedades pre-establecidas que indican los textos que deben mostrarse en cada caso. Estas propiedades se muestran en la siguiente tabla:

PropiedadFormato
struts.date.format.past{0} ago
struts.date.format.futurein {0}
struts.date.format.secondsan instant
struts.date.format.minutes{0,choice,1#one minute|1<{0} minutes}
struts.date.format.hours{0,choice,1#one hour|1<{0} hours}{1,choice,0#|1#, one minute|1<, {1} minutes}
struts.date.format.days{0,choice,1#one day|1<{0} days}{1,choice,0#|1#, one hour|1<, {1} hours}
struts.date.format.years{0,choice,1#one year|1<{0} years}{1,choice,0#|1#, one day|1<, {1} days}


Donde los números que aparecen entre llaves representan marcadores de posiciones para los datos de la fecha.

Pero ¿qué pasa si queremos mostrar los textos en un idioma distinto o si queremos modificar el texto que se muestra por default? Por ejemplo, ¿qué pasa si queremos mostrar el tiempo que ha transcurrido desde una fecha usando el texto "hace {0}" o "han pasado {0}" o si queremos mostrar el número exacto de segundos que han transcurrido? Entonces tenemos que modificar los valores de las propiedades correspondientes.

Para modificar estos valores es necesario hacer uso del mecanismo de i18n (internacionalización) de Struts 2. Hacer esto es muy sencillo, consiste en tres simples pasos:

  1. Crear un archivo que contendrá las propiedades (llave y valor) con los textos que se usarán en cada caso.
  2. Colocar los textos en el archivo de propiedades.
  3. Indicar a Struts 2 que debe usar este archivo usando la constante "struts.custom.i18n.resources".
Ya habíamos hecho esto anteriormente en el tercer tutorial de la serie cuando trabajamos en la carga de archivos al servidor.

Vamos con el primer paso. Para crear este archivo hacemos clic derecho sobre el nodo "Source Package" del panel de proyectos. En el menú contextual que aparece seleccionamos la opción "New -> Properties File..." (si no tienen esa opción en el menú, seleccionen la opción "Other..." y en la ventana que se abre seleccionen la categoría "Other" y el tipo de archivo "Properties File"):



Llamaremos a este archivo "struts-mensajes" (el IDE se encargará de colocar automáticamente la extensión .properties). Damos clic en el botón "Finish" y veremos aparecer en el editor nuestro archivo de propiedades. En este archivo colocaremos los textos que los usuarios verán al mostrar la información de la diferencia de las fechas, por ejemplo podemos poner:


struts.date.format.past=han pasado {0}
struts.date.format.future=faltan {0}
struts.date.format.seconds=un instante
struts.date.format.minutes={0,choice,1#un minuto|1<{0} minutos}
struts.date.format.hours={0,choice,1#una hora|1<{0} horas}{1,choice,0#|1#, un minuto|1<, {1} minutos}
struts.date.format.days={0,choice,1#un dia|1<{0} dias}{1,choice,0#|1#, una hora|1<, {1} horas}
struts.date.format.years={0,choice,1#un año|1<{0} años}{1,choice,0#|1#, un dia|1<, {1} dias}


Ya que tenemos definidos los mensajes, lo siguiente que debemos hacer es indicarle a Struts 2 dónde se localiza este archivo. Para ello usamos la constante "struts.custom.i18n.resources" para indicar el nombre del archivo (el cual será buscado a partir del directorio raíz de los paquetes de la aplicación, o sea en el nodo "Source Packages". Esta constante podemos colocarla, dependiendo si estamos trabajando con un archivo de configuración en XML o con anotaciones, en el archivo "struts.xml" o como un parámetro de inicialización del filtro de Struts 2 en el archivo "web.xml".

Si están usando el archivo "struts.xml" la constante queda de la siguiente forma:


<constant name="struts.custom.i18n.resources" value="struts-mensajes" />


Y si trabajan con anotaciones, la configuración del filtro queda así:


<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    <init-param>
        <param-name>struts.custom.i18n.resources</param-name>
        <param-value>struts-mensajes</param-value>
    </init-param>
</filter>


Ahora, si volvemos a ejecutar la aplicación (en este caso es necesario detener el servidor y volver a iniciarlo) veremos la siguiente salida:


han pasado 2 años, 123 dias
faltan 87 años, 21 dias


Con esto podemos comprobar que los valores que hemos colocado se respetan.

Ahora veremos un poco sobre internacionalización, con la etiqueta "i18n".

i18n y text

Veremos dos etiquetas en esta sección, ya que ambas se usan juntas y una no tiene sentido sin la otra.

Primero hablaremos de "i18n". Esta etiqueta obtiene un resource boundle (un archivo de propiedades donde colocamos las llaves y los textos que representan) y lo coloca en el ValueStack. Esto permite a la etiqueta de texto acceder a los mensajes del archivo.

Los textos estarán disponibles únicamente dentro de cuerpo de la etiqueta.

La etiqueta "i18n" sólo tiene un atributo, el cual se muestra en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameSi StringEl nombre del resource boundle que se usará.


Ahora, la etiqueta "text" se encarga de mostrar los textos que son cargados en el resource boundle con la etiqueta "i18n". En otras palabras: con "i18n" cargamos los textos, y con "text" los mostramos. Esto siempre y cuando el texto no sea algún componente de HTML como algún elemento de un formulario (para estos se usa el atributo "key" del componente), sino un texto libre dentro de la página.

Podemos indicar un valor por default para los casos en los que no se encuentre la llave indicada dentro del archivo de propiedades. Este texto lo colocamos dentro del cuerpo de la etiqueta (la llave se coloca en el atributo "name" de la etiqueta).

Los atributos de la etiqueta "text" se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameSi StringLa llave relacionada con el texto que queremos mostrar.
searchValueStackNotrueBooleanBusca en el ValueStack en caso de que la llave indicada no sea encontrada en el resource boundle.
varno StringEl nombre de la variable que será usada para representar el valor, la cual será colocada en la cima del ValueStack.


Para el ejemplo, lo primero que debemos hacer es crear un nuevo archivo de propiedades. Este puede estar en cualquier ubicación del código fuente de nuestro proyecto. Por orden, colocaré el archivo en el paquete "com.javatutoriales.struts2.tags.textos". El nombre de este archivo será "textos.properties":



Dentro de este archivo colocaremos tres textos, el primero será un texto con instrucciones para el usuario, el segundo será el texto de una etiqueta para un campo de texto de un formulario, y el tercero será el texto del botón de enviar de un formulario:


instrucciones=Por favor, coloque el nombre en el campo correspondiente
etiqueta=Nombre
boton=Confirmar


Ahora debemos colocar la etiqueta "s:i18n", y en su atributo "name" colocamos la ruta hasta el archivo de propiedades (textos.properties), pero sin colocar la extensión, de la siguiente forma:


<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
</s:i18n>


Recordemos que para colocar un texto que no estará dentro de ningún componente del HTML, podemos usar la etiqueta "s:text", colocando en su atributo "name" la llave del texto que queremos mostrar. En este caso mostraremos las instrucciones, por lo que el nombre de la llave que colocaremos será "instrucciones":


<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
    <s:text name="instrucciones" />
</s:i18n>


Como los siguientes dos elementos son componentes de un formulario, debemos ponerlos dentro de una etiqueta "s:form" que no tendrá ningún Action. Dentro del formulario agregaremos una etiqueta, un campo de texto, y un botón para enviar el formulario:


<s:form>
    <s:label /><s:textfield />
    <s:submit />
</s:form>


Aún no hemos colocado los textos a estos componentes. Para eso existen dos formas, en la primera usamos el atributo "value" del elemento. Para obtener el texto que se colocaré en los elementos, cuando no se usa la etiqueta "s:text", debemos usar la función "getText" de OGNL. Esta función recibe como parámetro la llave del texto que se mostrará:


<s:form>
    <s:label value="%{getText('etiqueta')}" /><s:textfield />
    <s:submit value="%{getText('boton')}" />
</s:form>


Por lo tanto, nuestro ejemplo queda de la siguiente forma:


<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
    <s:text name="instrucciones" />
    <s:form>
        <s:label value="%{getText('etiqueta')}" /><s:textfield />
        <s:submit value="%{getText('boton')}" />
    </s:form>
</s:i18n>


Si ejecutamos la aplicación deberemos ver la siguiente salida:



Podemos ver que se han tomado los textos que hemos especificado y se muestran en la página sin ningún problema.

La segunda forma es usando el elemento "key" del componente, por ejemplo, si modificamos el ejemplo anterior de la siguiente forma:


<s:i18n name="com/javatutoriales/struts2/tags/textos/textos">
    <s:text name="instrucciones" />
    <s:form>
        <s:label key="etiqueta" />
        <s:textfield />
        <s:submit key="boton" />
    </s:form>
</s:i18n>


La salida será exactamente la misma que la vez anterior.

Ahora veremos la siguiente etiqueta.

include

La etiqueta "include" permite agregar la salida de un server (ya sea un Servlet o una JSP) en la página actual. Esto es útil si tenemos contenido que se repetirá en varias páginas o si queremos conjuntar el contenido de varias en una sola (como una especie de mashup).

Esta etiqueta tiene sólo un atributo, el cual se muestra en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueSi StringEl Servlet o JSP que se incluirá.


Para este ejemplo, hagamos una página simple que tenga tan solo un texto que se mostrará en la página que la incluya. Agreguemos una nueva página llamada "contenido.jsp" en la raíz de las páginas web del proyecto:



Dentro de esta página pueden colocar los elementos que quieran para probar. Yo solo pondré un texto y un enlace a este mismo sitio web ^_^. El contenido de la página será el siguiente:


Bienvenido a <a href="http://javatutoriales.com/">Java Tutoriales</a>


Ahora, de regreso a la página "datos.jsp", lo único que debemos hacer es agregar la etiqueta "s:include", y colocar en su atributo "value" el nombre de la página que acabamos de crear, o sea "contenido.jsp":


<s:include value="contenido.jsp" />


Si actualizamos la página debemos ver la siguiente salida:



¿Qué pasa si queremos pasar algún parámetro a la página que estamos incluyendo? Bien, en ese caso podemos usar, como ya deben estar imaginando, la etiqueta "s:param", en cuyo atributo "name" ponemos el nombre del parámetro que queremos pasar, y en el atributo "value" el valor del atributo.

Modifiquemos un poco la página "contenido.jsp" para recibir un parámetro que será el nombre del usuario al que se le está mostrando la página. Como los parámetros son pasados como parámetros de petición, no podemos usar la etiqueta "s:property" para obtener este valor. Sin embargo podemos usar el lenguaje de expresiones (Expression Language o EL) de JSP para obtenerlo. El contenido de la página "contenido.jsp" queda de la siguiente forma:


Hola ${param.usuario}. Bienvenido a <a href="http://javatutoriales.com/">Java Tutoriales</a>


Ahora en la página "datos.jsp" agregamos, en el cuerpo de la etiqueta "s:include" el parámetro llamado "usuario". En este caso lo pondremos como un valor fijo para no complicar más el ejemplo:


<s:include value="contenido.jsp">
    <s:param name="usuario" value="'Alex'" />
</s:include>


Si actualizamos la página, deberemos ver la siguiente salida:



Con lo que podemos ver que el parámetro se ha recibido de forma correcta.

La siguiente etiqueta que veremos es justamente una que hemos estado usando bastante a lo largo del tutorial y que por lo tanto conocemos bien su funcionamiento "s:param":

param

Esta etiqueta se usa para parametrizar otras etiquetas. Como hemos visto a lo largo del tutorial, cada una de las etiquetas que requiere un parámetro lo recibe a través de "s:param".

Esta etiqueta tiene dos atributos, que se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
nameNo StringEl nombre del parámetro que se está estableciendo.
valueNo StringEl valor del parámetro.


No hay mucho más que decir de esta etiqueta, ya que la hemos estado usando de manera continua, sólo que existen dos formas de indicar el valor del parámetro que estamos estableciendo, y que hay una pequeña diferencia entre uno y otro.

En la primer forma, que es la que hemos estado usando, pasamos el valor usando el atributo "value", de la siguiente manera:


<s:param name=
"parametro" value="valor" />

Cuando trabajamos de esta forma, el valor que se evalúa como un Object, es decir que podemos pasar cualquier objeto y la etiqueta que estamos parametrizando recibirá este objeto tal cual. Es por esta razón que cuando establecemos cadenas debemos encerrarlas entre comillas simples o dobles.

En la segunda forma en establecer el valor, este se coloca en el cuerpo de la etiqueta, de la siguiente forma:


<s:param name=
"parametro">valor</s:param>

Cuando hacemos esto, el parámetro se evalúa como un String.

No haremos un ejemplo de esta etiqueta ya que, nuevamente, la hemos estado usando a lo largo del tutorial; además de que no puede ser usada sin otras etiquetas.

Ahora veremos la siguiente etiqueta, la cual también hemos estado usando bastante a lo largo de este tutorial.

property

Se usa para lo que ya hemos visto: especificamos el nombre de una propiedad de un objeto, el cual se espera que esté en la cima del stack, y se obtiene su valor. Recordemos que si no especificamos un valor, esta etiqueta mostrará lo que sea que se encuentre en la cima del stack.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueNoLo que sea que se encuentre en la cima del stackObjectEl valor a ser mostrado.
defaultNo StringEl valor que se usará si el atributo "value" es nulo.
escapeCsvNo BooleanIndica si se debe escapar información en CSV (mostrarse en vez de evaluarse).
escapeHtmlNo BooleanIndica si se debe escapar información en HTML (mostrarse en vez de evaluarse).
escapeJavaScriptNo BooleanIndica si se debe escapar información en JavaScript (mostrarse en vez de evaluarse).
escapeXmlNo BooleanIndica si se debe escapar información en XML (mostrarse en vez de evaluarse).


Esta etiqueta también le hemos estado usando a lo largo de todo el tutorial, por lo que tampoco veremos un ejemplo y pasaremos directamente a la siguiente etiqueta.

push

Pone un objeto momentáneamente en la cima del stack.

Esto quiere decir que el objeto que queramos colocar ya debe existir, y sólo estará en la cima del stack en el scope del cuerpo de la etiqueta. Una vez que la etiqueta se cierre, el objeto será removido de la cima del stack.

Esta etiqueta sólo tiene un atributo, el cual se muestra en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueSi StringEl nombre del objeto que se colocará en la cima del stack.


Para este ejemplo, haremos uso nuevamente de la etiqueta "s:bean" para crear un objeto el cual posteriormente pondremos en el stack. Recordemos que "s:bean" crea un objeto, pero no lo pone en el stack por si sólo, así que para hacer referencia a él debemos usar el operador número (#).


Entonces, lo primero que haremos es crear un nuevo bean, de la siguiente forma:


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario">
     <s:param name="nombre" value="'Alex'" />
     <s:param name="edad" value="123" />
</s:bean>


Nuevamente: este bean NO es colocado en la cima del ValueStack, por lo que para hacer uso de él tendíamos que usar su nombre (en este caso "usuario") junto con el operador número (#).

Comprobemos que lo anterior es cierto usando la etiqueta "s:property". Recordemos que si no indicamos ningún valor para "s:property", esta mostrará lo que se encuentre en la cima del stack. Si colocamos la etiqueta de esta forma:


<s:property />


Debemos ver, más o menos, la siguiente salida:


com.opensymphony.xwork2.DefaultTextProvider@1ef4d22


¿Qué es este objeto? No lo sé (pero no importa).

Ahora usemos la etiqueta "s:push", colocando en su atributo "value" el nombre del objeto que queremos colocar en la cima del stack, o sea nuestro bean llamado "usuario":


<s:push value="usuario">
</s:push>


Si dentro del cuerpo de la etiqueta colocamos nuevamente la etiqueta "s:property", de la siguiente forma:


<s:push value="usuario">
    <s:property />
</s:push>


Debemos ver la siguiente salida:


com.javatutoriales.struts2.tags.modelo.Usuario@d364bc


Como ahora el objeto "usuario" está en la cima del stack, podemos obtener directamente cualquiera de sus propiedades, simplemente poniendo el nombre de la propiedad en la etiqueta "s:property". Por ejemplo, para mostrar el nombre y la edad, lo hacemos de la siguiente manera:


<s:push value="usuario">
    Nombre: <s:property value="nombre" /><br />
    Edad: <s:property value="edad" />
</s:push>


Con lo que debemos ver la siguiente salida:



Si volvemos a colocar la etiqueta "s:propery" fuera del cuerpo de "s:push" debemos ver la misma salida de antes.

¿Para qué puede servirnos esta etiqueta? Como una utilidad. Por ejemplo si el objeto "usuario" tuviera muchos atributos que tuviéramos que mostrar, sería más fácil ponerlo en la cima del stack y hacer referencia simplemente al nombre de cada atributo, sin tener que precerlos con el nombre del objeto. O si tenemos un objeto compuesto por otros objetos y queremos poder tener alguno de los "sub-objetos" en la cima por algún tiempo.

Ahora veamos la siguiente etiqueta:

set

set es otra de esas etiquetas simples pero muy útiles. Permite asignar un valor a una variable en un scope especificado. Es útil cuando queremos asignar el resultado de una expresión compleja o larga a una variable para usar esta en vez de la expresión. Esto es útil en varios casos, por ejemplo cuando la expresión toma tiempo en evaluarse (mejora de rendimiento) y cuando es difícil de leer (mejora en la legibilidad).

También sirve, aunque esta funcionalidad no está documentada, para poder colocar valores que pueden ser leídos desde el EL de JSP en el ValueStack de OGNL para que podamos usarlas desde Struts 2.

Los scopes disponibles son básicamente los mismos que la especificación de servlets (application, session, request y page) más el scope "action", que establece la variable en el scope del ActionContext de Struts 2.

Si no se especifica ningún scope, por default la variable se coloca en el scope "action".

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
varNo StringEl nombre que tendrá la variable usada para referenciar al valor que es colocado en el ValueStack.
valueNo StringEl valor que se le asignará a la variable.
scopeNo StringEl scope en el que se asignará la variable.


Para el primer ejemplo, haremos de cuenta que hay un dato que se repite mucho en la página o que es muy largo (puede ser una fecha, un nombre, algún valor, un número de teléfono, etc.), y queremos manejarlo con una variable más corta o que si este valor cambiara sólo tuviéramos que moverlo en un lugar y que se refleje en todos los lugares donde la usamos.

Lo primero que haremos será coloca la etiqueta "s:set", en su atributo "var" pondremos el nombre de la variable, que en este caso será "datos":


<s:set var="datos" />


En su atributo "value" colocaremos una cadena de texto muy larga, la cual contendrá mis datos (nombre, sitio web y correo electrónico), de la siguiente forma (no olviden colocar el valor entre comillas simples dentro de las comillas dobles):


<s:set var="datos" value="'Programador Java: www.javatutoriales.com, programadorjavablog@gmail.com'" />


Ahora, para mostrar el valor de esta variable usamos la etiqueta "s:property", indicando en su atributo "value" el nombre de la variable, en este caso "datos":


<s:property value="datos" />


Al ejecutar la aplicación debemos ver la siguiente salida:



Para el siguiente ejemplo haremos de cuenta que hacemos un cálculo complejo y lo almacenamos en una variable. Estos valores los recibiremos usando el Expression Language (EL) de JSP. Lo primero que haremos es colocar una etiqueta "s:set", y en su atributo "var" colocamos "calculo":


<s:set var="calculo">
</s:set>


El cálculo que haremos tendrá valores constantes para facilitar el ejemplo, y en esta ocasión colocaremos el valor en el cuerpo de la etiqueta:


<s:set var="calculo">${1 + 2 + 3 + 4 + 5}</s:set>


Para mostrar el valor de la variable (el resultado del cálculo), usaremos nuevamente la etiqueta "s:property":


<s:property value="calculo" />


Al ejecutar nuevamente la aplicación debemos ver la siguiente salida:



Con lo que podemos ver que el resultado es correcto.

Una pregunta que oigo (y me hago a mí mismo) de forma constante, es si es posible cambiar el valor de una propiedad de un bean, creado con "s:bean", usando esta etiqueta. La respuesta es muy simple: NO. Esta etiqueta no establece el valor de un bean, sino que crea una nueva variable y le coloca el valor establecido. Veamos un ejemplo de esto. Crearemos nuevamente un bean de la clase "Usuario", al que le daremos el nombre de "usuario", y estableceremos algunos de sus parámetros:


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario">
    <s:param name="nombre" value="%{'Alex'}" />
    <s:param name="edad" value="%{123}" />
</s:bean>


Ahora, mostraremos el valor del atributo "nombre", usando "s:property":


<s:property value="#usuario.nombre" />


Con esto obtendremos en la pantalla el nombre que establecimos al "usuario":


Alex


Hasta aquí todo normal y funcionando como lo hemos visto en el tutorial. Si quisiéramos cambiar el valor de la variable nombre, tal vez podríamos pensar en usar la etiqueta "s:set" de la siguiente forma:


<s:set var="#usuario.nombre" value="'Otro'" />


Pero si volvemos a mostrar el nombre, volveremos a obtener la misma salida de la vez anterior:



Podemos pensar en varias formas de intentar hacer esto, pero el valor no cambiará. Revisando el código fuente de la etiqueta podemos ver que lo único que hace es establecer atributos en el ValueStack, no toma nunca ningún objeto. Lo que si podemos hacer es crear una variable que contenga el valor del nombre del usuario:


<s:set var="nombre" value="#usuario.nombre" />
<s:property value="nombre" />


Que es un ejemplo más de mejora de legibilidad.

Ahora veremos la última etiqueta de control.

debug

Esta es una etiqueta que no usaremos en producción, sino sólo durante la etapa de desarrollo. Su propósito es imprimir el valor del ValueStack y del StackContext en una página, de forma que podamos ver su contenido actual.
Esta etiqueta no tiene ningún atributo por lo que para usarla sólo debemos colocarla en el punto de la página donde queramos analizar el contenido del stack:


<s:debug />


Si la colocamos al final de la página "datos.jsp" y actualizamos la página veremos que aparece el siguiente enlace:



Si hacemos clic en él aparecerá el siguiente contenido en la página:



De esta manera podemos revisar los valores actuales en el stack y verificar que todo está funcionando correctamente.

Como la página en la que estamos trabajando no tiene asociado ningún Action, no hay nada interesante en la sección del ValueStack, pero si bajamos un poco, en el StackContext podremos encontrar, por ejemplo, el valor de la variable que definimos con "s:set":



Si colocamos esta misma etiqueta en "control.jsp" podremos ver los atributos del "ControlAction" asociado con la página:



Con esto terminamos con las etiquetas de control y veremos siguiente tipo de etiqueta, las etiquetas de formulario.

Etiquetas de Formulario

Este tipo de etiqueta se usa para generar los elementos de los formularios del sitio web. Algunas de las etiquetas generan como salida el componente indicado (como un campo de texto) mientras que otros colocan componentes con comportamientos más complejos, como listas con opciones transferibles entre ellas.

Las etiquetas de esta categoría son:



Para los ejemplos de esta categoría no crearemos ningún Action, ya que dedicamos un tutorial completo al trabajo con formularios y cómo obtener en un Action los valores que capturamos a través de este. Sólo crearemos una nueva página, llamada "formulario.jsp", en donde iremos colocando estas etiquetas:



En esta nueva página indicamos que usaremos las etiquetas de Struts 2 usando la directiva "@taglib":


<%@taglib uri="/struts-tags" prefix="s" %>


*Nota: La manera en la que los elementos del formulario se muestran dependen del tema que se esté utilizando (css_xhtml, xhtml, simple), como lo vimos al principio del tutorial. Por default al colocar un elemento este tendrá una etiqueta asociada, la cual muestra el texto indicado en su atributo "label", pero como indicamos que usaremos el tema "simple", y este no genera esta etiqueta, tendremos que colocar la etiqueta de forma manual.


*Nota: Como la mayoría de los atributos de estas etiquetas se derivan directamente de la etiqueta de HTML o son heredados del componente que Struts 2 usa como base, sólo se explicarán los atributos que tengan sentido para la etiqueta que estemos viendo. Los demás atributos (y que ya no colocaremos en las tablas) son:


accesskey, cssClass, cssErrorClass, cssErrorStyle, cssStyle, disabled, errorPosition, id, javascriptTooltip, key, label, labelSeparator, labelposition, listCssClass, listCssStyle, name, onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect, requiredLabel, requiredPosition, tabindex, template, templateDir, theme, title, tooltip, tooltipConfig, tooltipCssClass, tooltipDelay, tooltipIconPath.


*Nota: Todas las etiquetas de este grupo deben estar dentro de un formulario. No lo indicaremos de forma explícita en los ejemplos, pero todas las etiquetas estarán contenidas dentro de las siguientes:

<s:form>
</s:form>

Comencemos a ver la primera etiqueta:

form

Muestra un formulario en HTML... y eso es todo. No hay mucho más que decir de esta etiqueta, ya que el resto del trabajo lo hacen los componentes del formulario.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
actionNo StringEl nombre del Action al que este formulario será enviado.
includeContextNotrueBooleanIndica si el contexto actual debe ser incluido en la URL del Action al que se envirará el formulario.
namespaceNoEl namespace actualStringEl namespace del Action al que este formulario será enviado.
acceptcharset, enctype, focusElement, method, onreset, onsubmit, openTemplate, portletMode, target, validate, value, windowState.


Para esta etiqueta no proporcionaremos ningún ejemplo, ya que por sí sola no tiene ninguna utilidad, así que pasaremos directamente a al siguiente etiqueta.

label

Muestra una etiqueta "label" de HTML, la cual se usa para colocar un texto que identifica a un elemento de un formulario. Por lo regular al hacer clic sobre una etiqueta se hace clic también sobre el elemento con el que está relacionado. Para indicar esta relación se usa el atributo "for" de la etiqueta (el cual es un atributo de HTML), indicando el identificador del elemento con el que se relacionará.

Los atributos de esta etiqueta se muestran a continuación:

NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl texto que mostrará la etiqueta.
for


Tampoco veremos ejemplo de esta etiqueta, ya que se requiere de otro elemento para que esta tenga sentido.

head

Agrega elementos necesarios para el funcionamiento y apariencia visual de los elementos de la página, en el encabezado de esta.

Los archivos que se agreguen dependerán del tema que estemos usando.

Esta etiqueta no tiene ningún atributo.

Si colocamos la etiqueta de esta forma:


<head>
    <s:head />
</head>


Obtendremos el siguiente código:


<head>
    <script src="/tags/struts/utils.js" type="text/javascript"></script>
</head>


Un detalle importante de esta etiqueta es que agregará tanto scripts como hojas de estilo en el encabezado de la página. Sin embargo, las "buenas prácticas" dicen que para mejorar el rendimiento del sitio, siempre se deben poner los .css en el encabezado de la página, y los archivos .js en la parte inferior de la página.


file

Muestra un componente para cargar un archivo al servidor.

Ya habíamos hablado de esta etiqueta en el tutorial de trabajo con formularios en donde explicamos detalladamente cómo usar este componente.

Los atributos de esta etiqueta se muestran en la siguiente tabla:


NombreObligatorioValor DefaultTipoDescripción
acceptNo StringAtributo "accept" de HTML indicando los mime types aceptados por el componente.
valueNo StringEl valor pre-establecido que tendrá el componente. Este valor debería venir desde un atributo de un Action.


Veamos un pequeño ejemplo. Colocaremos la etiqueta para que sólo acepte imágenes de cualquier tipo, de la siguiente forma:


<s:file accept="image/*"  name="fileTest" id="fileTest" />


La etiqueta anterior genera el siguiente código:


<input type="file" name="fileTest" value="" accept="image/*" id="fileTest"/>


Que se ve así en el navegador.



Si presionamos el botón "Examinar..." y navegamos a un directorio que tenga varios tipos de archivos, veremos que sólo se nos muestran las imágenes (claro, esto depende del navegador, en IE menor a 10, para variar, esto no funciona bien ^_^).

Si queremos modificar esta etiqueta para que sólo acepte archivos de texto plano y html podemos colocarla de esta forma:


<s:file accept="text/html,text/plain"  name="fileTest" id="fileTest" />


Que genera el siguiente código:


<input type="file" name="fileTest" value="" accept="text/html,text/plain" id="fileTest"/>


Que visualmente es igual al caso anterior.

checkbox

Muestra un elemento input de tipo checkbox.

Como en HTML (al menos hasta antes de la versión 5) si un checkbox no está seleccionado este no se envía al formulario, Struts 2 agrega un elemento "hidden" extra por cada checkbox que colocamos. De esta manera un interceptor especial determina si el valor es true o false, y lo establece correctamente en el Action.

Los atributos de esta etiqueta son:

NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl valor pre-establecido que tendrá el checkbox (true o false). Este valor debería venir desde un atributo de un Action.


Para el ejemplo crearemos un checkbox cuyo nombre e id serán "checkboxTest", y una etiqueta para este componente:


<s:label    for="checkboxTest" value="Checkbox: " />
<s:checkbox id="checkboxTest"  name="checkboxTest" value="false" />


Con esto el HTML generado es el siguiente:


<label for="checkboxTest">Checkbox: </label>
<input type="checkbox" name="checkboxTest" value="true" id="checkboxTest"/>
<input type="hidden" id="__checkbox_checkboxTest" name="__checkbox_checkboxTest" value="true" />


Y se ve de la siguiente forma en el navegador:



Vemos que se ha agregado el elemento de tipo "hidden" que mencionamos anteriormente.

Ahora veamos la siguiente etiqueta.

checkboxlist

Crea una serie de checkboxes desde una lista o un mapa.

Los atributos de esta etiqueta son:

NombreObligatorioValor DefaultTipoDescripción
listSi StringUna fuente iterable (una lista o un mapa) de elementos que llenarán la lista.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá el elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título del elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta del elemento de la lista.
valueNo StringEl valor pre-establecido que tendrá el checkboxlist (los elementos que estarán seleccionados). Este valor debería venir desde un atributo de un Action.


Para el primer ejemplo crearemos una lista que colocaremos directamente en el atributo "list" de la etiqueta. Esta será una lista de cadenas con los valores del uno al cinco. Daremos a la etiqueta un id y un nombre de "checkboxlistTest":


<s:checkboxlist list="{'uno', 'dos', 'tres', 'cuatro', 'cinco'}" id="checkboxlistTest" name="checkboxlistTest" />


Cuando trabajamos con listas de cadenas, como en este caso, el valor y la etiqueta de cada elemento es el mismo, el texto de la cadena. Por lo que el código generado es el siguiente:


<input type="checkbox" name="checkboxlistTest" value="uno" id="checkboxlistTest-1" />
<label for="checkboxlistTest-1" class="checkboxLabel">uno</label>

<input type="checkbox" name="checkboxlistTest" value="dos" id="checkboxlistTest-2" />
<label for="checkboxlistTest-2" class="checkboxLabel">dos</label>

<input type="checkbox" name="checkboxlistTest" value="tres"id="checkboxlistTest-3" />
<label for="checkboxlistTest-3" class="checkboxLabel">tres</label>

<input type="checkbox" name="checkboxlistTest" value="cuatro" id="checkboxlistTest-4" />
<label for="checkboxlistTest-4" class="checkboxLabel">cuatro</label>

<input type="checkbox" name="checkboxlistTest" value="cinco" id="checkboxlistTest-5" />
<label for="checkboxlistTest-5" class="checkboxLabel">cinco</label>

<input type="hidden" id="__multiselect_checkboxlistTest" name="__multiselect_checkboxlistTest" value="" />


Que se ve de la siguiente forma en el navegador:



¿Qué pasa si queremos usar valores distintos para la etiqueta y el valor? En ese caso podemos usar un mapa, del cual la llave se tomará como valor y el valor se tomará como etiqueta, en el atributo "list" de la etiqueta.

Modifiquemos el ejemplo para que ahora el valor sea el número y la etiqueta el nombre del número, de la siguiente forma:


<s:checkboxlist list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="checkboxlistTest" name="checkboxlistTest" />


Lo cual genera el siguiente código:


<input type="checkbox" name="checkboxlistTest" value="1" id="checkboxlistTest-1" />
<label for="checkboxlistTest-1" class="checkboxLabel">uno</label>

<input type="checkbox" name="checkboxlistTest" value="2" id="checkboxlistTest-2" />
<label for="checkboxlistTest-2" class="checkboxLabel">dos</label>

<input type="checkbox" name="checkboxlistTest" value="3" id="checkboxlistTest-3" />
<label for="checkboxlistTest-3" class="checkboxLabel">tres</label>

<input type="checkbox" name="checkboxlistTest" value="4" id="checkboxlistTest-4" />
<label for="checkboxlistTest-4" class="checkboxLabel">cuatro</label>

<input type="checkbox" name="checkboxlistTest" value="5" id="checkboxlistTest-5" />
<label for="checkboxlistTest-5" class="checkboxLabel">cinco</label>

<input type="hidden" id="__multiselect_checkboxlistTest" name="__multiselect_checkboxlistTest" value="" />


Y que se ve de la siguiente forma en el navegador:



Aunque, siendo realistas, casi nunca vamos a generar componentes con elementos estáticos. Lo más normal es generarlos a través de una lista de objetos, típicamente recibida del atributo de un bean. Cuando hacemos esto, debemos indicar qué atributo del objeto se usará como valor y cuál como etiqueta.
Veamos un ejemplo del caso anterior, creando una lista de objetos "Usuario". Tomaremos el atributo "nombre" como etiqueta y, a falta de un atributo mejor, "edad" como valor.

Lo primero es crear unos cuantos objetos usando la etiqueta "s:bean":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
    <s:param name="nombre" value="%{'Alex1'}" />
    <s:param name="edad" value="%{1}" />
</s:bean>

<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
    <s:param name="nombre" value="%{'Alex2'}" />
    <s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
    <s:param name="nombre" value="%{'Alex3'}" />
    <s:param name="edad" value="%{3}" />
</s:bean>


Ahora creamos la lista de checkboxes. En su atributo "list" creamos una lista a la cual pasamos los tres objetos anteriores:


<s:checkboxlist list="{#usuario1, #usuario2, #usuario3}" id="checkboxlistTest" name="checkboxlistTest" />


El último paso es indicar cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "listKey", y para la etiqueta el atributo "listValue":


<s:checkboxlist list="{#usuario1, #usuario2, #usuario3}" id="checkboxlistTest" name="checkboxlistTest" listKey="edad" listValue="nombre" />


El ejemplo anterior genera el siguiente código:


<input type="checkbox" name="test" value="1" id="test-1" />
<label for="test-1" class="checkboxLabel">Alex1</label>

<input type="checkbox" name="test" value="2" id="test-2" />
<label for="test-2" class="checkboxLabel">Alex2</label>

<input type="checkbox" name="test" value="3" id="test-3" />
<label for="test-3" class="checkboxLabel">Alex3</label>

<input type="hidden" id="__multiselect_test" name="__multiselect_test" value="" />


Que se ve así en el navegador:



Esta misma lista de objetos que hemos generado de una forma estática, podríamos obtenerla de forma dinámica a través de un Action.

radio

Muestra un campo de entrada de tipo radio.

Esta etiqueta funciona de forma muy parecida a "s:checkboxlist", recibe una lista de elementos que mostrará como radio buttons.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringUna fuente iterable (una lista o un mapa) de elementos que llenarán la lista.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá el elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título del elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta del elemento de la lista.
valueNo StringEl valor pre-establecido que tendrá la lista de radio buttons (el elemento que estará seleccionado). Este valor debería venir desde un atributo de un Action.


Como esta etiqueta funciona igual a la anterior, saltaremos directo al último ejemplo de la vez anterior, y crearemos una serie de radio buttons basado en una lista de usuarios que crearemos con la etiqueta "s:bean". Primero los beans:


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
    <s:param name="nombre" value="%{'Alex1'}" />
    <s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
    <s:param name="nombre" value="%{'Alex2'}" />
    <s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
    <s:param name="nombre" value="%{'Alex3'}" />
    <s:param name="edad" value="%{3}" />
</s:bean>


Y luego la etiqueta "s:radio":


<s:radio list="{#usuario1, #usuario2, #usuario3}" id="radioTest" name="radioTest" listKey="edad" listValue="nombre" />


La cual genera el siguiente código:


<input type="radio" name="radioTest" id="radioTest1" value="1"/><label for="radioTest1">Alex1</label>
<input type="radio" name="radioTest" id="radioTest1" value="2"/><label for="radioTest1">Alex2</label>
<input type="radio" name="radioTest" id="radioTest1" value="3"/><label for="radioTest1">Alex3</label>


Que se ve así en el navegador:



Podemos ver que en este caso no se generan campos "hidden". Además, a diferencia de los checkbox, aquí no hay una forma de generar sólo un radio button (a menos que se use una lista con un solo elemento).

select

Genera una etiqueta de tipo "select" y una serie de etiquetas "option" llenas con la lista de valores que recibe como parámetro.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" de la etiqueta.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista.
multipleNofalseBooleanIndica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
valueNo StringEl valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un Action.
listCssClass, listCssStyle, size


Esta etiqueta funciona de manera muy similar a "checkboxlist" y "radio": En su atributo "list" puede recibir una lista o un mapa de elementos (los cuales pueden ser cualquier tipo de objeto).

Como a esta altura ya entendimos como poner estos elementos en el atributo "list" veremos, primero, cómo pasar un mapa con datos estáticos.

Si colocamos la siguiente etiqueta:


<s:label for="selectTest" value="Select: " />
<s:select list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="selectTest" name="selectTest" headerKey="-1" headerValue="Selecciona una opcion" />


Obtendremos el siguiente código:


<label for="selectTest">Select: </label>
<select name="selectTest" id="selectTest">
    <option value="-1">Selecciona una opcion</option>
    <option value="1">uno</option>
    <option value="2">dos</option>
    <option value="3">tres</option>
    <option value="4">cuatro</option>
    <option value="5">cinco</option>
</select>


Que se ve de la siguiente forma en el navegador:



Ahora, en vez de usar un mapa, lo haremos con una lista de objetos, así que lo primero que haremos es crear tres beans con la etiqueta "s:bean":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
    <s:param name="nombre" value="%{'Alex1'}" />
    <s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
    <s:param name="nombre" value="%{'Alex2'}" />
    <s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
    <s:param name="nombre" value="%{'Alex3'}" />
    <s:param name="edad" value="%{3}" />
</s:bean>


Y luego creamos una lista con estos objetos, la cual pasamos al atributo "list" de la etiqueta "s:select". También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "listKey", y para la etiqueta el atributo "listValue", de la siguiente forma:


<s:label for="selectTest" value="Select: " />
<s:select list="{#usuario1, #usuario2, #usuario3}" id="selectTest" name="selectTest" listKey="edad" listValue="nombre" headerKey="-1" headerValue="Seleccione una opcion" />


La etiqueta anterior genera el siguiente código:


<label for="selectTest">Select: </label>
<select name="selectTest" id="selectTest">
    <option value="-1">Seleccione una opcion</option>
    <option value="1">Alex1</option>
    <option value="2">Alex2</option>
    <option value="3">Alex3</option>
</select>


Que se ve así en el navegador:



Ahora veamos una etiqueta un poco distinta.

optgroup

Crea, dentro de una etiqueta "select", una etiqueta optgroup de HTML, que marca una serie de categorías con sus elementos. Sirven como una forma de agrupar valores como ayuda visual para el usuario. Por lo tanto esta etiqueta sólo puede ser usada dentro de una etiqueta "select".

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listNo StringLa fuente de datos iterable (un mapa o una lista de objetos) que se usará para llenar los elementos "option" de la etiqueta.
listKeyNo NoLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
labelNo StringEl nombre de la categoría que se mostrará en el componente select. Este valor no puede ser seleccionado.


A diferencia de los casos anteriores, esta etiqueta sólo puede recibir los valores de sus opciones a través de un mapa o de una lista de objetos, siempre y cuando estos NO sean cadenas.


Para el ejemplo lo primero que debemos hacer es tener una etiqueta "s:select", en esta pondremos algunas de las tecnologías existentes de Java:


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
</s:select>


Ahora, en el cuerpo de la etiqueta anterior, colocamos la etiqueta "s:optgroup". El primer grupo que crearemos será de servidores de aplicaciones Java. Por lo que primero colocaremos la etiqueta (esta es como el nombre de la categoría o del grupo, esta etiqueta se muestra de un color más obscuro y no se puede seleccionar), la cual será "Servidores":


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
    <s:optgroup label="Servidores" />
</s:select>


Lo siguiente que haremos será colocar la lista de opciones con un mapa. Aquí pondremos una lista con 4 servidores de aplicaciones. Aquí la llave (lo que se usará como valor) será un número que servirá a modo de identificador, y el valor (lo que se muestra como etiqueta) será el nombre el servidor:


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
    <s:optgroup label="Servidores" list="#{'1':'Glassfish', '2':'JBoss', '3':'WebLogic', '4':'WebSphere'}" />
</s:select>


Colocaremos un segundo grupo, de bases de datos, sólo para ver cómo se ven estos en la interfaz de usuario:


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
    <s:optgroup label="Servidores" list="#{'1':'Glassfish', '2':'JBoss', '3':'WebLogic', '4':'WebSphere'}" />
    <s:optgroup label="Bases de Datos" list="#{'5':'Oracle', '6':'MySQL', '7':'SQL Server', '8':'PosgreSQL'}" />
</s:select>


La etiqueta anterior genera el siguiente código:


<select name="selectTest" id="selectTest">
    <option value="JSE">JSE</option>
    <option value="JEE">JEE</option>
    <option value="JME">JME</option>
    
    <optgroup label="Servidores">
        <option value="1">Glassfish</option>
        <option value="2">JBoss</option>
        <option value="3">WebLogic</option>
        <option value="4">WebSphere</option>
    </optgroup>
    
    <optgroup label="Bases de Datos">
        <option value="5">Oracle</option>
        <option value="6">MySQL</option>
        <option value="7">SQL Server</option>
        <option value="8">PosgreSQL</option>
    </optgroup>
</select>


Que se ve así en el navegador:



Para el siguiente ejemplo, veremos cómo tomar la lista de opciones para la etiqueta "s:optgroup". Como el único bean que tenemos en la aplicación es el bean Usuario, haremos uso de este para nuestra lista de opciones.

Lo primero que haremos es crear tres beans, cada uno con un nombre y una edad distinta:


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
    <s:param name="nombre" value="%{'Alex1'}" />
    <s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
    <s:param name="nombre" value="%{'Alex2'}" />
    <s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
    <s:param name="nombre" value="%{'Alex3'}" />
    <s:param name="edad" value="%{3}" />
</s:bean>


Ahora crearemos la etiqueta "s:select" como la teníamos anteriormente:


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
</s:select>


Dentro del cuerpo de esta colocaremos el primer grupo, que será nuevamente el de "Base de Datos":


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
    <s:optgroup label="Bases de Datos" list="#{'Oracle':'5', 'MySQL':'6', 'SQL Server':'7', 'PosgreSQL':'8'}" />
</s:select>


Y como segundo grupo agregaremos uno llamado "Usuario" en la que pasaremos una lista con los objetos que creamos anteriormente. También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "listKey", y para la etiqueta el atributo "listValue", de la siguiente forma:


<s:select id="selectTest" name="selectTest" list="{'JSE', 'JEE', 'JME'}">
    <s:optgroup label="Bases de Datos" list=" #{'5':'Oracle', '6':'MySQL', '7':'SQL Server', '8':'PosgreSQL'}" />
    <s:optgroup label="Usuarios" listKey="edad" listValue="nombre" list="{#usuario1, #usuario2, #usuario3}" />
</s:select>


La etiqueta anterior genera el siguiente código:


<select name="selectTest" id="selectTest">
    <option value="JSE">JSE</option>
    <option value="JEE">JEE</option>
    <option value="JME">JME</option>

    <optgroup label="Bases de Datos">
        <option value="5">Oracle</option>
        <option value="6">MySQL</option>
        <option value="7">SQL Server</option>
        <option value="8">PosgreSQL</option>
    </optgroup>
    
    <optgroup label="Usuarios">
        <option value="1">Alex1</option>
        <option value="2">Alex2</option>
        <option value="3">Alex3</option>
    </optgroup>
</select>


Que se ve así en el navegador:



Como ven usar esta etiqueta es muy sencillo y genera una ayuda visual para clasificar cosas.

Ahora veamos la siguiente etiqueta.

textfield

Esta etiqueta muestra un elemento input de tipo texto, o sea un campo de texto.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un Action.
maxlength, readonly, size


Este será un ejemplo sencillo. Colocamos la etiqueta de la siguiente forma:


<s:label for="textfieldTest" value="Texto: " />
<s:textfield id="textfieldTest" name="textfieldTest" value="texto" />


Con lo que obtenemos el siguiente código:


<label for="textfieldTest">Texto: </label>
<input type="text" name="textfieldTest" value="texto" id="textfieldTest"/>


Que se ve de la siguiente forma en el navegador:



Así de fácil ^_^!

password

Muestra una etiqueta input de tipo password, esos que lo que escribamos lo muestran con una serie de asteriscos.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

venir desde un atributo de un Action.
NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un Action.
showPasswordNofalseBooleanSi se debe mostrar o no, en el código fuente de la página, el valor del password.
maxlength, readonly, size


El primer ejemplo será muy sencillo. Si colocamos la siguiente etiqueta:


<s:label for="passwordTest" value="Password: " />
<s:password id="passwordTest" name="passwordTest" value="password"  />


Obtenemos el siguiente código:


<label for="passwordTest">Password: </label>
<input type="password" name="passwordTest" id="passwordTest"/>


Que se ve así en el navegador:



En el ejemplo anterior podemos notar que no se ve el valor que pusimos en el atributo "value". Esto es una medida de seguridad, ya que si permitimos que se muestre el valor, aunque en la interfaz de usuario lo veríamos como una serie de asteriscos, en el código fuente este se vería de forma clara; y algún usuario "listo" podría obtener el valor de este preciado recurso.

Aun así, podría existir alguna razón por la que quisiéramos mostrar el valor del password. Para este caso lo único que debemos hacer es poner en true el valor del atributo "showPassword", de la siguiente forma:


<s:label for="passwordTest" value="Password: " />
<s:password id="passwordTest" name="passwordTest" value="password" showPassword="true"  />


Con lo que obtendremos el siguiente código (noten en valor del atributo "value", ¡ups!):


<label for="passwordTest">Password: </label>
<input type="password" name="passwordTest" value="password" id="passwordTest"/>


Que se ve de esta forma en el navegador:



En la vista no tenemos nada de qué preocuparnos.

La siguiente etiqueta también será de las simples.

hidden

Muestra una etiqueta input de tipo "hidden".

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl valor pre-establecido que tendrá el campo de texto. Este valor debería venir desde un atributo de un Action.


Este ejemplo también será muy sencillo. Si colocamos la siguiente etiqueta:


<s:hidden name="hiddenTest" value="abc123" />


Obtenemos el siguiente código:


<input type="hidden" name="hiddenTest" value="abc123" id="hiddenTest"/>


El cual no genera ninguna salida en pantalla.

textarea

Muestra un área de texto donde el usuario puede escribir texto de forma libre, incluyendo saltos de línea.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
valueNo StringEl valor pre-establecido que tendrá el área de texto. Este valor debería venir desde un atributo de un Action.
cols, readonly, rows, wrap


Los tributos cols (número de columnas) y rows (número de filas) determinan el tamaño visible del área de texto.

El valor del área de texto se coloca en su atributo "value". Este atributo por lo regular vendrá desde un Action, pero si vamos a definir un texto estático de gran longitud, podemos ponerlo en una variable con la etiqueta "s:set" y posteriormente pasar esta variable a la etiqueta "s:textarea".

El ejemplo será sencillo, si colocamos la siguiente etiqueta:


<s:label for="textareaTest" value="Textarea: " />
<s:textarea cols="20" rows="5" value="Texto que se encontrara en el area" id="textareaTest" name="textareaTest" />


Obtendremos el siguiente código:


<label for="textareaTest">Textarea: </label>
<textarea name="textareaTest" cols="20" rows="5" id="textareaTest">Texto que se encontrara en el area</textarea>


Que se verá así en el navegador:



No hay más que decir de esta etiqueta

A partir de aquí veremos una serie de componentes especiales que hacen uso de JavaScript para agregar comportamiento dinámico.

combobox

Este componente es una mezcla de un input de tipo text y un componente select puestos juntos. Esto permite colocar algún texto en el input seleccionándolo del select, o escribiéndolo directamente en el espacio correspondiente.

Los atributos de esta etiqueta se muestran a continuación:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" de la etiqueta.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista.
emptyOptionNofalseStringIndica si insertar un elemento option vacío.
valueNo StringEl valor pre-establecido del input. Este valor debería venir desde un atributo de un Action.
listCssClass, listCssStyle, maxLength, maxlength, readonly, size


Para llenar el input, la etiqueta genera un pequeño código en JavaScript. Por el resto, esta etiqueta funciona exactamente igual que una etiqueta "s:select" normal, por lo mismo sólo veremos dos ejemplos. El primero usaremos un mapa de opciones:


<s:label for="comboboxTest" value="Combobox: " />
<s:combobox list="#{'1':'uno', '2':'dos', '3':'tres', '4':'cuatro', '5':'cinco'}" id="comboboxTest" name="comboboxTest" headerKey="-1" headerValue="Seleccione un elemento" emptyOption="true" />


Esta etiqueta genera el siguiente código, en el que podemos ver el JavaScript usado para llenar el campo de texto:


<label for="comboboxTest">Combobox: </label>
<script type="text/javascript">
    function autoPopulate_comboboxTest(targetElement) {
      if (targetElement.options[targetElement.selectedIndex].value == '-1') {
         return;
      }
      if (targetElement.options[targetElement.selectedIndex].value == '') {
          return;
      }
      targetElement.form.elements['comboboxTest'].value=targetElement.options[targetElement.selectedIndex].value;
    }
</script>

<input type="text" name="comboboxTest" value="" id="comboboxTest"/><br />
<select onChange="autoPopulate_comboboxTest(this);">
    <option value="-1">Seleccione un elemento</option>
    <option value=""></option>
    <option value="1">uno</option>
    <option value="2">dos</option>
    <option value="3">tres</option>
    <option value="4">cuatro</option>
    <option value="5">cinco</option>
</select>


Esto se ve de la siguiente forma en el navegador:



Al seleccionar cualquier elemento del select, el valor (en este caso el número) aparecerá en el input.

Veamos el siguiente ejemplo, en el en vez de usar un mapa, lo haremos con una lista de objetos, así que lo primero que haremos es crear tres beans con la etiqueta "s:bean":


<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario1">
    <s:param name="nombre" value="%{'Alex1'}" />
    <s:param name="edad" value="%{1}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario2">
    <s:param name="nombre" value="%{'Alex2'}" />
    <s:param name="edad" value="%{2}" />
</s:bean>
<s:bean name="com.javatutoriales.struts2.tags.modelo.Usuario" var="usuario3">
    <s:param name="nombre" value="%{'Alex3'}" />
    <s:param name="edad" value="%{3}" />
</s:bean>


Y luego creamos una lista con estos objetos, la cual pasamos al atributo "list" de la etiqueta "s:combobox". También indicamos cuáles atributos servirán como valor y como etiqueta. Para indicar cuál será el valor usamos el atributo "listKey", y para la etiqueta el atributo "listValue", de la siguiente forma:


<s:label for="selectTest" value="Select: " />
<s:combobox list="{#usuario1, #usuario2, #usuario3}" id="selectTest" name="selectTest" listKey="edad" listValue="nombre" headerKey="-1" headerValue="Seleccione una opcion" emptyOption="true" />


Con la etiqueta anterior se genera el siguiente código:


<label for="comboboxTest">Combobox: </label>
<script type="text/javascript">
    function autoPopulate_comboboxTest(targetElement) {
      if (targetElement.options[targetElement.selectedIndex].value == '-1') {
         return;
      }
      if (targetElement.options[targetElement.selectedIndex].value == '') {
          return;
      }
      
      targetElement.form.elements['comboboxTest'].value=targetElement.options[targetElement.selectedIndex].value;
    }
</script>

<input type="text" name="comboboxTest" value="" id="comboboxTest"/><br />
<select onChange="autoPopulate_comboboxTest(this);">
    <option value="-1">Seleccione una opcion</option>
    <option value=""></option>
    <option value="1">Alex1</option>
    <option value="2">Alex2</option>
    <option value="3">Alex3</option>
</select>


Que se ve así en el navegador:



De igual forma, al seleccionar un elemento del select, su valor aparece en el campo de texto.

doubleselect

Esta es otra de las etiquetas muy útiles e interesantes. Se trata de dos listas ligadas, o dicho de otra forma, dos etiquetas select donde los valores de la segunda dependen del elemento que esté seleccionado en la primera.

Para generar el funcionamiento de este componente, Struts 2 generará un "pequeño" código en JavaScript que llenará los valores del segundo select.

Para los atributos de esta etiqueta podemos decir que ambos elementos tienen los mismos atributos, pero los atributos del segundo son antecedidos por la palabra "double" (desconozco porque habrán decidido llamarlo "double" y no algo más claro como "second" o algo así). Por ejemplo, para establecer el "id" del segundo select usamos el atributo "doubleId", para su atributo "onclic" usamos "doubleOnclick", etc.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" del primer select.
doubleListSi StringLos elementos que tendrá el segundo select. Por lo regular es una expresión que indica que elementos mostrar basado en lo que esté seleccionado en el primer select (quedará más claro con el ejemplo).
nameSi StringEl nombre que tendrá el primer select. En este caso es obligatorio porque se usará para el JavaScript que se genere.
doubleNameSi StringEl nombre que tendrá el segundo select. En este caso es obligatorio porque se usará para el JavaScript que se genere.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la primer lista (en caso de que trabajemos con un objeto que no sea un String).
doubleListKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la segunda lista (en caso de que trabajemos con un objeto que no sea un String).
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la primer lista (en caso de que trabajemos con un objeto que no sea un String).
doubleListTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la segunda lista (en caso de que trabajemos con un objeto que no sea un String).
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la primer lista (en caso de que trabajemos con un objeto que no sea un String).
doubleListValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la segunda lista (en caso de que trabajemos con un objeto que no sea un String).
emptyOptionNofalseBooleanIndica si insertar un elemento option vacío en el primer select.
doubleEmptyOptionNofalseBooleanIndica si insertar un elemento option vacío en el segundo select.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista del primer select.
doubleHeaderKeyNo StringEl valor que tendrá el primer elemento de la lista del segundo select.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista del primer select.
doubleHeaderValueNo StringLa etiqueta que tendrá el primer elemento de la lista del segundo select.
multipleNofalseBooleanIndica si debe permitirse seleccionar múltiples elementos de la primer lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
doubleMultipleNofalseBooleanIndica si debe permitirse seleccionar múltiples elementos de la segunda lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
valueNo StringEl valor del elemento o elementos que estarán pre-seleccionados en el primer select. Este valor debería venir desde un atributo de un Action.
doubleValueNo StringEl valor del elemento o elementos que estarán pre-seleccionados en el segundo select. Este valor debería venir desde un atributo de un Action.
listCssClass, listCssStyle, size, doubleSize


La información de qué elemento está seleccionado en la primer lista queda disponible en una variable llamada "top". Esta puede ser usada en la expresión de la segunda lista que indicará qué valores mostrar.

Comencemos con el ejemplo sencillo, colocando los valores de los elementos desde una lista.

Lo primero que debemos hacer es colocar nuestra etiqueta "s:doubleselect", aprovecharemos para darle un nombre y un id a ambos componentes:


<s:doubleselect name="primerLista"        id="doubleselecList1Test"
                   doubleName="segundaLista" doubleId="doubleselecList2Test" />


Ahora agregaremos los elementos de la primer lista. En esta colocaremos dos categorías, la primera será especialidad y la segunda el lenguaje de programación.


<s:doubleselect name="primerLista"        id="doubleselecList1Test"
                   doubleName="segundaLista" doubleId="doubleselecList2Test" 
                   list="{'especialidad','lenguaje'}" />


Y ahora viene la parte interesante, agregaremos una expresión para indicar qué elementos estarán en la segunda lista y en qué caso se mostrará qué. Digamos que cuando esté seleccionado el valor "especialidad" queremos mostrar los valores "comunicación", "bases de datos" y "criptografía", y que al seleccionar el valor "lenguaje" queremos mostrar los valores "java", "php" y ".net". La expresión queda de la siguiente forma (recuerden que se usa la variable "top" para saber el valor que está seleccionado en la primer lista):


top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}


Y la etiqueta queda de la siguiente forma:


<s:doubleselect name="primerLista"   id="doubleselecList1Test"
                            doubleName="segundaLista" doubleId="doubleselecList2Test" 
                            list="{'especialidad','lenguaje'}"
                            doubleList="top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}" />


La etiqueta anterior genera el siguiente código:


<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
    <option value="especialidad">especialidad</option>
    <option value="lenguaje">lenguaje</option>
</select>

<br/>
<select name="segundaLista" id="doubleselecList2Test">
</select>
<script type="text/javascript">
    var doubleselecList1TestGroup = new Array(2 + 0);
    for (i = 0; i < (2 + 0); i++)
        doubleselecList1TestGroup[i] = new Array();

    doubleselecList1TestGroup[0][0] = new Option("servidores", "servidores");

    doubleselecList1TestGroup[0][1] = new Option("bases de datos", "bases de datos");

    doubleselecList1TestGroup[0][2] = new Option("programacion", "programacion");

    doubleselecList1TestGroup[1][0] = new Option("java", "java");

    doubleselecList1TestGroup[1][1] = new Option("php", "php");

    doubleselecList1TestGroup[1][2] = new Option(".net", ".net");


    var doubleselecList1TestTemp = document.form.doubleselecList2Test;
    doubleselecList1TestRedirect(0);
    function doubleselecList1TestRedirect(x) {
        var selected = false;
        for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
            doubleselecList1TestTemp.remove(m);
        }

        for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
            doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
        }

        if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
            doubleselecList1TestTemp.options[0].selected = true;
        }
    }
</script>


En este caso podemos ver mucho código generado, este básicamente lo que hace es crear un arreglo bidimensional en el que coloca los elementos que estarán en la segunda lista, y luego el código para cambiar los elementos de la segunda lista.

Esto se ve así en el navegador:



Ahora ¿qué pasa si intentamos llenar la lista en base a un mapa, recordando que la finalidad de esto es que podamos tener valores diferentes para la etiqueta (lo que verá el usuario) y el código de la selección (lo que enviaremos al servidor)? Basándonos en los ejemplos anteriores, podríamos pensar que la forma de generar esta etiqueta sería la siguiente:


<s:doubleselect name="primerLista"   id="doubleselecList1Test"
                        doubleName="segundaLista" doubleId="doubleselecList2Test" 
                        list="#{'especialidad':'Especialidad', 'lenguaje':'Lenguaje de Programacion'}" 
                        doubleList="top == 'especialidad' ? {'comunicacion', 'bases de datos','criptografia'} : {'java', 'php', '.net'}" />


Sin embargo esta etiqueta genera el siguiente código:


<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
    <option value="especialidad">Especialidad</option>
    <option value="lenguaje">Lenguaje de Programacion</option>
</select>
<br/>
<select name="segundaLista" id="doubleselecList2Test" >
</select>
<script type="text/javascript">
    var doubleselecList1TestGroup = new Array(2 + 0);
    for (i = 0; i < (2 + 0); i++)
        doubleselecList1TestGroup[i] = new Array();


    var doubleselecList1TestTemp = document.form.doubleselecList2Test;
    doubleselecList1TestRedirect(0);
    function doubleselecList1TestRedirect(x) {
        var selected = false;
        for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
            doubleselecList1TestTemp.remove(m);
        }

        for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
            doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
        }

        if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
            doubleselecList1TestTemp.options[0].selected = true;
        }
    }
</script>


Si se fijan bien, el código anterior no genera el arreglo bidimensional de elementos parael segundo arreglo, por lo tanto este está vacío todo el tiempo:



Entonces ¿cómo hacemos para usar un mapa? En realidad… no es posible llenar estas listas usando un mapa, el API de Struts 2 en este caso no lo permite.

Lo que podemos hacer es simplificar la horrible expresión del ejemplo anterior. ¿Para qué? Imaginen que en el ejemplo anterior, en vez de tener dos clasificaciones o elementos en la primer lista, tuviéramos tres. Tendríamos que agregar más condiciones en la expresión para la segunda lista. Si esto ya sería in-manejable (dependiendo del número de elementos para cada clasificación), imaginen que fueran cuatro o cinco clasificaciones. No solo sería ilegible, sino que sería propenso a errores.

¿Qué podemos hacer en este caso? Pues usar un mapa ^_^! (sé que acabo de decir que no se puede, pero es un mapa un poco distinto a los anteriores). En vez de usar un mapa en cuya llave y valor son ambos cadenas (una para usar como etiqueta y otra como valor), necesitamos uno en donde la llave sea una cadena, que se mostrará en la primer lista, y el valor será una lista de cadenas con los elementos que se mostrarán en la segunda lista.

Para hacer esto en OGNL podemos hacerlo de la siguiente forma:


#{'Especialidad': {'Comunicación', 'Bases de Datos', 'Criptografía'},
    'Lenguajes': {'Java', 'PHP', '.Net'},
    'Conocimientos': {'Básico', 'Intermedio', 'Avanzado'}
}


Para poder hacer una selección más simple, podemos poner este mapa de valores en una variable, que llamaremos en este caso "mapaValores", usando la etiqueta "s:set", de esta forma:


<s:set var="mapaValores"  value="#{'Especialidad': {'Comunicación', 'Bases de Datos', 'Criptografía'},
                            'Lenguajes': {'Java', 'PHP', '.Net'},
                            'Conocimientos': {'Básico', 'Intermedio', 'Avanzado'}}" />


Ahora podemos usar esta variable en la etiqueta "s:doubleselect". Primero colocamos la etiqueta dando un nombre y un id a cada una de las listas:


<s:doubleselect name="primerLista"        id="doubleselecList1Test"
                   doubleName="segundaLista" doubleId="doubleselecList2Test"  />


Para colocar los valores de la primer lista (los cuales recuerden que deben ser una fuente de datos iterable), obtenemos un "set" con las llaves de la lista, de la siguiente forma:


<s:doubleselect name="primerLista"   id="doubleselecList1Test"
                            doubleName="segundaLista" doubleId="doubleselecList2Test" 
                            list="#mapaValores.keySet()" />


Y, finalmente, en la expresión para mostrar los valores de la segunda lista, es tan simple como indicar que los elementos estarán en la lista cuya llave estamos seleccionando actualmente, de la siguiente forma:


<s:doubleselect name="primerLista"   id="doubleselecList1Test"
                            doubleName="segundaLista" doubleId="doubleselecList2Test" 
                            list="#mapaValores.keySet()"  
                            doubleList="#mapaValores[top]" />


La etiqueta anterior genera el siguiente código:


<select name="primerLista" id="doubleselecList1Test" onchange="doubleselecList1TestRedirect(this.selectedIndex)">
    <option value="Especialidad">Especialidad</option>
    <option value="Lenguajes">Lenguajes</option>
    <option value="Conocimientos">Conocimientos</option>
</select>

<br/>
<select name="segundaLista" id="doubleselecList2Test" >
</select>
<script type="text/javascript">
    var doubleselecList1TestGroup = new Array(3 + 0);
    for (i = 0; i < (3 + 0); i++)
        doubleselecList1TestGroup[i] = new Array();

    doubleselecList1TestGroup[0][0] = new Option("Comunicación", "Comunicación");
    doubleselecList1TestGroup[0][1] = new Option("Bases de Datos", "Bases de Datos");
    doubleselecList1TestGroup[0][2] = new Option("Criptografía", "Criptografía");
    doubleselecList1TestGroup[1][0] = new Option("Java", "Java");
    doubleselecList1TestGroup[1][1] = new Option("PHP", "PHP");
    doubleselecList1TestGroup[1][2] = new Option(".Net", ".Net");
    doubleselecList1TestGroup[2][0] = new Option("Básico", "Básico");
    doubleselecList1TestGroup[2][1] = new Option("Intermedio", "Intermedio");
    doubleselecList1TestGroup[2][2] = new Option("Avanzado", "Avanzado");

    var doubleselecList1TestTemp = document.formulario.doubleselecList2Test;
    doubleselecList1TestRedirect(0);
    function doubleselecList1TestRedirect(x) {
        var selected = false;
        for (m = doubleselecList1TestTemp.options.length - 1; m >= 0; m--) {
            doubleselecList1TestTemp.remove(m);
        }

        for (i = 0; i < doubleselecList1TestGroup[x].length; i++) {
            doubleselecList1TestTemp.options[i] = new Option(doubleselecList1TestGroup[x][i].text, doubleselecList1TestGroup[x][i].value);
        }

        if ((doubleselecList1TestTemp.options.length > 0) && (! selected)) {
            doubleselecList1TestTemp.options[0].selected = true;
        }
    }
</script>


Que se ve así en el navegador:



De esta manera tenemos una etiqueta más mantenible y que puede manejar más categorías y elementos de forma sencilla.

Si quisiéramos regresar esta lista desde el atributo de un bean, este tendría que ser un mapa cuya llave sea una cadena, en su valor una lista de cadenas, de esta forma:


Map<String, List<String>>


El resto de código funciona de la misma forma.

updownselect

Coloca un componente "select", con botones para mover sus elementos arriba y abajo dentro de la lista. Cuando el formulario sea enviado, los elementos seleccionados de la lista serán enviados en el orden en el que se encuentren (de arriba abajo).

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" de la etiqueta.
valueNo StringEl valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un Action.
allowMoveDownNotrueBooleanIndica si se mostrará el botón para bajar los elementos en la lista.
allowMoveUpNotrueBooleanIndica si se mostrará el botón para subir los elementos de la lista.
allowSelectAllNotrueBooleanIndica si se mostrará el botón para seleccionar todos los elementos de la lista.
emptyOptionNofalseBooleanIndica si se debe agregar una opción vacía después del encabezado (header) de la lista.
moveDownLabelNovStringEl texto que se mostrará en la etiqueta para bajar un elemento de la lista.
moveUpLabelNo^StringEl texto que se mostrará en la etiqueta para subir un elemento de la lista.
selectAllLabelNo*StringEl texto que se mostrará en la etiqueta para seleccionar todos los elementos de la lista.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
multipleNotrueBooleanIndica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size


Para el primer ejemplo pondremos los valores que se mostrarán en el select, haciendo uso de un mapa.

Si colocamos la siguiente etiqueta:


<s:updownselect
            list="#{'java':'Java', 'php':'PHP', 'puntoNet':'.Net'}"
            name="updownselectTest" id="updownselectTest"
            headerKey="-1"
            headerValue="--- Selecciona los elementos en el orden deseado ---"
            emptyOption="true" />


Obtendremos el siguiente código:


<script type="text/javascript" src="/tags/struts/optiontransferselect.js"></script>
<table>
    <tr>
        <td>
            <select name="updownselectTest" size="5" id="updownselectTest" multiple="multiple">
                <option value="-1">--- Selecciona los elementos en el orden deseado ---</option>
                <option value=""></option>
                <option value="java">Java</option>
                <option value="php">PHP</option>
                <option value="puntoNet">.Net</option>
            </select>
            <input type="hidden" id="__multiselect_updownselectTest" name="__multiselect_updownselectTest" value="" />
        </td>
    </tr>
    <tr>
        <td>
             <input type="button" value="^" onclick="moveOptionUp(document.getElementById('updownselectTest'), 'key', '-1');" /> 
             <input type="button" value="v" onclick="moveOptionDown(document.getElementById('updownselectTest'), 'key', '-1');" /> 
             <input type="button" value="*" onclick="selectAllOptionsExceptSome(document.getElementById('updownselectTest'), 'key', '-1');" /> 
        </td>
    </tr>
</table>

<script type="text/javascript">
 var containingForm = document.getElementById("formulario");
 StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
        var updownselectObj = document.getElementById("updownselectTest");
        selectAllOptionsExceptSome(updownselectObj, "key", "-1");
      }, true);
</script>


Que se ve así en el navegador:



Como a esta altura ya sabemos cómo hacer lo mismo, tomando los elementos del select a partir de una lista, tanto de valores fijos como de objetos, no veremos otro ejemplo de esta etiqueta.

inputtransferselect

Esta es otra etiqueta que es una combinación de dos componentes. El primero es una etiqueta select que permite seleccionar múltiples valores, además de que permite ordenar los elementos de la lista y eliminar uno o varios elementos.

El segundo componente es un campo de entrada de texto que permite agregar valores a la primer lista.

Antes de hacer un submit del formulario, todos los elementos del componente "select" serán seleccionados y enviados al servidor.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" de la etiqueta.
valueNo StringEl valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un Action.
allowRemoveAllNotrueBooleanIndica si se mostrará el botón para eliminar todos los elementos de la lista.
allowUpDownNotrueBooleanIndica si se mostraran los botones para ordenar los elementos.
addLabelNo->StringLa etiqueta usada para el botón de agregar elementos.
upLabelNo^StringLa etiqueta usada para el botón de subir elemento.
downLabelNovStringLa etiqueta usada para el botón de bajar elemento.
removeLabelNo<-StringLa etiqueta utilizada para el botón de eliminar un elemento.
removeAllLabelNo<<-StringLa etiqueta utilizada para el botón de eliminar todos los elementos.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
multipleNotrueBooleanIndica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size


Veremos sólo dos ejemplos. En el primero llenaremos la lista de elementos con una lista, y dejaremos el resto de los valores por default. Lo primero que haremos es colocar la etiqueta "s:inputtransferselect", a la cual pondremos un nombre y un id:


<s:inputtransferselect
            name="inputselectTest"
            id="inputselectTest" />


Ahora colocaremos, en el atributo "list", una lista con las opciones iniciales que estarán en el select, en nuestro caso podremos los sistemas operativos que utilizamos:


<s:inputtransferselect
            name="inputselectTest"
            id="inputselectTest" 
            list="{'Linux', 'Mac OS', 'Windows'}"/>


Esto es todo. La etiqueta anterior genera el siguiente código:


<script type="text/javascript" src="/tags/struts/inputtransferselect.js"></script>
<table border="0">
    <tr>
        <td> 
            <input type="text" name="inputselectTest_input" id="inputselectTest_input"/>
        </td>
        <td valign="middle" align="center">
            <input type="button" value="->" onclick="addOption(document.getElementById('inputselectTest_input'), document.getElementById('inputselectTest'))" />
            <br />
            <br />
            <input type="button" value="<-" onclick="removeOptions(document.getElementById('inputselectTest'))" />
            <br />
            <br />
            <input type="button" value="<<--" onclick="removeAllOptions(document.getElementById('inputselectTest'))" />
            <br />
            <br />
        </td>
        <td>
            <select name="inputselectTest" size="5" id="inputselectTest" multiple="multiple">
                <option value="Linux">Linux</option>
                <option value="Mac OS">Mac OS</option>
                <option value="Windows">Windows</option>
            </select>
            <input type="hidden" id="__multiselect_inputselectTest" name="__multiselect_inputselectTest" value="" />
            <input type="button" onclick="moveOptionDown(document.getElementById('inputselectTest'), 'key', '');" value="v" />
            <input type="button" onclick="moveOptionUp(document.getElementById('inputselectTest'), 'key', '');" value="^" />
        </td>
    </tr>
</table>


Como podemos ver, a pesar de nuestros mejores esfuerzos, la etiqueta ordena todos los elementos en una tabla.

El código anterior se ve así en el navegador:



Pueden ingresar un nuevo nombre en el campo de texto y luego presionar el botón de agregar a la lista (->) y este aparecerá en la parte inferior del select.

Veamos el segundo ejemplo, en el que llenaremos la lista usando un mapa (creo que ya quedó claro cómo hacerlo con una lista de objetos).

En este ejemplo modificaremos los textos de los botones para hacerlos más claros.

Si colocamos la siguiente etiqueta:


<s:inputtransferselect name="inputselectTest" id="inputselectTest"
                addLabel="Agregar" removeLabel="Eliminar" removeAllLabel="Eliminar todos"
                upLabel="Subir" downLabel="Bajar"
                list="#{'1':'Linux', '2':'Mac OS', '3':'Windows'}"/>


Obtendremos el siguiente código:


<script type="text/javascript" src="/tags/struts/inputtransferselect.js"></script>
<table border="0">
    <tr>
        <td>
            <input type="text" name="inputselectTest_input" id="inputselectTest_input"/>
        </td>
        <td valign="middle" align="center">
            <input type="button" value="Agregar" onclick="addOption(document.getElementById('inputselectTest_input'), document.getElementById('inputselectTest'))" />
            <br />
            <br />
            <input type="button" value="Eliminar" onclick="removeOptions(document.getElementById('inputselectTest'))" />
            <br />
            <br />
            <input type="button" value="Eliminar todos" onclick="removeAllOptions(document.getElementById('inputselectTest'))" />
            <br />
            <br />
        </td>
        <td>
            <select name="inputselectTest" size="5" id="inputselectTest" multiple="multiple">
                <option value="1">Linux</option>
                <option value="2">Mac OS</option>
                <option value="3">Windows</option>
            </select>
            <input type="hidden" id="__multiselect_inputselectTest" name="__multiselect_inputselectTest" value="" />
            <input type="button" onclick="moveOptionDown(document.getElementById('inputselectTest'), 'key', '');" value="Bajar" />
            <input type="button" onclick="moveOptionUp(document.getElementById('inputselectTest'), 'key', '');" value="Subir" />
        </td>
    </tr>
</table>


<script type="text/javascript">
  var containingForm = document.getElementById("formulario");
  StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
      var selectObj = document.getElementById("inputselectTest");
       selectAllOptionsExceptSome(selectObj, "key", "");
      }, true);
</script>


Que se ve así en el navegador:



optiontransferselect

Esta etiqueta coloca dos etiquetas "select", que permiten mover los elementos de cada una de las listas entre ellas.

Antes de hacer un submit del formulario, todos los elementos del segundo "select" serán seleccionados y enviados al servidor.

Para los atributos de esta etiqueta podemos decir que ambos componentes tienen los mismos atributos, pero los atributos del segundo son antecedidos por la palabra "double" (desconozco porque habrán decidido llamarlo "double" y no algo más claro como "second" o algo así). Por ejemplo, para establecer el "id" del segundo select usamos el atributo "doubleId", para su atributo "onclic" usamos "doubleOnclick", etc. Como la lista crecería mucho si colocamos los atributos de ambas listas, sólo colocaremos los más importantes.

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
listSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" del select de la izquierda.
doubleListSi StringLa fuente de datos iterable (un iterador, una lista o un mapa) que se usará para llenar los elementos "option" del select de la derecha.
valueNo StringEl valor del elemento o elementos que estarán pre-seleccionados. Este valor debería venir desde un atributo de un Action.
allowAddAllToLeftNotrueBooleanIndica si se mostrará el botón para mover todos los elementos a la lista de la izquierda.
allowAddAllToRightNotrueBooleanIndica si se mostrará el botón para mover todos los elementos a la lista de la derecha.
allowAddToLeftNotrueBooleanIndica si se mostrará el botón para mover un elemento a la lista de la izquierda.
allowAddToRightNotrueBooleanIndica si se mostrará el botón para mover un elemento a la lista de la derecha.
allowSelectAllNotrueBooleanIndica si se mostrará el botón para seleccionar todos los elementos de las dos listas.
allowUpDownOnLeftNotrueBooleanIndica si se mostrará el botón para mover los elementos de arriba a abajo en la lista de la izquierda.
allowUpDownOnRightNotrueBooleanIndica si se mostrará el botón para mover los elementos de arriba a abajo en la lista de la derecha.
addAllToLeftLabelNo<<-StringLa etiqueta utilizada para el botón de mover todos los elementos a la lista de la izquierda.
addAllToRightLabelNo->>StringLa etiqueta utilizada para el botón de mover todos los elementos a la lista de la derecha.
addToLeftLabelNo<-StringLa etiqueta utilizada para el botón de mover un elemento a la lista de la izquierda.
addToRightLabelNo->StringLa etiqueta utilizada para el botón de mover un elemento a la lista de la derecha.
leftDownLabelNovStringLa etiqueta utilizada para el botón de bajar un elemento en la lista de la izquierda.
leftUpLabelNo^StringLa etiqueta utilizada para el botón de subir un elemento en la lista de la izquierda.
rightDownLabelNovStringLa etiqueta utilizada para el botón de bajar un elemento en la lista de la derecha.
rightUpLabelNo^StringLa etiqueta utilizada para el botón de subir un elemento en la lista de la derecha.
selectAllLabelNo<*>StringLa etiqueta utilizada para el botón de seleccionar todos los elementos de ambas listas.
addAllToLeftOnclickNo StringJavaScript que se ejecutará después de que se ha presionado el botón para enviar todos los elementos a la lista de la izquierda.
addAllToRightOnclickNo StringJavaScript que se ejecutará después de que se ha presionado el botón para enviar todos los elementos a la lista de la derecha.
addToLeftOnclickNo StringJavaScript que se ejecutará después de que se ha presionado el botón para enviar un elemento a la lista de la izquierda.
addToRightOnclickNo StringJavaScript que se ejecutará después que se ha presionado el botón para enviar un elemento a la lista de la derecha.
upDownOnLeftOnclickNo StringJavaScript que se ejecutará después que se ha presionado el botón para subir/bajar un elemento en la lista de la izquierda.
upDownOnRightOnclickNo StringJavaScript que se ejecutará después que se ha presionado el botón para subir/bajar un elemento en la lista de la derecha.
selectAllOnclickNo StringJavaScript que se ejecutará después de que se hace clic en el botón para seleccionar todos los elementos de las listas.
headerKeyNo StringEl valor que tendrá el primer elemento de la lista.
headerValueNo StringLa etiqueta que tendrá el primer elemento de la lista.
listKeyNo StringLa propiedad del objeto que se usará para mostrar el valor que tendrá cada elemento de la lista.
listTitleNo StringLa propiedad del objeto que se usará para mostrar el título de cada elemento de la lista.
listValueNo StringLa propiedad del objeto que se usará para mostrar la etiqueta de cada elemento de la lista.
emptyOptionNofalseBooleanIndica si se debe agregar una opción vacía después del encabezado (header) de la lista de la izquierda.
multipleNotrueBooleanIndica si debe permitirse seleccionar múltiples elementos de la lista. Si queremos pre-seleccionar varios elementos, debemos pasar un arreglo o una lista al atributo "value" de la etiqueta.
buttonCssClass, buttonCssStyle, listCssClass, listCssStyle, size, doubleListCssClass, doubleListCssStyle, doubleSize


Como ya hemos entendido la mecánica de cómo llenar los elementos de la lista, sólo veremos un ejemplo en el que usaremos un mapa.

Si colocamos la siguiente etiqueta:


<s:optiontransferselect
            name="optiontransferselect1Test" id="optiontransferselect1Test"
            doubleName="optiontransferselect2Test" doubleId="optiontransferselect2Test"
            list="#{'1':'PHP', '2':'.Net', '3':'Phyton'}"
            doubleList="#{'4':'Java', '5':'Scala', '6':'Groovy'}" />


Obtendremos el siguiente código:


<script type="text/javascript" src="/tags/struts/optiontransferselect.js"></script>
<table border="0">
    <tr>
        <td>
            <select name="optiontransferselect1Test" size="15" id="optiontransferselect1Test" multiple="multiple">
                <option value="1">PHP</option>
                <option value="2">.Net</option>
                <option value="3">Phyton</option>
            </select>
            <input type="hidden" id="__multiselect_optiontransferselect1Test" name="__multiselect_optiontransferselect1Test" value="" />
            <input type="button" onclick="moveOptionDown(document.getElementById('optiontransferselect1Test'), 'key', '');" value="v" />
            <input type="button" onclick="moveOptionUp(document.getElementById('optiontransferselect1Test'), 'key', '');" value="^" />
        </td>
        <td valign="middle" align="center">
            <input type="button" value="<-" onclick="moveSelectedOptions(document.getElementById('optiontransferselect2Test'), document.getElementById('optiontransferselect1Test'), false, '');" /><br /><br />
            <input type="button" value="->" onclick="moveSelectedOptions(document.getElementById('optiontransferselect1Test'), document.getElementById('optiontransferselect2Test'), false, '');" /><br /><br />
            <input type="button" value="<<--" onclick="moveAllOptions(document.getElementById('optiontransferselect2Test'), document.getElementById('optiontransferselect1Test'), false, '');" /><br /><br />
            <input type="button" value="-->>" onclick="moveAllOptions(document.getElementById('optiontransferselect1Test'), document.getElementById('optiontransferselect2Test'), false, '');" /><br /><br />
            <input type="button" value="<*>" onclick="selectAllOptions(document.getElementById('optiontransferselect1Test'));selectAllOptions(document.getElementById('optiontransferselect2Test'));" /><br /><br />
        </td>
        <td>
            <select name="optiontransferselect2Test" size="15" multiple="multiple" id="optiontransferselect2Test" >
               <option value="4">Java</option>
               <option value="5">Scala</option>
               <option value="6">Groovy</option>
            </select>
            <input type="hidden" id="__multiselect_optiontransferselect2Test" name="__multiselect_optiontransferselect2Test" value="" />
            <input type="button" onclick="moveOptionDown(document.getElementById('optiontransferselect2Test'), 'key', '');" value="v" />
            <input type="button" onclick="moveOptionUp(document.getElementById('optiontransferselect2Test'), 'key', '');" value="^" />
        </td>
    </tr>
</table>


<script type="text/javascript">
    var containingForm = document.getElementById("formulario");
    StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
        var selectObj = document.getElementById("optiontransferselect1Test");
        selectAllOptionsExceptSome(selectObj, "key", "");
    }, true);
   
    var containingForm = document.getElementById("formulario");
    StrutsUtils.addEventListener(containingForm, "submit", function(evt) {
        var selectObj = document.getElementById("optiontransferselect2Test");
        selectAllOptionsExceptSome(selectObj, "key", "");
    }, true);
</script>


Que se ve así en el navegador:



Ahora que ya hemos visto los componentes, veamos cómo limpiar y enviar los datos del formulario.

reset

Muestra un botón que permite limpiar todos los datos que se hayan colocado en el formulario.

Este componente puede mostrarse de dos formas, la primera es como un componente "input" (<input type="reset"...>), y la segunda como un botón (<button type="reset"...>).

Los atributos de esta etiqueta se muestran en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
typeNoinputStringIndica la forma en la que se mostrará e elemento. Los valores posibles son "input", "button" e "image"
valueNo StringEl texto que mostrará el botón.
action, method, src


Veamos un ejemplo en el que colocaremos esta etiqueta como un botón.

Si colocamos la siguiente etiqueta:


<s:reset type="button" value="Limpiar" />


Obtendremos el siguiente código:


<button type="reset" value="Limpiar">Limpiar</button>


Que se ve así en el navegador:



Si tuviéramos varios campos de formulario, con información, y presionáramos el botón "Limpiar", veríamos cómo estos campos regresan a sus valores iniciales. Por ahora sólo confíen en mi ^_^.

submit

Esta etiqueta genera un botón que permite enviar el formulario al servidor para ser evaluado.

Al igual que con la etiqueta anterior, este componente puede mostrarse de distintas formas:

  • input: muestra la etiqueta como <input type="submit"...>
  • image: muestra la etiqueta como <input type="image"...>
  • button: muestra la etiqueta como <button type="submit"...>


Los atributos de esta etiqueta se muestran en la siguiente tabla:



NombreObligatorioValor DefaultTipoDescripción
typeNoinputStringIndica la forma en la que se mostrará e elemento. Los valores posibles son "input", "button" e "image"
valueNo StringEl texto que mostrará el botón.
action, method, src


El ejemplo en este caso también será muy sencillo. Si colocamos la etiqueta de esta forma:


<s:submit type="button" value="Enviar" />


Generará el siguiente código:


<button type="submit" id="formulario_0" value="Enviar">Enviar</button>


Que se ve así en el navegador:



Esta etiqueta la usaremos en cada uno de los formularios que usemos en nuestras aplicaciones (siempre y cuando queramos que la información sea enviada a algún lado).

token

Esta etiqueta, además de ser la última de esta categoría, evita que un formulario sea enviado dos veces (por eso de los usuarios que siempre dan doble clic sobre cada botón o que piensan que entre más clics hagan en el botón de enviar, más rápido se atenderá su petición^_^!).

Esta etiqueta coloca un elemento oculto que contiene un tóken único. El servidor, al recibir este valor, verifica si ya ha sido enviado o no. En caso de que ya haya sido enviado, simplemente ignora las peticiones subsecuentes.

Esta etiqueta no tiene atributos.

Su forma de uso es muy simple, sólo la colocamos en un formulario, de la siguiente forma:


<s:token  />


Y con esto obtenemos, más o menos, el siguiente código:


<input type="hidden" name="token" value="Q6BZKAOPZ2WI2VBNHHXCXBNK7F1XS1I2" />


Este tóken varía y es el que nos asegura que el formulario no haya sido enviado más de una vez.

Con esto terminamos la categoría de etiquetas de formulario y podemos pasar a la última categoría del tutorial.

Etiquetas UI de No Formulario

Estas etiquetas se usan básicamente para mostrar información al usuario sobre lo ocurrido en un Action. Hay básicamente dos tipos de mensajes, unos que llamaremos mensajes informativos, o simplemente mensajes; y los que llamaremos mensajes de error.

Existen cinco etiquetas en esta categoría, aunque sólo tres nos son de utilidad:



En el tutorial sólo hablaremos de las tres primeras.

Para ver el funcionamiento de las mismas, necesitamos crear una clase Action que se encargará de establecer los mensajes.

Crearemos, en el paquete "actions", una nueva clase llamada "MensajesAction". Esta clase extenderá de "ActionSupport". Esta clase sobre-escribirá el método "execute" y por ahora sólo regresará el valor "SUCCESS":


public class MensajesAction extends ActionSupport {

    @Override
    public String execute() throws Exception {
        return SUCCESS;
    }   
}


Decoraremos esta clase con alguna anotaciones de Struts 2, para indicar que se trata de un Action que se encontrará en el namespace "/", que tendrá el nombre "mensajes" y que en el caso de un resultado exitoso el flujo de la aplicación ira a la página "mensajes.jsp":


@Namespace(value = "/")
@Action(value = "mensajes", results = {
@Result(name = "success", location = "/mensajes.jsp")})
public class MensajesAction extends ActionSupport {

    @Override
    public String execute() throws Exception {
        return SUCCESS;
    }   
}


También crearemos la página "mensajes.jsp" que será donde colocaremos las etiquetas para ver su funcionamiento:



En esta página indicamos que usaremos las etiquetas de Struts 2 usando la directiva "@taglib":


<%@taglib uri="/struts-tags" prefix="s" %>


*Nota: Las etiquetas de esta categoría comparten los mismos atributos generales que las de la categoría anterior, así que tampoco los indicaremos.
Ahora si, comencemos a ver el funcionamiento de las etiquetas.

actionmessage

Esta etiqueta muestra un mensaje proveniente de un Action.

Como se imaginarán, por el texto anterior, el mensaje es establecido desde una clase Action y este se muestra en la página del resultado de la ejecución del mismo.

Esta etiqueta no tiene atributos.

Para el ejemplo, iremos primero a la clase "MensajesAction". En su método execute pondremos un mensaje que será fijo (aunque este debería ser generado dinámicamente dependiendo de la ejecución de la aplicación). Es importante decir que este mensaje debe ser para informar cualquier cosa que no sea un error del Action (para esto usaremos otra etiqueta). Puede ser un mensaje informativo, una pregunta, o cualquier otra cosa que necesiten avisar al usuario.

Para colocar estos textos, la clase ActionSupport proporciona unos métodos de utilidad para que esto sea fácil y sencillo; el método "addActionMessage", el cual, como su nombre lo indica, añade un nuevo mensaje a los ya existentes, o sea que podemos tener más de un mensaje a la vez.

En mi caso simplemente pondré un texto indicando que ese es un texto informativo (recuerden que este código debe estar dentro del método "execute").


addActionMessage("Hola, este es un texto informativo");


Ahora vayamos a la página "mensajes.jsp". Es esta página agregamos la etiqueta de la siguiente forma:


<s:actionmessage />


Eso es todo.

Ahora, si ejecutamos nuevamente nuestra aplicación y entramos a la siguiente dirección:


http://localhost:8080/tags/mensajes


Debemos ver el siguiente mensaje:



Podemos ver que junto a este mensaje aparece una viñeta. Eso quiere decir que estos mensajes se muestran en una lista. Para comprobar esto agregaremos un segundo mensaje:


addActionMessage("Este es otro texto informativo");


Con lo que veremos aparecer un segundo mensaje en la lista:



Si vemos el código fuente que se genera, podremos comprobar que estos mensajes se colocan en una lista. También veremos que esta lista tiene la clase de CSS "ActionMessage", la cual podemos usar para personalizar la forma en la que se muestran los mensajes.


<ul class="ActionMessage">
    <li><span>Hola, este es un texto informativo</span></li>
    <li><span>Este es otro texto informativo</span></li>
</ul>


Ahora veremos la siguiente etiqueta de esta categoría.

actionerror

Esta etiqueta, de forma similar a la anterior, se utiliza para mostrar mensajes al usuario. Estos mensajes deben indicar que ocurrió algún error en la aplicación, siempre y cuando este error no esté relacionado con algún campo de un formulario, ya que para este caso existe una etiqueta especial, como puede ser un error al llamar a otro servicio, o al procesar los datos, etc.

Esta etiqueta no tiene atributos.

Para este ejemplo, nuevamente regresaremos a la clase "MensajesAction". Aquí utilizaremos nuevamente un método de utilidad de la clase base "ActionSupport" para agregar estos mensajes, el método "addActionError". Con este método agregaremos un mensaje de error de la siguiente forma:


addActionError("Ha ocurrido un error al procesar su solicitud");


Ahora, en la página "mensajes.jsp" pondremos la etiqueta "s:actionerror" de la siguiente forma:


<s:actionerror />


Si ejecutamos nuevamente la aplicación veremos aparecer el siguiente mensaje:



Al igual que con la etiqueta anterior, esta también se encuentra en una lista. Esta lista tiene la clase CSS "errorMessage".

Si vemos el código fuente generado por la etiqueta anterior podremos ver más o menos lo siguiente:


<ul class="errorMessage">
    <li><span>Ha ocurrido un error al procesar su solicitud</span></li> 
</ul>


Ahora veremos la última etiqueta de esta categoría, y de este tutorial.

fielderror

Esta etiqueta muestra algún error relacionado con un campo de un formulario.

Esta etiqueta sólo tiene un atributo, que se muestra en la siguiente tabla:

NombreObligatorioValor DefaultTipoDescripción
fieldnameNo StringIndica el nombre del campo que se mostrará (en casa de que sólo se quieran mostrar los mensajes de un campo).


Si colocamos esta etiqueta sin ningún atributo o cuerpo, mostrará los mensajes relacionados con todos los campos de los formularios de la página. También podemos indicar que muestre los mensajes relacionados con un solo campo, usando el atributo "fieldname", o pasándolos como parámetros en el cuerpo de la etiqueta.

Para este ejemplo debemos crear un formulario en la página "mensajes.jsp". En él colocaremos 3 campos, un "nombre", un "correo", y un "password" (con sus correspondientes etiquetas). Indicaremos que el Action que procesará este formulario será el Action llamado "mensajes", de la siguiente forma:


<s:form action="mensajes">
    <s:label value="Nombre: " for="nombre" />
    <s:textfield name="nombre" id="nombre" />
            
    <s:label value="Password: " for="password" />
    <s:password name="password" id="password" />
            
    <s:label value="Correo:" for="correo" />
    <s:textfield name="correo" id="correo" />
            
    <s:submit value="Enviar" />
</s:form>


En la clase "MensajesAction", agregamos tres Strings, una para cada uno de los campos anteriores, junto con sus correspondientes setters y getters:


private String nombre;
private String password;
private String correo;

public String getNombre() {
    return nombre;
}

public void setNombre(String nombre) {
    this.nombre = nombre;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getCorreo() {
    return correo;
}
public void setCorreo(String correo) {
    this.correo = correo;
}


Ahora, si queremos que exista la posibilidad de que haya errores al establecer los valores de los campos del formulario, debemos colocar algunas validaciones.

Del tutorial de trabajo con formularios, sabemos que la forma más sencilla de agregar estas validaciones es usando anotaciones, y que estas deben ser colocadas en los setters de los atributos.

Validaremos que los tres campos sean proporcionados y, adicionalmente, que el campo "correo" tenga el formato de un correo electrónico:


@RequiredStringValidator(message = "El nombre es un campo obligatorio")
public void setNombre(String nombre) {
    this.nombre = nombre;
}

@RequiredStringValidator (message = "El password es un campo obligatorio")
public void setPassword(String password) {
    this.password = password;
}

@RequiredStringValidator(message = "El correo electronico es un campo obligatorio")
@EmailValidator(message = "El campo no contiene un correo electronico valido")
public void setCorreo(String correo) {
    this.correo = correo;
}


También debemos agregar un @Result más a la configuración del Action, indicando que, en caso de algún error en la entrada de algún campo, debemos regresar a la página "/mensajes.jsp":


@Result(name = "input", location = "/mensajes.jsp")


La configuración completa del Action queda de la siguiente forma:


@Namespace(value = "/")
@Action(value = "mensajes", results = {
    @Result(name = "success", location = "/mensajes.jsp"),
    @Result(name = "input", location = "/mensajes.jsp")
})
public class MensajesAction extends ActionSupport {


Ahora regresemos a "mensajes.jsp". Aquí pondremos, antes del formulario, la etiqueta "s:fielderror":


<s:fielderror />


Ahora, ejecutamos nuevamente nuestra aplicación, y entramos a la siguiente dirección:


http://localhost:8080/tags/mensajes.jsp


Con lo que deberemos ver el formulario correspondiente:



Si presionamos el botón "Enviar", sin colocar ningún valor en ninguno de los campos, debemos ver la siguiente salida.



Podemos ver que en la parte superior de formulario se colca una lista con los mensajes de error de cada uno de los campos. Esta lista tiene la clase CSS "errorMessage" (la misma que la etiqueta anterior).

El código generado por la etiqueta anterior es el siguiente:


<ul class="errorMessage">
    <li><span>El password es un campo obligatorio</span></li>
    <li><span>El nombre es un campo obligatorio</span></li>
    <li><span>El correo electronico es un campo obligatorio</span></li>
</ul>


¿Qué pasaría si en vez de querer mostrar todos los errores juntos, quisiéramos mostrarlos de forma individual, o en grupos propios, o junto al campo correspondiente (pensando además que un campo pudiera generar más de un error)?

Para esos casos, la etiqueta "errorMessage" nos permite indicar el nombre del campo del cual deseamos mostrar el error.

Tenemos dos formas de hacer esto. La primera es mostrar el error correspondiente a un solo campo, usando el atributo "fieldName" de la etiqueta. En él colocamos el nombre del campo como aparezca en la etiqueta del formulario. Por ejemplo, para el campo "nombre", colocamos la etiqueta de la siguiente forma:


<s:fielderror fieldName="nombre" />


Para el campo "password", de la siguiente:


<s:fielderror fieldName="password" />


Etc.

Modifiquemos el formulario anterior para incluir estos mensajes.

*Nota: En una aplicación web real, la maquetación deberíamos hacerla mediante CSSs para que el acomodo de los elementos quede de una forma más estilizada. Sin embargo, para la caridad del ejemplo, sólo colocaremos los componentes esenciales.

<s:fielderror />
    <s:form action="mensajes">
    <s:label value="Nombre: " for="nombre" />
    <s:textfield name="nombre" id="nombre" />
    <s:fielderror fieldName="nombre" />
    <br />
    <s:label value="Password: " for="password" />
    <s:password name="password" id="password" />
    <s:fielderror fieldName="password" />
    <br />
    <s:label value="Correo:" for="correo" />
    <s:textfield name="correo" id="correo" />
    <s:fielderror fieldName="correo" />
    <br />
    <s:submit value="Enviar" />
</s:form>


Si presionamos nuevamente el botón "Enviar" del formulario, sin colocar ningún valor en los campos de texto, veremos la siguiente salida:



Que genera el siguiente código:


<form id="mensajes" name="mensajes" action="/tags/mensajes.action" method="post">
    
    <label for="nombre">Nombre: </label>
    <input type="text" name="nombre" value="" id="nombre"/>
    <ul class="errorMessage">
        <li><span>El nombre es un campo obligatorio</span></li>
    </ul>
    <br />
    
    <label for="password">Password: </label>
    <input type="password" name="password" id="password"/>
    <ul class="errorMessage" >
        <li><span>El password es un campo obligatorio</span></li>
    </ul>
    <br />
    
    <label for="correo">Correo:</label>
    <input type="text" name="correo" value="" id="correo"/>
    <ul class="errorMessage">
        <li><span>El correo electronico es un campo obligatorio</span></li>
    </ul>
    <br />
    
    <input type="submit" id="mensajes_0" value="Enviar"/>
</form>


Podemos ver que ahora se coloca una lista para cada uno de los elementos indicados, esto para el caso que dijimos anteriormente: que un campo genere más de un error.

Veamos el último ejemplo, en el que colocamos los errores de varios campos en un solo bloque. Para hacer esto, podemos pasar como parámetros, en el cuerpo de la etiqueta "s:fielderror", los nombres de los campos de los cuales queremos mostrar los errores.

Supongamos que queremos mostrar en un bloque los errores de los campos "nombre" y "correo", y en otro los del campo "password". Para este caso debemos colocar dos etiquetas, la primera queda de la siguiente forma:


<s:fielderror>
    <s:param>nombre</s:param>
    <s:param>correo</s:param>
</s:fielderror>


Y la segunda puede quedar así (aunque para este caso particular también pudimos haber usado el atributo "fieldName"):


<s:fielderror>
    <s:param>password</s:param>
</s:fielderror>


Para que sea más claro a la hora de ver estos dos grupos de errores en la pantalla, colocaremos cada uno dentro de un "fieldset":


<fieldset>
    <legend>Nombre y correo</legend>
    <s:fielderror>
        <s:param>nombre</s:param>
        <s:param>correo</s:param>
    </s:fielderror>
</fieldset>

<fieldset>
    <legend>Password</legend>
    <s:fielderror>
        <s:param>password</s:param>
    </s:fielderror>
</fieldset>


Si presionamos nuevamente el botón "Enviar" del formulario, sin colocar valores en los campos de texto, veremos la siguiente salida:



En donde podemos ver los dos grupos anteriores de errores.

La salida anterior genera el siguiente código:


<fieldset>
    <legend>Nombre y correo</legend>
    <ul class="errorMessage">
        <li><span>El nombre es un campo obligatorio</span></li>
        <li><span>El correo electronico es un campo obligatorio</span></li>
    </ul>
</fieldset>

<fieldset>
    <legend>Password</legend>
    <ul class="errorMessage">
        <li><span>El password es un campo obligatorio</span></li>
    </ul>
</fieldset>


Donde podemos ver que el ejemplo funciona correctamente.

Existen otras dos etiquetas en este grupo, las cuales dijimos que no veríamos ejemplos. Para los curiosos sólo las mencionaremos:

component

Muestra un componente de UI propio usando el template especificado. Este template puede ser creado usando JSP, Freemarker o Velocity.

div

Crea una etiqueta "div" de HTML. Sólo tiene una utilidad cuando se usa el tema "ajax".

Con esto terminamos con las etiquetas de esta categoría y con el tutorial.

Habíamos dicho que existe una quinta categoría de etiquetas, las etiquetas "AJAX", pero que estas hacen uso del framework Dojo y que tiene problemas para integrarse con nuevas versiones del framework, o con otros frameworks, y que por lo tanto en las nuevas versiones de Struts 2 estas se proporcionan como un plugin adicional.

De forma personal, si van a hacer uso de Ajax en una aplicación Struts 2, les recomiendo el uso del plugin de jQuery

Con esto terminamos este largo tutorial, que espero que les sea de utilidad.

Si tienen alguna duda, sugerencia, comentario o aclaración, pueden dejarla en la sección de comentarios o enviar un correo a programadorjavablog@gmail.com (pueden agregarme al chat de gmail). También pueden seguir JavaTutoriales en las siguientes redes sociales:

Saludos y gracias.

Descarga los archivos de este tutorial desde aquí:



Entradas Relacionadas: