Jugando con tablas en JSF

De ChuWiki

Introducción[editar]

JSF nos ofrece muchas posibilidades para hacer tablas. En este tutorial vamos a hacer un pequeño ejemplo usando <h:panelGrid> y <h:dataTable>. En el ejemplo crearemos una tabla sencilla a la que se pueden añadir y borrar filas.

h:panelGrid y h:dataTable[editar]

Ambos nos permiten pintar una tabla en el navegador, pero lo hace recogiendo los datos de forma distinta. h:panelGrid símplemente es una tabla que tendremos que hacerlos poniendo manualmente todos los elementos. Sin embargo h.dataTable nos permite asociarla a una Collection, Array, ResultSet, RowSet... indicando cómo es una de las filas, iterando automáticamente el resto.

Un ejemplo tonto de h:panelGrid

   <h:panelGrid columns="2">
      <h:outputText value="fila 1, columna 1"></h:outputText>
      <h:outputText value="fila 1, columna 2"></h:outputText>
      <h:outputText value="fila 2, columna 1"></h:outputText>
      <h:outputText value="fila 2, columna 2"></h:outputText>
      <h:outputText value="fila 3, columna 1"></h:outputText>
      <h:outputText value="fila 3, columna 2"></h:outputText>
   </h:panelGrid>

Unicamente le hemos dicho que hay dos columnas y todos los componentes consecutivamente. El mostrarse en el navegador, se irán cogiendo en orden para rellenar las filas con dos elementos en cada una de ellas (dos columnas).

Un ejemplo tonto con h:dataTable. Supongamos que tenemos un @ManagedBean con una propiedad que sea una lista de beans Dato

package com.chuidiang.ejemplos.jsf;

import javax.faces.bean.ManagedBean;

@ManagedBean
public class Dato {
   private int valor;
   private String nombre;

   public String getNombre() {
      return nombre;
   }

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

   public int getValor() {
      return valor;
   }

   public void setValor(int valor) {
      this.valor = valor;
   }
}
package com.chuidiang.ejemplos.jsf;

import java.util.LinkedList;

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;

@ManagedBean
public class HolaMundo {
   private LinkedList<Dato> datos;

   public HolaMundo() {
      // rellenamos unos datos cualquiera en el constructor
      // por tener algo que se vea.
      datos = new LinkedList<Dato>();
      for (int i = 5; i < 10; i++) {
         Dato dato = new Dato();
         dato.setValor(i);
         dato.setNombre("nombre" + i);
         datos.add(dato);
      }
   }

   public LinkedList<Dato> getDatos() {
      return datos;
   }

   public void setDatos(LinkedList<Dato> datos) {
      this.datos = datos;
   }
}

La clase HolaMundo lleva un simple atributo que es una LinkedList<Dato> y los métodos set y get correspondiente. El código xhtml con el h:dataTable para ver estos datos es el siguiente

   <h:dataTable value="#{holaMundo.datos}" var="dato">
      <h:column>
         <f:facet name="header">
            <h:outputText value="valor"></h:outputText>
         </f:facet>
         <h:outputText value="#{dato.valor}"></h:outputText>
      </h:column>
      <h:column>
         <f:facet name="header">
            <h:outputText value="nombre"></h:outputText>
         </f:facet>
         <h:outputText value="#{dato.nombre}"></h:outputText>
      </h:column>
      <h:column>
         <h:form>
            <h:commandButton action="#{holaMundo.removeValor(dato)}"
               value="Borrar"></h:commandButton>
         </h:form>
      </h:column>
   </h:dataTable>

Al h:dataTable le hemos puesto un value="#{holaMundo.datos}", que es la forma de indicar que cogemos el atributo datos de la clase HolaMundo para los datos de nuestra tabla. Como hemos comentado, puede ser un Array, Collection, List, ResultSet, etc. El h:dataTable iterará sobre esta colección de forma automática. Hemos puesto var="dato" para indicar que el elemento concreto de la colección al que le toca el turno se llamará "dato", de forma que dentro del h:dataTable, lo referenciaremos con este nombre.

Dentro del h:dataTable ponemos tantos <h:column> como columnas que queramos que tenga la tabla. Dentro de cada <h:column> podemos poner un <f:facet> para que haga de cabecera de la columna y el dato que queramos para esa celda concreta. Fijémonos en la primera columna

      <h:column>
         <f:facet name="header">
            <h:outputText value="valor"></h:outputText>
         </f:facet>
         <h:outputText value="#{dato.valor}"></h:outputText>
      </h:column>
  • <f:facet name="header"> es para indicar que esto es columna de la tabla. Se podría poner name="footer" para el pié de la tabla. Dentro del f:facet ponemos el h:outputText con el texto de esta cabecera, "valor" en este caso. Por supuesto, podríamos poner cualquier elemento que queramos que haga de cabecera.
  • <h:outputText value="#{dato.valor}"> indica el contenido de la celda, que será el atributo valor de la variable dato, que es la que habíamos definido en el var="dato" del h:dataTable.

La mini aplicación[editar]

Vamos a hacer una mini aplicación que nos permita desde el navegador añadir y borrar Dato a la lista de datos que hay en HolaMundo.

En HolaMundo.java hacemos unos cambios. Le ponemos métodos addValor() y removeValor que permitan añadir y borrar datos de la lista. También, a HolaMundo le ponemos @ApplicationScoped para que los datos sean igual para todos los usuarios que usen esta aplicación (mientras no se rearranque la aplicación, no vamos a hacer persistencia en base de datos).

...

@ManagedBean
@ApplicationScoped
public class HolaMundo {
   
   ...

   public void addValor(Dato valor) {
      datos.add(valor);
   }

   public void removeValor(Dato valor) {
      datos.remove(valor);

   }
}

A la clase Dato.java vamos a ponerle un id único, de forma que cuando queramos borrar un dato, sepamos cual es. Le ponemos un método equals() que haga campo de ese campo id nada más, de forma que dos instancias de Dato son equals() si tienen el mismo id. El método equals() lo genera automáticamente el IDE si se le pide. Y si se define equals(), las buenas costumbres requieren que se defina también el método hashCode(), que hace también automáticamente el IDE.

Para el id único ponemos un contador estático, de forma que cada vez que hagamos un new de esta clase, se incremente en uno y el valor nos sirva de id. Esto, por supuesto, no es necesario si tuviéramos detrás una base de datos que gestione los id de verdad.

package com.chuidiang.ejemplos.jsf;

import javax.faces.bean.ManagedBean;

@ManagedBean
public class Dato {
   private static int contador = 0;
   private int id;
   private int valor;
   private String nombre;

   public Dato() {
      id = contador++;
   }

   public int getId() {
      return id;
   }

   public void setId(int id) {
      this.id = id;
   }

   /** Generado automáticamente por eclipse */
   @Override
   public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + id;
      return result;
   }

   /** Generado automáticamente por eclipse */
   @Override
   public boolean equals(Object obj) {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (getClass() != obj.getClass())
         return false;
      Dato other = (Dato) obj;
      if (id != other.id)
         return false;
      return true;
   }

   public String getNombre() {
      return nombre;
   }

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

   public int getValor() {
      return valor;
   }

   public void setValor(int valor) {
      this.valor = valor;
   }

}

Ya tenemos todo preparado. Ahora sólo nos queda el xhtml, que será un dataTable con tres columnas. La primera con el atributo valor, la segunda con el atributo nombre.... y la tercera con el botón de borrar el elemento.

   <h:dataTable value="#{holaMundo.datos}" var="dato">
      <h:column>
         <f:facet name="header">
            <h:outputText value="valor"></h:outputText>
         </f:facet>
         <h:outputText value="#{dato.valor}"></h:outputText>
      </h:column>
      <h:column>
         <f:facet name="header">
            <h:outputText value="nombre"></h:outputText>
         </f:facet>
         <h:outputText value="#{dato.nombre}"></h:outputText>
      </h:column>
      <h:column>
         <h:form>
            <h:commandButton action="#{holaMundo.removeValor(dato)}"
               value="Borrar"></h:commandButton>
         </h:form>
      </h:column>
   </h:dataTable>

Fíjate que en la tercera columna, donde está el botón de borrar, la action es la llamada al método removeValor de HolaMundo, al que pasamos como parámetro el dato de la fila en cuestión.

Y en la página pondremos también un formulario para crear un nuevo elemento. Para que quede bien organizadito, lo ponemos en un panelGrid, de forma que las etiquetas ocupen la primera columna y las cajas de texto la segunda. El botón de enviar irá en la segunda columna de la tercera fila, por lo que ponemos un elemento vacío para ocupar la primera columna de la tercera fila

   <h:form>
      <h:panelGrid columns="2">
         <!-- primera fila -->
         <h:outputText value="Valor:"></h:outputText>
         <h:inputText id="valor" value="#{dato.valor}"></h:inputText>

         <!-- segunda fila -->
         <h:outputText value="Nombre:"></h:outputText>
         <h:inputText id="nombre" value="#{dato.nombre}"></h:inputText>

         <!-- tercera fila. el outputLabel esta vacio y es para hacer
              que el boton quede en la segunda columna -->
         <h:outputLabel></h:outputLabel>
         <h:commandButton action="#{holaMundo.addValor(dato)}" value="Añadir"></h:commandButton>
      </h:panelGrid>
   </h:form>

Aquí dato hace referencia al @ManagedBean Dato.java, no a la variable del h:dataTable, ya que este formulario está fuera del h:dataTable. En el botón hemos puesto como action la llamada al meétodo addValor() de HolaMundo.

Si arrancamos y visualizamos esto, veremos lo siguiente

donde los botones de "borrar" y el botón de "Añadir" funcionan.