Columnas dinámicas con JSF

En este post voy a explicar cómo realizar una tabla de columnas dinámicas con JSF. Esta solución se basa en la librería de etiquetas JSF de Apache Trinidad, por lo que podremos aplicarla a cualquier implementación y versión de JSF. Yo recomiendo utilizar la implementación de Apache MyFaces.

Muchas veces, la versión de JSF a utilizar viene determinada por el entorno de ejecución. Por ejemplo, la versión 1.2 de JSF requiere servlet 2.5 y jsp 2.1, sólo implementada por Tomcat 6.x. Por lo que si vamos a ejecutar la aplicación en un contenedor Tomcat 5.5 tendremos que optar por la versión 1.1 de JSF.

Lo primero es preparar el entorno, para ello podemos consultar las siguientes páginas:
Una vez preparado el entorno, veamos cómo vamos a dar soporte a los datos desde la parte del servidor. Nuestra tabla estará representada por un mapa donde las claves serán las filas y los valores serán a su vez mapas cuyas claves serán las columnas y cuyos valores son el valor de la celda para dicha fila y dicha columna. Además, necesitaremos proporcionar a la página JSF las filas y las columnas, para que podamos ir recorriéndolas.
El bean que proporciona los datos quedaría de la siguiente forma:
package com.blogspot.tutorialexception;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Bean {
    
    private List<String> filas;
    private List<String> columnas;
    private Map<String, Map<String, String>> tabla;
    
    public Bean() {
        filas = new ArrayList<String>();
        for(int i=1;i<11;i  ) {
            filas.add("Fila " + i);
        }
        
        columnas = new ArrayList<String>();
        for(int i=1;i<6;i  ) {
            columnas.add("Columna " + i);
        }
        
        tabla = new HashMap<String, Map<String,String>>();
        String fila;
        String columna;        
        for (int i = 0; i < filas.size(); i  ) {
            fila = filas.get(i);
            tabla.put(fila, new HashMap<String, String>());
            for (int j = 0; j < columnas.size(); j  ) {
                columna = columnas.get(j);
                tabla.get(fila).put(columna, "Valor " + (i+1) + "x" + (j+1));
            }            
        }
    }

    public List<String> getFilas() {
        return filas;
    }

    public void setFilas(List<String> filas) {
        this.filas = filas;
    }

    public List<String> getColumnas() {
        return columnas;
    }

    public void setColumnas(List<String> columnas) {
        this.columnas = columnas;
    }

    public Map<String, Map<String, String>> getTabla() {
        return tabla;
    }

    public void setTabla(Map<String, Map<String, String>> tabla) {
        this.tabla = tabla;
    }
}
Este bean debe añadirse al fichero faces-config.xml
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
    <application>
        <default-render-kit-id>org.apache.myfaces.trinidad.core</default-render-kit-id>
        <locale-config>
            <default-locale>es_ES</default-locale>
        </locale-config>
    </application>
    <managed-bean>
        <managed-bean-name>bean</managed-bean-name>
        <managed-bean-class>com.blogspot.tutorialexception.Bean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>
</faces-config>
También hemos añadido la propiedad default-render-kit-id para poder utilizar Trinidad.

Observa que en el constructor se inicializa el mapa y las listas de filas y columnas. Esta parte se hace para el ejemplo y en un caso real, tendríamos que rellenar las listas y el mapa con datos reales, por ejemplo provenientes de una base de datos. De forma gráfica, los objetos creados por este bean tendrían el siguiente aspecto:
Lista filas:

Lista columnas:

Para cada elemento fila, tendríamos lo siguiente:


Ahora sólo queda hacer uso de las etiquetas tr:forEach y trh:tableLayout para representar nuestra tabla de datos con columnas dinámicas.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib prefix="tr" uri="http://myfaces.apache.org/trinidad"%>
<%@ taglib prefix="trh" uri="http://myfaces.apache.org/trinidad/html"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<f:view>
    <html lang='es'>
    <head>
        <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1' />        
            <style type="text/css">
            .mitabla {
                width: 100%;
                padding: 0;
                margin: 0;
            }            
            th {
                font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
                color: #4f6b72;
                border-right: 1px solid #C1DAD7;
                border-bottom: 1px solid #C1DAD7;
                border-top: 1px solid #C1DAD7;
                letter-spacing: 2px;
                text-transform: uppercase;
                text-align: left;
                padding: 6px 6px 6px 12px;
                background: #CAE8EA url(images/bg_header.jpg) no-repeat;
            }            
            td {
                border-right: 1px solid #C1DAD7;
                border-bottom: 1px solid #C1DAD7;
                background: #fff;
                padding: 6px 6px 6px 12px;
                color: #4f6b72;
            }
            th.nobg {
                background:transparent none repeat scroll 0 0;
                border-left:0 none;
                border-right:1px solid #C1DAD7;
                border-top:0 none;
            }
            
            th.spec {
                -x-system-font:none;
                background:#FFFFFF url(css/bullet1.gif) no-repeat scroll 0 0;
                border-left:1px solid #C1DAD7;
                border-top:0 none;
                font-family:"Trebuchet MS",Verdana,Arial,Helvetica,sans-serif;
                font-size:10px;
                font-size-adjust:none;
                font-stretch:normal;
                font-style:normal;
                font-variant:normal;
                font-weight:bold;
                line-height:normal;
            }
            th.specalt {
                -x-system-font:none;
                background:#F5FAFA url(css/bullet2.gif) no-repeat scroll 0 0;
                border-left:1px solid #C1DAD7;
                border-top:0 none;
                color:#797268;
                font-family:"Trebuchet MS",Verdana,Arial,Helvetica,sans-serif;
                font-size:10px;
                font-size-adjust:none;
                font-stretch:normal;
                font-style:normal;
                font-variant:normal;
                font-weight:bold;
                line-height:normal;
            }
            td.alt {
                background:#F5FAFA none repeat scroll 0 0;
                color:#797268;
            }
        </style>
        <title>
            <h:outputText value="Ejemplo de Tabla"/>
        </title>
    </head>
    <body>        
        <trh:tableLayout styleClass="mitabla" width="100%">        
            <trh:rowLayout>
                <trh:cellFormat header="true" styleClass="nobg">Tabla</trh:cellFormat>
                <!-- 
                    Por cada elemento de la lista columnas, 
                    pintamos los títulos de dichas columnas
                -->
                <tr:forEach var="columna" items="#{bean.columnas}">
                   <trh:cellFormat header="true">
                       <tr:outputText value="#{columna}"/>
                   </trh:cellFormat>
                </tr:forEach>
            </trh:rowLayout>
            <!-- 
                Por cada elemento de la lista filas, pintamos una fila
            -->
            <tr:forEach var="fila" items="#{bean.filas}" varStatus="numFila">
                <trh:rowLayout>
                    <!-- 
                        Hacemos uso del atributo varStatus para intercalar 
                        estilos en las filas.
                    -->
                    <trh:cellFormat header="true" styleClass="spec" rendered="#{numFila.index%2!=0}">
                        <tr:outputText value="#{fila}"/>
                    </trh:cellFormat>
                    <trh:cellFormat header="true" styleClass="specalt" rendered="#{numFila.index%2==0}">
                        <tr:outputText value="#{fila}"/>
                    </trh:cellFormat>
                    <!--
                        Por cada elemento de la lista columnas, pintando 
                        una celda cuyo valor es el
                        contenido del mapa bidimensional pasándole las dos claves.
                    -->
                    <tr:forEach var="columna" items="#{bean.columnas}">
                        <trh:cellFormat rendered="#{numFila.index%2!=0}">
                            <tr:outputText value="#{bean.tabla[fila][columna]}"/>
                        </trh:cellFormat>
                        <trh:cellFormat styleClass="alt" rendered="#{numFila.index%2==0}">
                            <tr:outputText value="#{bean.tabla[fila][columna]}"/>
                        </trh:cellFormat>
                    </tr:forEach>
                </trh:rowLayout>
            </tr:forEach>
        </trh:tableLayout>
    </body>
</html>
</f:view>
A continuación explicamos las partes más importantes:
Líneas 10-75:
Estilos de tabla extraídos de la web http://veerle.duoh.com/blog/comments/a_css_styled_table/
Línea 81:
Declaración del elemento trh:tableLayout (http://myfaces.apache.org/trinidad/trinidad-api/tagdoc/trh_tableLayout.html)
Línea 83:
Declaración del elemento trh:rowLayout (http://myfaces.apache.org/trinidad/trinidad-api/tagdoc/trh_rowLayout.html)
Líneas 87-91:
Iteramos sobre la lista de de columnas (#{bean.columnas}) y pintamos el título de cada columna
Línea 96:
Iteramos sobre la lista de filas (#{bean.filas}). Además definimos el atributo varStatus que nos permite saber en cualquier momento de la iteración algunos datos de la misma, como el índice, el número de elementos procesados, el primero, etc. a partir de una variable a la que hemos llamado numFila. Para más información consultar la documentación oficial.
Líneas 101-106:
Simplemente se muestra un uso del atributo varStatus que pinta una celda u otra en función de si el índice del elemento actual de la lista filas es par o impar mediante el atributo rendered.
Líneas 111-118:
Iteramos sobre la lista de columnas de nuevo, para ir accediendo a los valores de cada fila y columna. La expresión EL #{bean.tabla[fila][columna]} sería equivalente a bean.getTabla.get(fila).get(columa). Al igual que antes, pintamos una celda u otra en función de la variable de estado numFila.

El resultado final debería ser el siguiente:

2 comentarios:

Anónimo dijo...

Muchas gracias! Muy buen aporte, implementaré y luego comentaré.

Anónimo dijo...

xevere...

Publicar un comentario