martes, 4 de octubre de 2011

Control ListView en ASP.NET

Si tuviera que elegir un solo control enlazado a datos, elegiría el control ListView, y como no me gusta que te quedes con la duda de porqué, escribo este post.

¿Por qué ListView?

No ofrece ninguna salida HTML predeterminada.

Después de llevar cierto tiempo programando en ASP.NET Web Forms, te das cuenta que es mejor construir de cero tu Layout que lidiar con la salida predeterminada que generan muchos controles. Quizás es más sencillo al comienzo, utilizar controles que automáticamente generan una salida con un formato predeterminado, pero con el paso del tiempo, esa ayuda inicial se torna en una barrera infranqueable con la que es difícil convivir y en la que se pierde mucho tiempo intentando adaptar la salida predeterminada a la salida que realmente quieres obtener.

Versatilidad

Gracias a ciertas propiedades (que veremos después), es posible utilizar este control como si fuera un GridView, un Repeater, un FormView, un DetailsView y, literalmente, podría suplantar la funcionalidad de cualquier otro control enlazado a datos.

Plantillas de datos completas

Dispone de todas las plantillas de datos necesarios para cubrir todos los escenarios.

Por ejemplo, el control Repeater no tiene una plantilla EmptyTemplate (aunque implementarla no es complicado).

Paginación automática

Gracias al control DataPager, el control ListView ofrece paginación automática y además de forma más flexible que el resto de controles enlazados a datos.

Distintos layout en el mismo control

Aunque el control ListView no ofrece ninguna salida HTML predeterminada, eso no significa que incorpore un SmartTag que puede hacernos la vida más fácil.

Lo primero que tenemos que hacer es suministrar un control de origen de datos válido. En mi caso he optado por un control SqlDataSource con la siguiente estructura:

clip_image001[4]

Dejando a un lado las operaciones de edición, inserción y eliminación (que quedan fuera el ámbito de este post), las operaciones que podremos realizar a través del SmartTag del control ListView son:

·         Seleccionar un diseño predeterminado.

·         Habilitar la paginación.

En función del diseño predeterminado seleccionado, obtendremos automáticamente la siguiente estructura (aquí quiero puntualizar que “automáticamente” significa que se ha generado código por nosotros, no que el control ListView ofrezca una salida predeterminada).

Diseño

Salida

Cuadrícula

Como si fuera un GridView.

En mosaico

Lo más parecido podría ser un DataList, pero esta vista no tiene una equivalencia clara.

Lista con viñetas

Como si fuera un RadioButtonList o CheckBoxList.

Flujo

Como si fuera un FormView o DetailsView.

Fila única

Como si fuera un DataList con RepeatDirection en horizontal.

 

Cuadrícula

clip_image002[4]

En mosaico

clip_image003[4]

Lista con viñetas

clip_image004[4]

Flujo

clip_image005[4]

Fila única

clip_image006[4]

Cómo puedes ver, las opciones que automáticamente nos ofrece el SmartTag deberían ser suficientes para casi cualquier diseño. En cualquier caso, la flexibilidad del control ListView permite llevar a cabo casi cualquier diseño, combinando la estructura y repetición de elementos. Siendo así, los límites los ponemos nosotros y no el control (como sí ocurre en el resto de controles enlazados a datos).

Conociendo su estructura

El control ListView es un control enlazado a datos a través de plantillas.

Las plantillas disponibles son:

·         LayoutTemplate

·         GroupTemplate

·         GroupSeparatorTemplate

·         ItemTemplate

·         AlternatingItemTemplate

·         ItemSeparatorTemplate

·         SelectedItemTemplate

·         InsertItemTemplate

·         EditItemTemplate

·         EmptyDataTemplate

·         EmptyItemTemplate

Las plantillas más significativas son:

LayoutTemplate y GroupTemplate que definen la estructura contenedora de los elementos enlazados al control. Mientras que LayoutTemplate es obligatoria, GroupTemplate sólo es necesario si queremos agrupar elementos.

ItemTemplate y AlternatingItemTemplate definen el elemento actual. ItemTemplate es obligatoria y AlternatingItemTemplate simplemente cumple una función de elemento alterno en lo relativo a su diseño.

EmptyDataTemplate define el código que se mostrará si el control está vacío.

EmptyItemTemplate define el código que se mostrará si un elemento está vacío. Esto puede suceder si agrupamos con GroupTemplate, puesto que habría huecos vacíos en nuestro diseño.

Trabajando con ListView

Para nuestro ejemplo, queremos conseguir un diseño en mosaico, así que comencemos desde el principio y vayamos poco a poco:

LayoutTemplate es una plantilla obligatoria y que define el código HTML que envolverá a nuestro control. Se podría hablar de él como un contenedor o wrapper.

Además, también es obligatoria la plantilla ItemTemplate que representa el código HTML de un elemento en particular. El contenido de esta plantilla se repetirá n veces (tantos como registros tenga el control ListView).

Para “situar” nuestra plantilla de ItemTemplate dentro de LayoutTemplate, lo haremos con un control de servidor, esto es con el atributo runat=”server”  (da igual el control que sea aunque por convención se suele utilizar un PlaceHolder) con el atributo id igual a “itemPlaceholder” (si no nos gusta este nombre, podemos cambiarlo en la propiedad ItemPlaceholderID del control ListView).

    <asp:ListView ID="ListView1" runat="server" DataKeyNames="CompanyID,ProductID" DataSourceID="SqlDataSource1">

        <ItemTemplate>

            <td>

                <asp:Label runat="server" Text='<%# Eval("CompanyID") %>' />

                <br />

                <asp:Label runat="server" Text='<%# Eval("ProductID") %>' />

            </td>

        </ItemTemplate>

        <LayoutTemplate>

            <table border="1">

                <tr>

                    <asp:PlaceHolder ID="itemPlaceholder" runat="server" />

                </tr>

            </table>

        </LayoutTemplate>

    </asp:ListView>

 

La salida obtenida es una tabla con 5 celdas (por ahora estamos limitando la consulta de nuestro control SqlDataSource a 5 elementos):

clip_image007[4]

Como vemos, el control con id itemPlaceholder ha sido sustituido por todo el contenido de la plantilla ItemTemplate.

Antes decíamos que el control con id = “itemPlaceholder” suele ser un PlaceHolder por convención. En realidad, esta convención es casi forzada porque el control ListView “sustituirá” este control, con el contenido de la plantilla ItemTemplate. Si por ejemplo, escribimos este otro código, veremos como el resultado no es el esperado puesto que la sustitución dio al traste con nuestras intenciones:

        <LayoutTemplate>

            <table border="1">

                <tr runat="server" id="itemPlaceHolder">

                </tr>

            </table>

        </LayoutTemplate>

 

Y el resultado nos devuelve una tabla sin filas (sin la etiqueta tr que ha sido sustituida por ItemTemplate).

<table border="1">

<td>

                        <span id="ListView1_CompanyID_0">520</span>

<br />

<span id="ListView1_ProductID_0">/1</span>

</td>

                   

      

Continuando con nuestro ejemplo, vemos que es necesario que nuestra salida tenga más de 1 fila para lograr el efecto del mosaico.

Para lograr esto necesitamos “agrupar”.

Para agrupar tenemos disponible la plantilla GroupTemplate y la propiedad GroupItemCount del control ListView.

Con GroupTemplate definimos como será nuestra agrupación y con GroupItemCount establecemos cada cuantos elementos se producirá la agrupación.

Al igual que utilizamos un control de servidor con id=”itemPlaceholder” para representar donde se inyectaría la plantilla ItemTemplate, ahora utilizaremos un control de servidor con id = “groupPlaceholder” para decidir donde se inyectará la plantilla GroupTemplate (o el id que establezcamos en la propiedad GroupPlaceholderID).

Veamos cómo queda nuestro código de agrupación:

    <asp:ListView ID="ListView1" runat="server" DataKeyNames="CompanyID,ProductID" DataSourceID="SqlDataSource1"

        GroupItemCount="2">

        <ItemTemplate>

            <td>

                <asp:Label runat="server" Text='<%# Eval("CompanyID") %>' />

                <br />

                <asp:Label runat="server" Text='<%# Eval("ProductID") %>' />

            </td>

        </ItemTemplate>

        <GroupTemplate>

            <tr>

                <asp:PlaceHolder runat="server" ID="itemPlaceholder" />

            </tr>

        </GroupTemplate>

        <LayoutTemplate>

            <table border="1">

                <asp:PlaceHolder runat="server" ID="groupPlaceHolder" />

            </table>

        </LayoutTemplate>

    </asp:ListView>


Y la salida que genera es la siguiente:

clip_image008[4]

Ya casi tenemos nuestro mosaico, pero lo cierto es que no me gusta nada ese hueco vacío en la tercera fila que podría dar al traste con nuestro diseño, ¿Cómo le explico yo al diseñador que hay un hueco y no puedo hacer nada al respecto?

Para resolver esto tenemos la plantilla EmptyItemTemplate, que trabaja conjuntamente con la propiedad GroupItemCount para saber que en ese hueco realmente debería ir un elemento pero que no está disponible.

Si agregamos la plantilla EmptyItemTemplate, tenemos el siguiente resultado:

        <EmptyItemTemplate>

            <td>

                &nbsp;

            </td>

        </EmptyItemTemplate>

 

clip_image009[4]

En lo relativo a la estructura y diseño, nuestro mosaico ya está listo!

Paginación en ListView

Para completar este post, sólo nos queda implementar la paginación en nuestro mosaico.

Para paginar con el control ListView, necesitamos hacer uso del control DataPager.

Algunas de las ventajas de este control en lo relativo a su integración con ListView son:

  • Si el control DataPager está dentro del control ListView, la paginación será automática.
  • Si el control DataPager está fuera del control ListView, será necesario enlazar ambos, pero a cambio obtenemos una gran flexibilidad en nuestros diseños.
  • Puede haber cualquier número de controles DataPager enlazados a un ListView, trabajando de forma coordinada. Esto permitirá por ejemplo, controles de paginación tanto en la cabecera como en el pie de nuestro mosaico.

Para comenzar con la paginación, lo haremos incluyendo el control DataPager dentro del control ListView, tanto en la cabecera como en el pie.

<LayoutTemplate>

            <div>

                <asp:DataPager ID="dpTop" runat="server" PageSize="5">

                    <Fields>

                        <asp:NextPreviousPagerField />

                    </Fields>

                </asp:DataPager>

            </div>

            <table border="1">

                <asp:PlaceHolder runat="server" ID="groupPlaceHolder" />

            </table>

            <div>

                <asp:DataPager ID="dtBottom" runat="server" PageSize="5">

                    <Fields>

                        <asp:NextPreviousPagerField />

                    </Fields>

                </asp:DataPager>

            </div>

        </LayoutTemplate>


clip_image010[4]

A propósito del control DataPager, podemos observar cómo hemos establecido el tamaño de página con la propiedad PageSize. Además, hemos establecido el modo de paginación a “Anterior y Siguiente” con el elemento NextPreviousPagerField. Lógicamente, este elemento permite personalizar el texto, el tipo de los botones, etc.

Además de NextPreviousPagerField, también tenemos disponible NumericPagerField que ofrece una paginación con números de página (igualmente parametrizable). Ambos pueden convivir sin problemas como en el siguiente ejemplo:

clip_image011[4]

Si no podemos o queremos incluir controles DataPager dentro del control ListView, también podemos situarlos fuera y enlazarlos manualmente a través de la propiedad PagedControlID.

    <asp:DataPager ID="dtTop" runat="server" PageSize="5" PagedControlID="ListView1">

        <Fields>

            <asp:NextPreviousPagerField />

            <asp:NumericPagerField />

        </Fields>

    </asp:DataPager>

    <hr />

    <asp:ListView ID="ListView1" runat="server" DataKeyNames="CompanyID,ProductID" DataSourceID="SqlDataSource1"

        GroupItemCount="2">

    </asp:ListView>


clip_image012[4]

Por cierto, ¿Qué pasa si quiero mostrar el típico “Página 1 de 5, elementos 1 a 10 de 56”?

Esto significará que ni NextPreviousPagerField ni NumericPagerField satisfacen tus requerimientos, así que es momento de utilizar la plantilla TemplatePagerField que habilita una paginación personalizada.

Lo primero es saber de dónde obtenemos la información necesaria para forma la frase “Página 1 de 5, elementos 1 a 10 de 56”.

Toda esta información está disponible en el control DataPager y sus propiedades PageSize, StartRowIndex y TotalRowCount. Para acceder al propio control DataPager desde la plantilla TemplatePagerField, utilizaremos la propiedad Container.

clip_image014[4]

El código necesario para armar la cadena es:

            <asp:TemplatePagerField>

                <PagerTemplate>

                    Página

                    <%# Math.Ceiling((Container.StartRowIndex + 1) / Container.PageSize)%>

                    de

                    <%# Math.Ceiling(Container.TotalRowCount / Container.PageSize)%>, elementos

                    <%# Container.StartRowIndex + 1%>

                    a

                    <%# If(Math.Ceiling((Container.StartRowIndex + 1) / Container.PageSize) < Math.Ceiling(Container.TotalRowCount / Container.PageSize), Container.StartRowIndex + Container.PageSize, Container.TotalRowCount)%>

                </PagerTemplate>

            </asp:TemplatePagerField>


Si te digo la verdad me ha costado bastante escribir este código, no soy matemático y lo mismo se me ha colado algo, pero…

Por otro lado, si no haces el enlace a un control de origen de datos de forma declarativa, te tocará incluir algo de código para que funcione la paginación.

Primero, será recomendable que incluyas el control o controles DataPager fuera del control ListView (y que asocies ambos con la propiedad PageControlID).

Segundo, en el evento Page_Load tendrás que cargar inicialmente el control ListView.

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Not IsPostBack Then

            ListView1.DataSource = GetMyData()

            ListView1.DataBind()

        End If

    End Sub

 

Tercero, tendrás que agregar código al evento ListView_PagePropertiesChanging para controlar la paginación.

Private Sub ListView1_PagePropertiesChanging(sender As Object, e As System.Web.UI.WebControls.PagePropertiesChangingEventArgs) Handles ListView1.PagePropertiesChanging

        ' Establecer paginación en el control DataPager

        DataPager1.SetPageProperties(e.StartRowIndex, e.MaximumRows, False)

        ' Enlazar el control ListView a datos

        ListView1.DataSource = GetMyData()

        ListView1.DataBind()

    End Sub

 

Hasta aquí hemos llegado por hoy con el control ListView, pero como habrás podido observar es un control con grandes posibilidades y que permite una control absoluto sobre el HTML generado en el cliente.

Un saludo!

3 comentarios:

  1. mio no esta enlasado con nada, sino lleno el ListView con puro codigo, pero cuadno hago click no me pagina recien al hacer otro click recien me pagina. que puedo hacer para solucionar eso.

    ResponderEliminar