DropDownList con OptionGroup en ASP.NET Web Forms

En un proyecto reciente he tenido la necesidad de agrupar los elementos que muestra un control DropDownList. Normalmente – con simple HTML - para conseguir esta funcionalidad podemos hacer uso de una etiqueta <select> con elementos option marcados con <optiongroup>, tal como podemos ver en el siguiente código HTML.

<select>
    <optgroup label="Europa">Europa</optgroup>
        <option>España</option>
        <option>Francia</option>
    <optgroup label="America">America</optgroup>
        <option>Uruguay</option>
        <option>Argentina</option>
</select>

El problema es que incomprensiblemente (si alguien conoce el motivo que me lo cuente!) el control de ASP.NET DropDownList, utilizado para renderizar elementos select de HTML no permite ninguna opción de agrupamiento.  aunque esta sea una opción estandar de HTML – enlace a W3C.

Para solucionar el problema tenemos varias opciones – si eres un programador “viejuno” seguro que pensaras en controlar el renderizado del control al más puro estilo de ASP clásico, es decir con un script en el archivo aspx, algo así …

<select>
<%foreach (var item in collection)
  {%>
<!--Aqui pintamos el optgroup o option según corresponda-->
<% }%>
</select>

Pero de este modo estamos generando el odiado “código espagueti” que tanto criticamos en ASP clásico, y además nos vemos obligados a repetir nuestro código por cada elemento select que queramos agrupar en nuestra aplicación.

No parece la mejor opción …

Sin duda es preferible crear un nuevo control – al que hemos lllamado DropDownListWithOptionGroup – que herede de DropDownList de forma que ya tengamos implementada toda la funcionalidad del control DropDownList “de serie”. El control es extremadamente sencillo, tan solo heredamos

namespace ControlsExtensions
{
    /// <summary>
    ///     Control para ser renderizado con DropDownListAdapter
    ///     Permite agrupar los elementos de un DropDownList en grupo
    /// </summary>
    public class DropDownListWithOptionGroup : DropDownList { }
}

Por supuesto para poder utilizar el control en nuestras páginas lo registramos nuestro archivo web.config

<system.web>
        <pages>
            <controls>
                <add tagPrefix="asp" namespace="ControlsExtensions" assembly=""/>
            </controls>
        </pages>
</system.web>

La pregunta es inmediata ¿como y donde modificamos el comportamiento del control para que incluya los elemento optgroup al renderizarse?

La respuesta es creando un adaptador especifico para el control. Para modificar la forma en la que el control se renderiza tenemos que sobreescribir el método RenderContents. EL código es el siguiente:

namespace ControlsAdapters
{
    public class DropDownListAdapter :
           System.Web.UI.WebControls.Adapters.WebControlAdapter
    {
        protected override void RenderContents(HtmlTextWriter writer)
        {
            ControlsExtensions.DropDownListWithOptionGroup list = 
                this.Control as ControlsExtensions.DropDownListWithOptionGroup;
            string currentOptionGroup;
            List<string> renderedOptionGroups = new List<string>();
            foreach (ListItem item in list.Items)
            {
                if (item.Attributes["OptionGroup"] == null)
                {
                    RenderListItem(item, writer);
                }
                else
                {
                    currentOptionGroup = item.Attributes["OptionGroup"];
                    if (renderedOptionGroups.Contains(currentOptionGroup))
                    {
                        RenderListItem(item, writer);
                    }
                    else
                    {
                        if (renderedOptionGroups.Count > 0)
                        {
                            RenderOptionGroupEndTag(writer);
                        }
                        RenderOptionGroupBeginTag(currentOptionGroup,
                                                  writer);
                        renderedOptionGroups.Add(currentOptionGroup);
                        RenderListItem(item, writer);
                    }
                }
            }
            if (renderedOptionGroups.Count > 0)
            {
                RenderOptionGroupEndTag(writer);
            }
        }
        private void RenderOptionGroupBeginTag(string name,
                     HtmlTextWriter writer)
        {
            writer.WriteBeginTag("optgroup");
            writer.WriteAttribute("label", name);
            writer.Write(HtmlTextWriter.TagRightChar);
            writer.WriteLine();
        }
        private void RenderOptionGroupEndTag(HtmlTextWriter writer)
        {
            writer.WriteEndTag("optgroup");
            writer.WriteLine();
        }
        private void RenderListItem(ListItem item,
                     HtmlTextWriter writer)
        {
            
            writer.WriteBeginTag("option");
            writer.WriteAttribute("value", item.Value, true);
            if (item.Selected)
            {
                writer.WriteAttribute("selected", "selected", false);
            }
            foreach (string key in item.Attributes.Keys)
            {
                writer.WriteAttribute(key, item.Attributes[key]);
            }
            writer.Write(HtmlTextWriter.TagRightChar);
            HttpUtility.HtmlEncode(item.Text, writer);
            writer.WriteEndTag("option");
            writer.WriteLine();
        }
    }
}

Bien, ya tenemos nuestro control y un adaptador para renderizar el control – ahora tenemos que decirle al motor de ASP.NET que utilice el nuevo adaptador para renderizar el control. Esto lo conseguimos añadiendo un archivo .browser a nuestro proyecto – el archivo debe estar ubicado en la carpeta App_Browsers y en nuestro caso lo hemos llamado BrowserFile.browser. El contenido de este archivo es el que relaciona nuestro control con su adaptador de renderizado – es decir , establecemos la relación entre el control DropDownListWithOptionGroup y el DropDownListAdapter:

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter 
                controlType="ControlsExtensions.DropDownListWithOptionGroup" 
                adapterType="ControlsAdapters.DropDownListAdapter" />            
        </controlAdapters>
    </browser>
</browsers>

Para probar nuestro control creamos una página sencilla, utilizando nuestro control:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Sample.aspx.cs" Inherits="Sample" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:DropDownListWithOptionGroup runat="server" ID="ddPaises">
            <asp:ListItem Value="EXP" Text="España" OptionGroup="Europa">
            </asp:ListItem>
            <asp:ListItem Value="FRA" Text="Francia" OptionGroup="Europa">
            </asp:ListItem>
            <asp:ListItem Value="URU" Text="Uruguay" OptionGroup="America">
            </asp:ListItem>
            <asp:ListItem Value="ARG" Text="Argentina" OptionGroup="America">
            </asp:ListItem>
        </asp:DropDownListWithOptionGroup>
    </div>
    </form>
</body>
</html>

El resultado de la ejecución es el siguiente:

image

Referencias:

www.codeproject.com : http://www.codeproject.com/KB/custom-controls/DropDownListOptionGroup.aspx?msg=2256992

http://weblogs.asp.net/scottgu : http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx

Saludos y hasta la próxima, DJK

Pedro  Herrarte  Sánchez
DropDownList con OptionGroup en ASP.NET Web Forms
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:09/01/2011
Última actualizacion:09/01/2011
Visitas totales:6888
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com