sábado, 31 de diciembre de 2011

Usando CSS Sprites en una columna de un GridView

La técnica de sprites es bastante antigua... bueno, yo los usé programando para la Commodore 64, así que si, tenemos unos cuántos años!

En este caso vamos a ver como utilizar la técnica de sprites con hojas de estilos (CSS) para identificar un  un estado determinado en un registro de datos. Para eso listaremos los datos en  un GridView en una página web y usaremos una columna con imágenes para mostrar los diferentes valores de estado del registro.

Este ejemplo apunta a mostrar cómo usar estilos para posicionar una imagen en un elemento dinámicamente, y si bien las imágenes solo muestran un estado, podrían utilizarse botones o links en su lugar para lograr acciones determinadas. En este ejemplo se verá también cómo aplicar wilcards (comodines) en las clases de una hoja de estilos.


La técnica es bastante sencilla, pero muy útil. En este ejemplo se verá cómo establecer una imagen para cada valor de la columna estado. Para este ejemplo tendremos 4 posibles estados diferentes, del 0 al 3, pero puede ser cualquier otro tipo de dato, siempre que sean valores definidos, para que sean identificables.

Lo primero que tenemos que tener es un gráfico con las imágenes que  representan a cada uno de los estados. Según el valor del estado, se mostrará la parte del gráfico correspondiente al valor de estado del dato.

El gráfico de este ejemplo tiene 5 imágenes: 4 corresponden a cada valor posible del campo Estado, y una la utilizaré solamente a modo de  ejemplo para que la imagen cambie cuando se pose el mouse en ella. En el ejemplo que dejo para bajar, en el ToolTip de la imagen se muestra el valor del estado, mientras la imagen cambia a un signo de interrogación.

Imágenes que usaremos en este ejemplo
Este gráfico tiene 60 x 40 pixels, y a cada imagen se le asignó un espacio de 20 x 20 pixels. 


La cosa es bastante simple: mediante una clase CSS para cada estado se establece un gráfico de fondo al objeto que contiene el dato en la celda correspondiente de la grilla. En nuestro caso será un span de HTML (más abajo se explica por qué). Luego se modifica la posición de la imagen a mostrar, desplazándola n pixels en los ejes x e y para representar cada valor de estado.
El gráfico puede tener cualquier forma. Lo que importa es el posicionamiento que se le de mediante los estilos.

Para mostrar el relojito verde en el objeto que contiene el valor del estado el desplazamiento será de 20 px hacia la izquierda y  20 px  hacia arriba.


El ejemplo:
Usaremos un GridView en una página web, con dos columnas. La columna que importa es la de Estado, (cuyo campo se llama STATUS). Los pasos son:

Agregar un GridView a la página, estableciendo AutoGenerateColumns="False" y la columna STATUS como TemplateField. El código de la grilla quedaría así:

            <asp:GridView ID="GridView1" AutoGenerateColumns="False" runat="server">
                <Columns>
                    <asp:TemplateField HeaderText="Estado">
                        <EditItemTemplate>
                            <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("STATUS") %>'></asp:TextBox>
                        </EditItemTemplate>
                        <ItemTemplate>
                            <asp:Label ID="Label1" runat="server" Text='<%# Bind("STATUS") %>'></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:BoundField DataField="TAREA" HeaderText="Descripción" />
                </Columns>
            </asp:GridView>

Vamos a crear un DataTable como fuente de datos para poblar la grilla. Podríamos usar  cualquier fuente de datos, pero para este ejemplo, en el CodeBehind de la página, tendremos este código:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.Page.IsPostBack)
        {
            // Asignar la fuente de datos al GridView y mostrarlos.
            GridView1.DataSource = getDatos();
            GridView1.DataBind();
        }
    }
 
    private DataTable getDatos()
    {
        // Construir la tabla con datos.
        // En este caso para Estado (STATUS) utilizaremos 
        // 4 valores enteros, entre 0 y 3.
        DataTable dt = new DataTable();
        dt.Columns.Add(new DataColumn("STATUS", typeof(int))); // Estados, de 0 a 3 en este caso.
        dt.Columns.Add(new DataColumn("TAREA"));
 
        // Llenar tabla con los datos.
        DataRow dr = dt.NewRow();
        dt.Rows.Add(1, "Obtener lista de requisitos.");
        dt.Rows.Add(2, "Llamar al cliente.");
        dt.Rows.Add(0, "Presentar diseño.");
        dt.Rows.Add(3, "Modificar planos.");
        dt.Rows.Add(1, "Agregar funcionalidad requerida.");
        dt.Rows.Add(3, "Generar localización nuevo proyecto.");
 
        // Retornar tabla
        return dt;
    }


Al ejecutar, luego de cargarse los datos, veremos el valor de STATUS en la columna Estado.


Lo que queremos aquí son cuatro cosas:
  1. No ver el valor de Estado. 
  2. Ver en su lugar una imagen por cada valor de estado (valor del campo STATUS).
  3. Que la imagen cambie cuando se posa el mouse sobre ella y 
  4. Mostrar en el ToolTip de la imagen el valor de STATUS.

Vamos entonces a concentrarnos en el ItemTemplate correspondiente a la columna de Estado. Ahi veremos que la propiedad Text del Label hace un Bind al campo STATUS. 
     
<ItemTemplate>
     <asp:Label ID="Label1" runat="server" Text='<%# Bind("STATUS") %>'></asp:Label>
</ItemTemplate>

Para no ver el valor de STATUS, simplemente dejamos Text="" (vacío).

El truco principal está en utilizar el DataFormatString que se aplica al enlazar el campo. Es decir, cuando se hace Bind("STATUS"), se puede agregar un formato al texto resultante. Por ejemplo, Bind("STATUS", "{0:0.0}") mostrará el valor numérico con dos decimales.

Utilizando esto, la idea es asignarle a la propiedad CssClass una clase CSS dinámica para cada valor de STAUS. Lo que se quiere lograr es, por ejemplo:

CssClass="tareaStyle_0" para el STATUS = 0
CssClass="tareaStyle_1" para el STATUS = 1
CssClass="tareaStyle_2" para el STATUS = 2
CssClass="tareaStyle_3" para el STATUS = 3

Luego aplicaremos un estilo a cada uno de ellos en particular.
Aplicando entonces el formato al enlazar el dato, quedaría así:

CssClass='<%# Bind("STATUS", "tareaStyle_{0}") %>'
donde {0} será reemplazado con el valor del Estado STATUS.

El código de la grilla quedaría finalmente como se muestra a continuación, utilizando la misma técnica para el ToolTip:

<ItemTemplate>
    <asp:Label ID="Label1" runat="server" CssClass='<%# Bind("STATUS", tareaStyle_{0}") %>' Text="" ToolTip='<%# Bind("STATUS", "El estado de esta tarea es {0}") %>'></asp:Label>
</ItemTemplate>

A los fines de simplificar el ejemplo, eliminé el EditItemTemplate del GridView, ya que no mostraremos la fila en modo edición.

<asp:GridView ID="GridView1" AutoGenerateColumns="False" runat="server">
    <Columns>
        <asp:TemplateField HeaderText="Estado">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" CssClass='<%# Bind("STATUS", "tareaStyle_{0}") %>' Text="" ToolTip='<%# Bind("STATUS", "El estado de esta tarea es {0}") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="TAREA" HeaderText="Descripción" />
    </Columns>
</asp:GridView>

La hoja de estilos. 
Ahora solo falta aplicar los estilos correspondientes a cada unos de los diferentes valores de estado.
Creamos una nueva hoja de estilos. Debemos contar también con el archivo de imagen que contiene los iconos que representarán los valores de STATUS, que en este caso se llamará icoStatus.png.

La forma de crear las clases CSS es primero crear una en común para todos estilos de los estados. Como tenemos 4 estados diferentes que se llamarán tareaStyle_0, tareaStyle_1, tareaStyle_2 y tareaStyle_3 lo más lógico sería crear una enumerando todos, de esta manera:

.tareaStyle_0, .tareaStyle_1, .tareaStyle_2, .tareaStyle_3
{
    /* ... estilos ...*/
}

Pero para no tener que estar agregando una clase para cada diferente valor, en lugar de eso utilizaremos wilcards en la clase para hacer que se corresponda solo con la parte del nombre de la clase .tareaStyle_

Al convertir la columna a TemplateField, el objeto que queda dentro del ItemTemplate de la columna es control web Label, por lo que éste será traducido por el servidor a HTML y enviado al navegador como un <span>. Así definiremos los siguientes estilos para el atributo class del span:


span[class*='tareaStyle_'] 
{
width:20px;
height:20px;
display:block;
margin:0 auto 0 auto;
background-image:url('icoStatus.png');
background-repeat:no-repeat;
cursor:pointer;
} 

Es importante tener en cuenta lo definido como ancho y alto para el span (20px por 20px), ya que funcionará como la ventana donde se mostrará la imagen correspondiente del gráfico de fondo, cuando lo posicionemos correctamente según el valor de STATUS. Luego se define la imagen de fondo, sin repeticiones, y finalmente que el cursor del mouse aparezca como una manito, como lo hace en los links.
Si no definimos una posición para la imagen de fondo, entonces veremos solo el primer icono de arriba a la izquierda.

Ahora solo queda posicionarlo para cada uno de los estados. En estos no podremos usar ningún comodín, debiendo crear la clase correspondiente a cada estado, pero que solamente contendrá el desplazamiento del gráfico para obtener la posición de la imagen deseada en el span de 20 x 20 pixels.


/* estilos para cada estado */
 
.tareaStyle_0 {
    background-position: 0 0;
}
 
.tareaStyle_1 {
    background-position: -20px 0;
}
 
.tareaStyle_2 {
    background-position: 0 -20px;
}
 
.tareaStyle_3 {
    background-position: -20px -20px;
}


Como se ve, la posición corresponde al lugar en que se encuentra cada imagen.



Por último solo nos queda el estilo para cambiar la imagen al pasar el mouse por encima. En este caso utilizaremos otra vez los wilcards en la clase de estilos y la imagen correspondiente al signo de interrogación:

span[class*='tareaStyle_']:hover {
    background-position: -40px 0;   
}


Conclusión:
La ventaja de usar esta técnica es que se descarga una sola imagen desde el servidor, y luego el navegador calcula qué mostrar en base a lo definido en los estilos, haciendo en general mucho más rápida la descarga  de la página.

En la web se pueden encontrar varias herramientas para generar este tipo de gráficos combinados. CSS Sprites  es una página, donde se suben las imágenes y devuelve un solo gráfico con todas las combinadas. La sugerencia es que suban todas las imágenes de un mismo tamaño para que sea más fácil ubicarlas luego, pero depende de cada proyecto.

Recursos:

  • CSS Sprites: Herramienta para generar los CSS sprites
  • El código de ejemplo de esta nota. Hacer clic en "Descargar" para bajar todos los archivos juntos. Luego incluirlos en un proyecto web para hacerlo funcionar.

4 comentarios:

  1. Hola Marcelo,

    Y si lo que tengo es un campo con un checkbox (si/no), ¿como debe implementarse?

    ResponderEliminar
  2. Igual que en el ejemplo del Label. Solo debes tener en cuenta para los estilos que un webcontrol del tipo se corresponde a un elemento input del tipo checkbox en el HTML: < i nput type="checkbox" />

    Saludos!

    ResponderEliminar
  3. Como podrìa hacer para que un campo(celda) Si/No se muestre de color verde en Si y rojo en No...? de antemano gracias.

    ResponderEliminar
    Respuestas
    1. Hola, ru consulta escapa al tema de esta nota, pero el estilo debería estar aplicado al elemento de la grilla, o dentro de la grilla. Se puede hacer con JavaScript, utilizando también jQuery.
      saludos!

      Eliminar