07 noviembre, 2006

Struts Tiles

.
Plantillas

Struts Tiles (plantillas) es una opción que provee Struts para hacer variar el contenido de las diferentes secciones que componen un portal.

Supongamos un portal con el siguiente formato:


Probablemente el header y el footer sean estáticos, pero indudablemente el cuerpo va a variar según la opción del menu que sea seleccionada.

Tiles nos permite definir una plantilla para nuestro portal y luego, según corresponda, setear diferentes vistas en cada una de las secciones definidas.


Comenzando con Struts Tiles

Para comenzar vamos a desarrollar un portal estático con el que podamos configurar y probar el seteo de tiles. Luego, con este tema resuelto analizaremos un portal con contenidos dinámicos.


Configurar Tiles Paso a Paso

1 - Crear un Web Project (de MyEclipse) llamado HMTILES y agregarle Struts Capabilites.


2 - En struts-config.xml, justo debajo de:
   1:
2:<message-resources parameter=
3: "test.struts.ApplicationResources" />
4:

agregar el siguiente código:
   1:
2:<plug-in className=
3: "org.apache.struts.tiles.TilesPlugin">
4: <set-property
5: property="definitions-config"
6: value="/WEB-INF/tiles-defs.xml" />
7: <set-property
8: property="moduleAware"
9: value="true" />
10: <set-property
11: property="definitions-parser-validate"
12: value="true" />
13:</plug-in>
14:


3 - Crear dentro de WebRoot una carpeta llamada jsp.


4 - Crear dentro de WebRoot/jsp la siguiente página JSP:

portalLayout.jsp
   1:
2:<%@ page language="java"%>
3:<%@ taglib
4: uri="http://jakarta.apache.org/struts/tags-html"
5: prefix="html"%>
6:<%@ taglib
7: uri="http://jakarta.apache.org/struts/tags-tiles"
8: prefix="tiles"%>
9:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
10:Transitional//EN">
11:
12:<html:html locale="true">
13:<head>
14:<html:base />
15: <title>
16: <tiles:getAsString name="title" />
17: </title>
18:</head>
19:<body>
20: <table border="1" width="600" cellspacing="5">
21: <tbody>
22: <tr>
23: <td colspan="2">
24: <tiles:insert attribute="header" />
25: </td>
26: </tr>
27: <tr>
28: <td width="200">
29: <tiles:insert attribute="menu" />
30: </td>
31: <td width="400">
32: <tiles:insert attribute="cuerpo" />
33: </td>
34: </tr>
35: <tr>
36: <td colspan="2">
37: <tiles:insert attribute="footer" />
38: </td>
39: </tr>
40: </tbody>
41: </table>
42:</body>
43:</html:html>
44:

Como vemos, portalLayout.jsp representa la plantilla del portal. Dentro de esta página utilizamos los custom tags de Struts para definir diferentes secciones en las que luego Tiles seteará sus contenidos. Entre las secciones podemos identificar: header, menu, cuerpo y footer además de titulo.


5 - Crear dentro de WEB-INF el siguiente archivo:

tiles-defs.xml
   1:
2:<?xml version="1.0" encoding="ISO-8859-1" ?>
3:<!DOCTYPE tiles-definitions PUBLIC
4:"-//Apache Software Foundation//DTD Tiles
5:Configuration 1.1//EN"
6:"http://jakarta.apache.org/struts/dtds/
7:tiles-config_1_1.dtd">
8:
9:<tiles-definitions>
10: <!-- DEFINICION BASE -->
11: <definition name="base.definition"
12: path="/jsp/portalLayout.jsp">
13: <put name="header" value="/jsp/header.jsp" />
14: <put name="menu" value="/jsp/menu.jsp" />
15: <put name="footer" value="/jsp/footer.jsp" />
16: </definition>
17:
18: <!-- DEFINICION BIENVENIDA -->
19: <definition name="page.bienvenido"
20: extends="base.definition">
21: <put name="title" value="Bienvenido" />
22: <put name="cuerpo" value="/jsp/bienvenido.jsp" />
23: </definition>
24:
25: <!-- DEFINICION PANTALLA 1 -->
26: <definition name="page.pantalla1"
27: extends="base.definition">
28: <put name="title" value="Pantalla 1" />
29: <put name="cuerpo" value="/jsp/pantalla1.jsp"/>
30: </definition>
31:
32: <!-- DEFINICION PANTALLA 2 -->
33: <definition name="page.pantalla2"
34: extends="base.definition">
35: <put name="title" value="Pantalla 2" />
36: <put name="cuerpo" value="/jsp/pantalla2.jsp"/>
37: </definition>
38:</tiles-definitions>
39:

En tiles-defs.xml definimos las diferentes instancias por las que puede pasar el portal. Vemos que existe una definición base en la que se setean las secciones header, menu y footer. Luego en las diferentes definiciones (page.bienvenida, page.pantalla1, page.pantalla2) definimos los elementos variables que corresponde setear en la plantilla según sea la instancia (o estado) por el que pasa el portal.


6 - Tenemos que crear ahora las páginas JSP que utilizan las definiciones anteriores. En WebRoot/jsp creamos las siguiente páginas:
  • header.jsp
  • footer.jsp
  • menu.jsp
  • bienvenido.jsp
  • pantalla1.jsp
  • pantalla2.jsp
Como ejemplo podemos ver la página header.jsp

header.jsp
   1:
2:Este es el Header
3:

Todas las demás páginas (salvo menu.jsp que veremos a continuación) son iguales a header.jsp.

Ahora veamos el código de la página del menú:

menu.jsp
   1:
2:<%@ taglib
3: uri="http://jakarta.apache.org/struts/tags-html"
4: prefix="html"%>
5:
6:<!-- usamos un custom tag para el link -->
7:<html:link page="/fwdPantalla1.do">
8: Pantalla 1
9:</html:link><br>
10:
11:<!-- usamos un custom tag para el link -->
12:<html:link page="/fwdPantalla2.do">
13: Pantalla 2
14:</html:link><br>
15:
16:<!-- este link no funciona -->
17:<a href="/fwdPantalla2.do">
18: Pantalla 2 (este link no funciona)
19:</a><br>
20:
21:<!-- este link tampoco funciona -->
22:<a href="fwdPantalla2.do">
23: Pantalla 2 (este link tampoco funciona)
24:</a><br>
25:

En el menú definimos dos links utilizando el custom tag html:link de Struts y dos links utilizando el tag a href de HTML. Luego analizaremos las diferencias pero desde ya podemos asegurar que los links definidos con el a href de HTML no funcionan.

Por último, para "entrar" en cada una de las definiciones previstas en tiles-defs.xml necesitamos un action que forwardee.


7 - Agregar los siguientes action dentro del tag action-mappings en el archivo struts-config.xml.
   1:
2:<action path="/fwdInicio"
3: type="test.struts.action.FWDInicioAction">
4: <forward name="ok" path="page.bienvenido" />
5:</action>
6:<action path="/fwdPantalla1"
7: type="test.struts.action.FWDPantalla1Action">
8: <forward name="ok" path="page.pantalla1" />
9:</action>
10:<action path="/fwdPantalla2"
11: type="test.struts.action.FWDPantalla2Action">
12: <forward name="ok" path="page.pantalla2" />
13:</action>
14:

Como vemos tenemos un action para forwardear a cada una de las definiciones expuestas en tiles-defs.xml. Solo queda escribir las clases para los actions que acabamos de definir


8 - Creamos el paquete test.struts.action y dentro de este paquete creamos las siguientes clases:
  • FWDInicioAction.java
  • FWDPantalla1Action.java
  • FWDPantalla2Action.java
Todas son exactamente iguales. Como ejemplo veremos la primera.

FWDInicioAction.java
   1:
2:package test.struts.action;
3:
4:import javax.servlet.http.*;
5:import org.apache.struts.action.*;
6:
7:public class FWDInicioAction extends Action
8:{
9: public ActionForward execute(
10: ActionMapping mapping
11: , ActionForm form
12: , HttpServletRequest request
13: , HttpServletResponse response)
14: {
15: return mapping.findForward("ok");
16: }
17:}
18:


9
- Deployamos el proyecto y accedemos a la página principal:



Agregar Contenido Dinámico

Ahora, de a poco, vamos a agregar algo de contenido dinámico. Por ejemplo, sería bueno mostrar la fecha en el header.

Como vimos en los capítulos anteriores, la idea es que a las páginas JSP les llegue toda la información que tienen que mostrar. Es decir, que la responsabilidad de obtener esa información no recaiga en la página. La página solo debe mostrarla, pero no conseguirla.

Entonces, la solución será implementar un "servlet anterior" para header.jsp en el que vamos a obtener la fecha y la vamos a setear como atributo en el request para que llegue a la página JSP.

El "servlet anterior" lo implementaremos como un action al que (por convención) le antepondremos el prefijo "PRE".

PREHeaderAction.java
   1:
2:package test.struts.action;
3:
4:import java.util.*;
5:import javax.servlet.http.*;
6:import org.apache.struts.action.*;
7:
8:public class PREHeaderAction extends Action
9:{
10: public ActionForward execute(
11: ActionMapping mapping
12: , ActionForm form
13: , HttpServletRequest request
14: , HttpServletResponse response)
15: {
16: GregorianCalendar gc=new GregorianCalendar();
17:
18: // seteo la fecha de hoy
19: gc.setTimeInMillis(
20: System.currentTimeMillis());
21:
22: // Los meses comienzan a numerados desde 0
23: int iMes=gc.get(Calendar.MONTH)+1;
24: int iDia=gc.get(Calendar.DAY_OF_MONTH);
25: int iAnio=gc.get(Calendar.YEAR);
26:
27: // armo la fecha como "dd/mm/aaaa"
28: String sFecha=iDia+"/"+iMes+"/"+iAnio;
29:
30: // seteo el string sFecha en el request
31: request.setAttribute("fecha",sFecha);
32:
33: return mapping.findForward("ok");
34: }
35:}
36:

El próximo paso es agregar una entrada action dentro del tag action-mappings en struts-config.xml.
   1:
2:<!-- por convencion le enteponemos "pre" -->
3:<action path="/preHeader"
4: type="test.struts.action.PREHeaderAction">
5: <forward name="ok" path="/jsp/header.jsp" />
6:</action>
7:

Y ahora modificamos tiles-defs.xml para que en lugar de setear directamente header.jsp primero invoque al "servlet anterior" /preHeader.do quien (luego de setear todos los datos que va a necesitar header.jsp) forwardeará a la página JSP.
   1:
2:<!-- DEFINICION BASE -->
3:<!-- en lugar de setear directamente header.jsp -->
4:<!-- setea su "servlet anterior": /preHeader.do -->
5:
6: <definition name="base.definition"
7: path="/jsp/portalLayout.jsp">
8: <put name="header" value="/preHeader.do" />
9: <put name="menu" value="/jsp/menu.jsp" />
10: <put name="footer" value="/jsp/footer.jsp" />
11: </definition>
12:


Pasaje de Parámetros


En munu.jsp definimos 4 links, dos de los cuales (los dos últimos) no funcionan correctamente. Para comprender la diferencia entre los primeros dos (lo que generamos con html:link) y los últimos dos (los que generamos con a ref) analizaremos el código HTML generado por la página JSP.
   1:
2:<!-- usamos un custom tag para el link -->
3:<a href="/HMTILES/fwdPantalla1.do">
4: Pantalla 1
5:</a>
6:
7:<!-- usamos un custom tag para el link -->
8:<a href="/HMTILES/fwdPantalla2.do">
9: Pantalla 2
10:</a>
11:
12:<!-- este link no funciona -->
13:<a href="/fwdPantalla2.do">
14: Pantalla 2 (este link no funciona)
15:</a>
16:
17:<!-- este link tampoco funciona -->
18:<a href="fwdPantalla2.do">
19: Pantalla 2 (este link tampoco funciona)
20:</a>
21:

Lo que vemos es que los primeros dos links hacen referencia a /HMTILES/fwdPantallan.do. El tercer link apunta a: /fwdPantalla2.do y el cuarto link apunta a: fwdPantalla2.do.

El problema surge porque menu.jsp está en el directorio jsp por lo tanto si el link es relativo (el caso del cuarto link) el browser va a buscar el recurso en el mismo directorio. Y si el link es absoluto (el caso del tercer link) el browser va a buscar un recurso en la raíz del server, fuera del contexto /HMTILES.

Ahora bien, que tiene que ver esto con el pasaje de parámetros y (de última) si los links a href generan este tipo de problemas por que directamente no los abandonamos y utilizamos los html:link?

El problema es el siguiente: los html:link no soportan el uso de expresiones dentro del tag. Es decir que el siguiente ejemplo no compila:
   1:
2:<%
3: String nom = "Juan 'No FUNCIONA' Garcia";
4:%>
5:
6:<!-- esto no compila -->
7:<html:link page="/fwdPantalla2.do?nombre=<%=nom%>">
8: Pantalla 2
9:</html:link>
10:

Obviamente, existe una forma de pasar parámetros dentro de un html:link pero esta implica obligatoriamente utilizar formularios (ActionForm) y aún así solo se puede pasar un único parámetro. Si queremos pasar más de un parámetro tenemos que comenzar a inventar trucos.

Por lo tanto, la solución es programar una clase que permita linkear correctamente una dirección pasándole en el URL todos los parámetros que necesitemos.

ULink.java
   1:
2:package test.util;
3:
4:import java.util.*;
5:import javax.servlet.http.HttpServletRequest;
6:
7:public class ULink
8:{
9: private String href;
10: private Hashtable params;
11: private HttpServletRequest request;
12:
13: public ULink(String href
14: ,HttpServletRequest request)
15: {
16: this.request=request;
17: this.href=href;
18: params=new Hashtable();
19: }
20:
21: public void addParameter(String paramName
22: ,Object paramValue)
23: {
24: if( paramValue==null )
25: {
26: paramValue="";
27: }
28:
29: params.put(paramName,paramValue);
30: }
31:
32: public String toString()
33: {
34: String pName;
35: String pValue;
36: StringBuffer sb=new StringBuffer();
37:
38: sb.append( _obtenerRaiz(request) );
39:
40: sb.append(href);
41: if( params.size()>0 )
42: {
43: sb.append("?");
44: }
45:
46: int i=0;
47: for(Enumeration e=params.keys()
48: ; e.hasMoreElements();)
49: {
50: pName=(String)e.nextElement();
51: pValue=params.get(pName).toString();
52: sb.append(pName+"="+pValue);
53: if( i++<params.size()-1 )
54: {
55: sb.append("&");
56: }
57: }
58: return sb.toString();
59: }
60:
61: private String _obtenerRaiz(
62: HttpServletRequest req)
63: {
64: StringBuffer sb=req.getRequestURL();
65: String url=sb.toString();
66: String cp=req.getContextPath();
67: int pos=url.indexOf(cp);
68: String s1=url.substring(0,pos);
69: String s2=s1+cp;
70: return s2;
71: }
72:}
73:

Ahora podemos utilizar un a href de HTML y pasarle los parámetros con una expresión JSP.
   1:
2:<%@ page import="test.util.ULink" %>
3:
4:<%
5: String nom = "Juan 'Ahora SI FUNCIONA' Garcia";
6: ULink lnk = new ULink("/fwdPantalla2.do",request);
7: lnk.addParameter("nombre",nom);
8:%>
9:
10:<!-- ahora utilizamos un a href de HTML -->
11:<a href="<%=lnk%>">Pantalla 2</a>
12:







.