domingo, 31 de julio de 2011

Archivos de Configuración - Una introducción (1/3)

 

Introducción


Es muy común tener que definir parámetros en las aplicaciones que se desarrollan proporcionando cierta configuración que si bien se podría considerar casi estática (porque no cambia constantemente), si sea necesario prever la posibilidad de adaptación.

Un ejemplo muy claro de estos es la cadena de conexión a la base de datos, por lo general una vez instalada la aplicación no cambia, pero cuando se esta en la etapa de implementación seguramente sea necesario su adaptación al entorno.

Por lo general se busca que sea un lugar que impacte lo menos posible en el desarrollo, algunos tienden a crear una clase y colocar constantes o variables readonly dentro del propio código, pero el problema con esto es que requieren recompilar por completo el desarrollo, además de tener que actualizar cada cliente por un simple cambio de configuración.

También se busca un lugar estándar y conocido, algunos usan la registry de windows para definir configuración, quizás era una opción se algo mas viable cuando se contaba con Win XP, pero con Sistemas Operativos como ser Vista o Win 7 esto cambio bastante, el modelo de seguridad que estos imponen aplica restricciones a estas acciones, no haciendo tan cómoda la escritura en este sitio.

Entonces porque no usar la propuesta que hace .net al respecto, si se trata de los archivos de configuración, entre las ventajas que este presenta se pueden encontrar:

  • una lectura simple, ya que se basa en xml
  • fácil acceso y modificación (se puede editar con el notepad), por lo general este archivo se encuentra junto a la aplicación por lo que la seguridad debería permitir la escritura en esta carpeta

Entre los temas que se trataran en este articulo

1- Agregar un archivo de configuración a nuestro proyecto

2- Definir una sección key-value

2a- usando la sección appSettings

2b- definiendo una sección propia

 

1- Agregar un archivo de configuración a nuestro proyecto


Si bien es una acción sencilla, si recién se esta introduciendo en el desarrollo, puede que no resulte tan intuitivo encontrar los pasos para agregar un archivo de configuración.

Estando sobre el proyecto en el “Solution Explorer”, se acciona el menú que aparece con el boton derecho del mouse, seleccionado el menu Add –> New Item…

image

en el recuadro se seleccionara la opción:

image

Esto agregar un archivo de nombre App.config, que en una primer instancia tendrá solo un tag.

Empecemos por una tarea simple, la lectura de una cadena de conexión.

Para lograr esta tarea se necesitara la ayuda de una clase en particular, me refiero al 

ConfigurationManager

con esta clase tendremos acceso a los tag de información definidos, pero para hacer uso de la misma se requiere hacer referencia a la librería System.Configuration, los pasos para esta acción serian representados en la siguiente imagen:

image

Solo queda agregar el código que tomaría la información del archivo de configuración, usándolo luego con los objetos de ado.net para conectarse

image

En el punto 1 se define el “using” a la librería referenciada en el paso anterior (definiendo así su namespace), mientras que en el 2 se hace uso de la clase ConfigurationManager para tomar la cadena de conexión.

Por supuesto en el archivo de configuración se debió agregar la key que se define en el .config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="default" connectionString="Data Source=LecturaConnectionString\TestDb.sdf;Persist Security Info=False;"/>
  </connectionStrings>
</configuration>

2- Definir una sección key-value


Definir información del tipo key-value en el archivo de configuración puede realizarse de varias formas

  • usando la sección appsetting
  • por medio de una seccion custom creada por uno

 

2a- Usando AppSetting

Esta es la forma más simple y directa de definir valores simples en la configuración, solo basta con ingresar en la sección <appSettings> el tag “add” con cada item que se requiera.

En el ejemplo se observará la sección definida como:

<appSettings>
  <add key="1" value="Efectivo" />
  <add key="2" value="Tarjeta Credito" />
  <add key="3" value="Tarjeta Debito" />
  <add key="4" value="Cheque" />
  <add key="5" value="Transferencia Bancaria" />
</appSettings>

En el código, en el Form2, se trabaja con esta información con al ayuda de linq para cargar un combo

private void Form2_Load(object sender, EventArgs e)
{


    var result = (from configKey in ConfigurationManager.AppSettings.Keys.Cast<string>()
                  let configValue = ConfigurationManager.AppSettings[configKey]
                  select new
                  {
                      key = configKey,
                      value = configValue
                  }).ToList();

    
    cmbMediosPago.DisplayMember = "value";
    cmbMediosPago.ValueMember = "key";
    cmbMediosPago.DataSource = result;
    
}

Acceder a un ítem en concreto es tan simple como usar

ConfigurationManager.AppSettings[key]

 

private void cmbMediosPago_SelectionChangeCommitted(object sender, EventArgs e)
{
    string key = Convert.ToString(cmbMediosPago.SelectedValue);
    string value = ConfigurationManager.AppSettings[key];

    lblSeleccion.Text = string.Format("Se ha seleccionado\n Key:{0} \n Value:{1}", key, value);
}

2b- Definiendo una sección propia

El uso de la sección <appSettings> en algunas circunstancia puede resultar demasiado genérico y no brindar un lugar que sea claramente identificable para el negocio que se esta programando, esta sección al ser tan común se puede llenar rápidamente de items key-value no relacionados, haciendo difícil el mantenimiento.

Pero existe una alternativa a este problema y consiste en crear una sección propia para conserva estos pares key-value de forma personalizada, otorgando una visibilidad con sentido para la aplicación

En el código del Form3 se implementa la solución usando una sección definida por uno mismo, la cual aplica el mismo concepto key-value.

<configuration>
  <configSections>
    <section name="MediosPago" type="System.Configuration.DictionarySectionHandler" />
  </configSections>
  
  
  <MediosPago>
    <add key="1" value="Efectivo" />
    <add key="2" value="Tarjeta Credito" />
    <add key="3" value="Tarjeta Debito" />
    <add key="4" value="Cheque" />
    <add key="5" value="Transferencia Bancaria" />
  </MediosPago>
  
</configuration>

Para recuperar la información y listarla

private void Form3_Load(object sender, EventArgs e)
{

    var result = (from config in ((Hashtable)ConfigurationManager.GetSection("MediosPago")).Cast<DictionaryEntry>()
                 select new 
                 {
                     key = config.Key,
                     value = config.Value
                 }).ToList();

    cmbMediosPago.DisplayMember = "value";
    cmbMediosPago.ValueMember = "key";
    cmbMediosPago.DataSource = result;

    cmbMediosPago.SelectedIndex = -1;

}

Obtener un ítem basándonos en la key requiere recuperar la sección completa para luego si acceder al valor

private void cmbMediosPago_SelectionChangeCommitted(object sender, EventArgs e)
{
    string key = Convert.ToString(cmbMediosPago.SelectedValue);
    string value = ((Hashtable)ConfigurationManager.GetSection("MediosPago"))[key].ToString();

    lblSeleccion.Text = string.Format("Se ha seleccionado\n Key:{0} \n Value:{1}", key, value);
}

Por supuesto tanto en estos ejemplo hacer uso de las clase de configuración para tomar el valor del ítem seleccionado, no tiene un sentido practico, ya que el propio combobox proporciona ambos datos key y value (usando el SelectedValue y SelectedText), solo se realiza con fines ilustrativos para poder aplicar los conceptos de programación con el archivo de configuración.

 

Código de ejemplo


[C#] 
[VB.NET] 

domingo, 17 de julio de 2011

[Winforms] Singleton - Pasar datos entre formularios

 

Introducción


En mas de una oportunidad surgió el hecho de comunicar formulario y pasar información entre los mismos, cuando estos se invocan uno a otra la solución es relativamente simple, pero que sucede cuando las distintas invocaciones deben capturar y conservar los datos en cada paso que realizan.

Es aquí donde entra en juego un patrón bien simple de aplicar pero no por eso menos potente, hago referencia al singleton.

Para el presente artículo platearemos un escenario nada complicado, simplemente algunos forms donde se capturan datos números, pero esta información deberá ser usada en la ultima pantalla, la cual realiza el calculo con los datos recolectados en los pasos previos.

image

 

1- Singleton,  Pasar datos entre formularios


Implementar el patrón es muy simple, solo se deben aplicar ciertas reglas que abrigan a tomar la misma instancia.

public class RecolectarDatos
{
    private static RecolectarDatos datos;

    private RecolectarDatos()
    {
    }

    public static RecolectarDatos Instance()
    {
        if (datos == null)
        {
            datos = new RecolectarDatos();
        }

        return datos;
    }

    public int Dato1 { get; set; }
    public int Dato2 { get; set; }
    public int Dato3 { get; set; }

    public int Dato4 { get; set; }
    public int Dato5 { get; set; }
}

El primer punto es definir una instancia static del propia clase singleton (línea 3)

Segundo, definir el constructor de la clase como privado (línea 5), de esta forma no se podrán crear instancia de la clase, salvo desde dentro de la propia clase.

Tercero, definir un método o propiedad static, la cual será el punto central por donde se podrá recuperar una instancia de la clase (línea 9), en este punto es donde se valida y crea la instancia de la variable static definida mas arriba, es por esto que siempre se retorna esta misma instancia.

El resto del código pueden ser método, propiedades, otras instancias de clases, o sea se puede aplicar cualquier concepto de POO sin problemas, porque el patron singleton devuelve una única instancia, pero esta tiene comportamiento como cualquier otro objeto.

 

2 – Eventos, Informar de cambios entre formulario


Al llegar al ultimo formulario era necesario informar al primero que las operaciones se realizaron correctamente, pero entre estos formulario no hay visibilidad alguna, no se aplico ninguna pasamano de instancias entre los formularios, o algo que los comunique.

Es por eso que la subscripción a un evento genérico resulta ideal para avisar de una determinada acción sin ser necesario pasar instancias.

 

public static class CompleteEvents
{
    public delegate void CompleteHandler(CompleteEventArgs args);
    public static event CompleteHandler Complete;

    public static void RaiseEvent(int calculo)
    {
        if(Complete != null)
            Complete(new CompleteEventArgs(calculo));
    }
}

public class CompleteEventArgs : EventArgs
{
    public CompleteEventArgs(int calculo)
    {
        this.Calculo = calculo;
    }

    public int Calculo { get; set; }
}

El formulario principal se adjunta al evento definido en la clase, mientras que en el ultimo formulario es invocado el método RaiseEvent() que lanzara la acción.

Adicionalmente se define un argumento del evento para informar el resultado del calculo, igualmente en este caso por aplicar singleton, quizás no era necesario, ya que se podría haber tomado directamente los valores de cada dato recolectado y realizar nuevamente el calculo.

 

Código del ejemplo


[C#] 
[VB.NET] 

sábado, 2 de julio de 2011

[WinForm] Listar Archivos del Directorio seleccionado

 

Introducción

En el presente artículo se implementa un ejemplo sencillo de como listar directorios con su contenido, incluyendo además el icono asociado a la extensión del archivo.

Algunas pruebas

Si bien durante las primeras pruebas se hizo uso de la funcionalidad

Icon.ExtractAssociatedIcon

para obtener la imagen relacionada con la extensión del archivo, note que no siempre se recuperaba el icono correcto, es por eso que investigando algo mas en detalle encontré que por medio de las api se puede realizar esto mismo.

La implementación de esta se puede hallar en el archivo ExtractIcon.cs

Si bien en el código solo he dejado una de estas alternativas

private void LoadFileList(string path)
        {
            string[] files = Directory.GetFiles(path);

            lvFiles.Items.Clear();

            foreach (var item in files)
            {
                string extension = Path.GetExtension(item);

                if (!imlSmall.Images.ContainsKey(extension))
                {
                    Icon iconSmall = FileExplorer.ExtractIconClass.GetIcon(item, true);
                    imlSmall.Images.Add(extension, iconSmall);
                    Icon iconLarge = FileExplorer.ExtractIconClass.GetIcon(item, false);
                    imlLarge.Images.Add(extension, iconLarge);
                }

                ListViewItem listviewItem = new ListViewItem(Path.GetFileName(item), extension);
                lvFiles.Items.Add(listviewItem);

            }
        }

podrían cambiarse las líneas 13 a 16 para hacer uso del

Icon.ExtractAssociatedIcon()

y comprobar por uno mismo la diferencia en el icono que se obtiene.

 

Ejemplo de código

Debo remarcar que la implementación que se encuentre en la clase ExtractIcon.cs, no es de mi autoría, sino que use de guía ejemplo como ser

Extracting Icons from Files

ExtractIconClass.cs

Building a Better ExtractAssociatedIcon

 

[C#]
[VB.NET]

jueves, 23 de junio de 2011

[GridView] Eventos de controles contenidos en el GridView (1/2)

 

Introducción

En ciertas ocasiones puede ser necesario trabajar directamente con los eventos de los controles contenidos en el propio GridView, pero el problema planteado es como trabajar con el control que ejecuta la acción y al mismo tiempo poder acceder a la información de la fila del gridview que contiene a dicho control.

El articulo demuestra como conseguirlo de dos formas distintas:

- una implicara extender el control que se quiera utilizar

- la otra simplemente accederá al objeto que contiene al control que lanza el evento

En los ejemplos se hará uso de un control radiobutton como parte de gridview y será este quien ejecute el evento con el cual se necesitara trabajar.

 

Extendiendo el control

Para poder implementar este camino se necesitara definir una propiedad que será útil cuando se trabaje con el evento del control.

Esta propiedad no se dispone de forma estándar en el control, sino que deberá crearse un control que extienda del original.

public class CustomRadioButtonList : RadioButtonList 
    {
        //
        // La creacion del custom control es justamente para agregar esta propiedad 
        // que por defecto no posee el RadioButtonList
        //
        [DefaultValue("")]
        public string CommandArgument
        {
            get
            {
                string s = ViewState["CommandArgument"] as string;
                return s == null ? String.Empty : s;
            }
            set
            {
                ViewState["CommandArgument"] = value;
            }
        }

    }

Es importante mencionar que el uso de este nuevo control requiere de su declaración en la pagina

<%@ Register Assembly="GridViewEventosControles" Namespace="GridViewEventosControles"
    TagPrefix="cc1" %>

El siguiente paso sea definir del control que se ha creado dentro de la pagina.

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <cc1:CustomRadioButtonList ID="rblRespuesta" runat="server" 
            CommandArgument='<%#Container.DataItemIndex %>' 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </cc1:CustomRadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

La línea donde se usa el Container.DataItemIndex permite asigna el índice de cada row a la propiedad, para que pueda ser tomada desde el código al ejecutarse el evento.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    CustomRadioButtonList radiolist = sender as CustomRadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se toma el index de la row del gridview, el cual fue asociado en el CommandArgument
    //
    int rowindex = Convert.ToInt32(radiolist.CommandArgument);
    
    //
    // Sabiendo el index de la row con que se debe trabajar se pued recuperat el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[rowindex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

[C#]
[C# Skydrive]

 

Accediendo al objeto contenedor

Para esta implementación no se requiere el uso de ningún control adiciona, simplemente se hará uso de una propiedad que permite recuperar el contenedor del control.

Específicamente se hace referencia a NamingContainer

La definición del control en el grid será de forma normal

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <asp:RadioButtonList ID="rblRespuesta" runat="server" 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </asp:RadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

Pero el acceso al row que contiene el control es donde esta el truco.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    RadioButtonList radiolist = sender as RadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se recupera la row del GridView que contien el control
    //
    GridViewRow row = radiolist.NamingContainer as GridViewRow;
    
    //
    // Sabiendo el index de la row con que se debe trabajar se puede recuperar el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[row.RowIndex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

Es allí donde se usa la propiedad para obtener el row donde esta contenido el control que lanzo el evento, y el el index será utilizado para obtener el identificador de la entidad, en este caso de la pregunta a la cual se da la respuesta.

 

[C#]
[C# SkyDrive]

lunes, 20 de junio de 2011

[GridView] Aplicar color en las filas

 

Introducción

Este artículo plantea como aplicar color a las filas del gridview basándose en la definición de cierta regla previamente establecida.

En el ejemplo se tiene una lista de productos, la cual por “error” uno fue duplicado, la idea es poder detectar en que registros del grid se presenta este problema y remarcarlo con un color diferente para un fácil reconocimiento.

Se hará uso de linq trabajando junto a un dataset para poder aplicar la verificación de cada ítem en memoria.

El resultado obtenido será el visualizado en la imagen siguiente:

Determina color GridView

Para aplicar la lógica de colores en cada fila del grid será usado el evento RowDataBound, el mismo se lanza por cada row creada en el grid, es por eso que validando la información de la fila contra el origen de datos será posible determinar si la información esta repetida

 

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        string desc = e.Row.Cells[0].Text;

        int cant = (from item in ((DataTable)Session["datos"]).AsEnumerable()
                      where item.Field<string>("Descripcion") == desc
                      select item).Count();

        if (cant > 1)
        {
            e.Row.BackColor = Color.LightCoral;
        }
        
    }
}

 

[C#]
[C# SkyDrive]

[Winforms] Control global de Errores – Implementar Log

 

Introducción

He notado en varias oportunidades que en ciertas circunstancias surgen errores no controlador que son difíciles de rastrear

Estos es consecuencia de un descuido, al no agregar correctamente en todos los puntos importantes el try..catch, o simplemente por la lógica de la aplicación por se grande implicaría un gran esfuerzo poner en cada evento el control de errores.

Es por eso que definir un control global a nivel de aplicación podría ayudar, atrapando el problema y registrando que sucedió y donde, esta información es muy preciada cuando se esta perdido y no se sabe que produce el problema, mas cuando se esta en el entorno de producción y no se cuenta con una herramienta de debug

En este artículo se trataran los siguientes temas:

  1. Control global de errores
  2. Log usando System.IO.Log

 

1- Control Global Errores

El implementar la lógica para controlar globalmente los errores no es nada difícil, el truco esta en adjuntarse a un evento, concretamente:

Application.ThreadException

Un buen lugar para realizar esta asignación del evento es el archivos Program.cs, dentro del método Main()

[C#]

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

    Application.Run(new Form1());
}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
    MessageBox.Show(e.Exception.Message);
}

Para lograr esto mismo en vb.net requiere de algunos paso adicionales, ya que vb.net no brinda un acceso transparente al método Main, ni permite que este sea asignado como inicio de la aplicación, pero en este articulo

Winforms, realizar tareas antes de inicializar aplicación

explico como destrabar esta situación para poder así implementarlo como lo harían en c#, definiendo el Main() como inicio de la aplicación.

[VB.NET]

<STAThread()> _
Friend Shared Sub Main()

    Application.EnableVisualStyles()
    Application.SetCompatibleTextRenderingDefault(False)

    AddHandler Application.ThreadException, AddressOf Application_ThreadException

    Application.Run(New Form1())

End Sub

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    MessageBox.Show(e.Exception.Message)

End Sub

El código en ambos lenguajes preserva el mismo concepto, pero la forma en como se adjunta el evento difiera bastante.

Entonces, ahora si sucediera algo como lo reflejado en la imagen

imagen1

O sea, si se ingresara caracteres en el calculo, el no atrapar el error en un bloque try..catch, haría que la aplicación finalice de forma brusca cerrándose, pero al tener definido el evento ThreadException, entraría en acción tomando el error y mostrando el mensaje.

Con estos simples paso ya tenemos el control global de errores implementado.

 

2- Log usando System.IO.Log

Si bien en una primer instancia hacer uso de un mensaje podría ayudar a detectar el problema en la aplicación, este podría evolucionar en un log a un archivo para dejar tracking de lo sucedido, en este caso no solo se pondría el mensaje del problema, sino que además se podría agregar el StackTrace para poder analizar que métodos se fueron ejecutando hasta causar el fallo.

En este caso el log a un archivos será muy simple

[C#]

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            using(FileRecordSequence record = new FileRecordSequence("application.log", FileAccess.Write))
            {

                string message = string.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, 
                                                                                    e.Exception.Message, 
                                                                                    e.Exception.StackTrace);

                record.Append(CreateData(message), SequenceNumber.Invalid, 
                                                            SequenceNumber.Invalid, 
                                                            RecordAppendOptions.ForceFlush);
            }
        }


        private static IList<ArraySegment<byte>> CreateData(string str)
        {
            Encoding enc = Encoding.Unicode;

            byte[] array = enc.GetBytes(str);

            ArraySegment<byte>[] segments = new ArraySegment<byte>[1];
            segments[0] = new ArraySegment<byte>(array);

            return Array.AsReadOnly<ArraySegment<byte>>(segments);
        }

[VB.NET]

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    Using record As New FileRecordSequence("application.log", FileAccess.Write)

        Dim message As String = String.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace)

        record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush)
    End Using

End Sub


Private Shared Function CreateData(ByVal str As String) As IList(Of ArraySegment(Of Byte))

    Dim enc As Encoding = Encoding.Unicode

    Dim _array As Byte() = enc.GetBytes(str)

    Dim segments As ArraySegment(Of Byte)() = New ArraySegment(Of Byte)(0) {}
    segments(0) = New ArraySegment(Of Byte)(_array)

    Return Array.AsReadOnly(Of ArraySegment(Of Byte))(segments)

End Function

Ahora el evento global de errores tiene bastante mas código, en donde se arma un mensaje bastante mas útil, el cual será enviado a la funcionalidad de log para registrar el suceso.

Para recuperar el archivo, solo deben ir a la ubicación donde esta el .exe, en este caso debería esta en la carpeta \bin\Debug del proyecto.

Lo mas probable es que el archivo no se legible a simple vista porque este sistema de log trabaja con el concepto de entradas de registros, es por eso que se confecciono un formulario muy simple para poder recuperar la información de forma visual.

[C#] 
[VB.NET] 

domingo, 12 de junio de 2011

[ASP.NET][GridView] - Como seleccionar una fila

 

Introducción


He visto en reiteradas oportunidad que una operación simple como es el caso de operar con una fila de un GridView se puede transformarse en algo complejo, mas que nada motivado por la distintas formas que hay para realizar esta acción.

En este articulo veremos las algunas formas de lograrlo y como difieren las técnicas que se puede aplicarse.

Temas que se tratar:

1- Seleccionar una Row

a- Definiendo un CommandField

b- Usando un ImageButton y CommandName

c- Usando el evento RowCommand

2-Uso de DataKeyNames y DataKeys

   a- DataKeyNames con campos Múltiples

Para todos los casos planteados partiremos del mismo gridview, el cual se ira modificando para agregarle opciones y ver los distintos temas.

 

1- Seleccionar una Row


Existen varias formas de realizar una misma tarea, pero veremos aquí las dos mas simples y directas que se suelen encontrar cuando se necesita seleccionar un registro en el control gridview.

1a- Definiendo un CommandField

Iremos realizando los paso de forma visual así se comprende como proceder, remarcando luego como impacta esto en el html del grid

El primer paso será editar las columnas del GridView hasta visualizar el cuadro con las opciones de CommandField disponibles.

imagen1

imagen2

Para este caso en particular se agregara solo la opción de selección. Mediante las flechas laterales se puede posicionar el comando. También se dispone de distintos tipos de representación visual, como ser un Link, Button o Image.

imagen3

Para este caso usaremos un comando del tipo Image, por lo tanto se deberá definir la propiedad “SelectImageUrl”. Si se define del tipo Link y se quiere cambiar el texto, se usaría la propiedad “SelectText”.

El próximo paso será el de definir el evento de selección, para esto solo marcamos el gridview, y yendo a sus propiedades se podrá activar el evento SelectedIndexChanged

imagen4

El html resultante debería tener resaltadas las siguiente características

 imagen5

Con estos pasos ya estamos listos para capturar la acción de selección del gridview.

 

1b - Usando un ImageButton y CommandName

En esta alternativa se hará uso de un TemplateField, se procede de la misma forma del paso 1a, pero se agrega un item diferente

imagen6

Una vez que esta el témplate, se adapta modificando directamente en el html, incluyendo de esta forma el control ImageButton.

Es muy importante remarcar que el ImageButton deberá tener la propiedad CommandName=”Select” para que esta ejecute el evento SelectedIndexChanged

imagen7

A nivel de código de la pagina se encontrara la definición del evento

imagen8

Hay que aclarar que en este caso se uso un ImageButton para corresponder con la acción del punto 1a, en donde se define una imagen, pero si se requiere de un link solo será cuestión de usar un LinkButton, definiendo en este el CommandName=”Select”, es justamente el CommandName quien define que evento será lanzado al presionarse.

 

1c- Usando el evento RowCommand

Seguramente a estas alturas se preguntaran que cantidad de formas de hacer lo mismo, asi es, y para completarlo una opción extra.

Resulta que al definir un ImageButton (o LinkButton) en un TemplateItem y usar el CommandName=”Select” se habilita un evento adicionar para poder capturar esta acción, si es que el SelectedIndexChanged no nos convence.

Se trata del evento RowCommand.

Hay un pequeño detalle con este evento y se trata de la definición del CommandArgument para determinar que fila lanza la acción.

imagen9

[C#]

protected void gvPerson_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName == "Select")
    {
        //
        // Se obtiene indice de la row seleccionada
        //
        int index = Convert.ToInt32(e.CommandArgument);
        
        //
        // Obtengo el id de la entidad que se esta editando
        // en este caso de la entidad Person
        //
        int id = Convert.ToInt32(gvPerson.DataKeys[index].Value); 

    }

}

[VB.NET]

Protected Sub gvPerson_RowCommand(sender As Object, e As GridViewCommandEventArgs)
	
        If e.CommandName = "Select" Then
		'
		' Se obtiene indice de la row seleccionada
		'
		Dim index As Integer = Convert.ToInt32(e.CommandArgument)

		'
		' Obtengo el id de la entidad que se esta editando
		' en este caso de la entidad Person
		'

		Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(index).Value)
	End If

End Sub

En el ejemplo de la pagina WebForm4.aspx, se podrá probar como ambos eventos, tanto el RowCommand y el SelectedIndexChanged, pueden definirse, aunque lo normal es utilizar solo uno de estos.

 

2 - Uso de DataKeyNames y DataKeys


Una de las mejores técnicas usada para detectar que entidad se esta editando o seleccionado es por medio de id o código que esta tenga asignada, pero como logar hacerlo sin mostrar el identificador al usuario en una columna ?, es justamente el trabajo de estas dos propiedades que se consigue resolver el problema.

Si se presta atención al html este contaba con al definición de esta propiedad de nombre DataKeyNames

imagen10

Solo debe definirse que campo del origen de datos identifica a la entidad que se esta trabajando.

Cuando se lance el evento solo será cuestión de tomar la row que ejecuta la acción, y de esta, por el índice recuperar el valor del id de la entidad, en este caso el PersonID.

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Value);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Value)


End Sub

2a- DataKeyNames con campos Múltiples

En este ejemplo solo se utilizo un valor simple para identificar a la entidad, pero el CommandName puede definir mas de un campo de información.

Por ejemplo que sucede si se quiere enviar el PersonId y el Nombre, esto es tan solo un ejemplo para demostrar las funcionalidad, no tiene una aplicación práctica en este caso, ya que con solo el PersonID seria mas que suficiente.

Para definir el DataKeyNames en el grid es tan simple como separar los campos por una coma.

imagen11

y en el código solo se toma el valor de la propiedad Values

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id y el nombre  de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Values["PersonID"]);

    string nombre = Convert.ToString(gvPerson.DataKeys[row.RowIndex].Values["FirstName"]);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id y el nombre  de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Values("PersonID"))

	Dim nombre As String = Convert.ToString(gvPerson.DataKeys(row.RowIndex).Values("FirstName"))


End Sub

Ejemplo Código


Para el ejemplo se hizo uso de Visual Studio 2008 con SP1, el service pack es útil para poder hacer uso de Entity Framework y poder crear el ADO.NET Entity Data Model haciendo simple el acceso a la db.

La base de datos es Sql Server 2008 Express R2, y se encuentra el mdf dentro de la carpeta App_Data, pero se podría adjuntar al servicio de Sql Server o hacer uso del script que se encuentra en el proyecto de DataAccess.

 

 

domingo, 8 de mayo de 2011

[ASP.NET] GridView – Edición Empleados


Introducción


En esta ocasión se implementara al versión web de un articulo previo:
[WinForms] Edición Empleados – Grabar imagen en base de datos

Los puntos que se trataran con detalle serán:
- selección de un fila en el gridview
- eliminar un registro del grid
- visualizar una imagen que se encuentra dentro de la base de datos en un control de Image

 pantalla



1 – Selección  de una fila en el GridView


Existen varias formas de lograr este objetivo, en el ejemplo del este articulo aplique solo una de ellas haciendo uso del CommandField para definir las acciones sobre el grid, para ello hice uso de la opción visual:


 image


esto desplegara el dialogo:


 image


es aquí donde se definen las columnas entre ellas el CommandField, al seleccionarlo mostrara las propiedades de configuración, las propiedades de la sección “Behavior” es donde uno selecciona que botones quiere visualizar, es por eso que en este caso la propiedad “ShowSelecteButton” esta en True.

Además es importante la sección “Appearance” en donde se define “ButtonType” del tipo imagen y el “SelectImageUrl”, con la url del botones que se ve en el grid.
Concluida la definición de columnas y botones de acción, se debe especificar una propiedad muy importante en el grid, se trata del “DataKeyNames”, esta debería llevar el nombre la propiedad (o columna) del origen de datos que se usara como identificación de la entidad que se edita, en este caso como son empleado, será su id, definiéndose: DataKeyNames="IdEmpleado"

Este paso es importante ya que en los eventos se podrá recuperar sobre que entidad se debe aplicar la acción.
El botón de selección lanzara el evento “SelectedIndexChanging”, es por eso que será necesario definirlo en el grid: onselectedindexchanging="gvEmpleados_SelectedIndexChanging"
mientras que en el código de la pagina:

protected void gvEmpleados_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    int idempleado = Convert.ToInt32(gvEmpleados.DataKeys[e.NewSelectedIndex].Value);

    Response.Redirect(string.Format("EditarEmpleado.aspx?id={0}", idempleado));

}

Se hace uso del DataKeys para tomar el valor definido por el DataKeyNames, estas dos propiedades trabajan bien relacionadas entre ellas.
Lo último paso que queda es redireccionar a la pagina de edición.



2- Eliminar un registro


La operación de eliminar es muy parecida a la selección, solo cambia el comando usado para esto.
Al igual que la selección hay que configurar el CommandField habilitando la propiedad “ShowDeleteButton” en true.
Esta acción lanzara el evento “RowDeleting”, para lo cual especificamos su definición en el grid mediante: onrowdeleting="gvEmpleados_RowDeleting"
y luego en el código:

protected void gvEmpleados_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
    int idempleado = Convert.ToInt32(gvEmpleados.DataKeys[e.RowIndex].Value);

    EmpleadosDAL.Eliminar(idempleado);

    CargarGrid(); //luego de eliminar se recarga el grid
}

Aquí también se hace uso del DataKeys para recuperar el id de la entidad que lanzo la acción.


3- Visualizar imagen


Cuando la imagen se encuentra dentro de la base de datos es necesario usar un intermediario para poder asignarla al control Image, y se haga visible al usuario.
Además todo esto sin generar archivos temporales que permitan el acceso a la imagen.
En este caso es el handler quien nos brinda esta ayuda, si se observa el código del mismo se ve muy simple, se recupera la entidad del empleado, y a continuación si contiene una imagen asociada se poniéndola en el Response, sino hay imagen se envía una por defecto, la cual indicara que no esta disponible (líneas: 21-23).

public class HttpImageHandler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        //
        // Se recupera la entidad empleado
        //
        int id = Convert.ToInt32(context.Request.Params["id"]);

        EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(id);

        //
        // Arma el contexto que enviara la imagen en el response
        // se usa el nombre del empleado para el nombre del archivo que se envia
        //
        context.Response.Clear();
        context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", empleado.Nombre)); 
        context.Response.ContentType = "image/jpg";

        byte[] imagenEmpleado = empleado.Imagen;
        if (empleado.Imagen == null)
            imagenEmpleado = File.ReadAllBytes(context.Server.MapPath("Imagenes/NoDisponible.jpg"));

        //
        // Se escribe en el response la imagen asociada al empleado
        //
        context.Response.BinaryWrite(imagenEmpleado);
        context.Response.End();
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

Esto es tanto usado por el grid para recuperar las imágenes que lista, como así también por cualquier otro control individual que necesites mostrar la imagen del empleado.
Es por eso que en el webform “EditarEmpleado.aspx”, cuando se carga el empleado todos sus datos se asignan directo a los controles, menos el control Image que recibe una url al handler para que tome de allí la imagen que debe mostrar (línea: 18)

private void CargarEmpleado(int id)
{
    EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(id);

    if (empleado == null)
    {
        imgEmpleado.ImageUrl = "Imagenes/NoDisponible.jpg";
        return;
    }

    lblIdEmpleado.Text = Convert.ToString(empleado.IdEmpleado);
    txtNombre.Text = empleado.Nombre;
    txtApellido.Text = empleado.Apellido;
    txtFechaNacimiento.Text = empleado.FechaNacimiento.ToShortDateString();
    
    AsignarEstudios(empleado);

    imgEmpleado.ImageUrl = string.Format("imagen.ashx?id={0}", id);
}


Código del artículo



El ejemplo fue desarrollado con Visual Studio 2008, y base de datos Sql Server 2008 R2 Express.
Dentro de la carpeta “script” del proyecto “DataAccess” encontrara un archivo .sql que define la estructura de datos.

[C#]
[VB.NET]

[jQuery] RadioButton y CheckBox

 

Introducción

Después del artículo donde se analizo la forma de trabajar con controles de lista como ser los combos y listbox

[jQuery] Trabajo con ListBox y Combos

seguiremos con otro grupo de controles igual de importantes: Radio Buttons y Checkbox.

Listado de temas

  1. RadioButton
    1. Recuperar ítem Seleccionado
    2. Asignar Selección
    3. RadioButtonList
  2. CheckBox
    1. Recuperar ítem Seleccionado
    2. Asignar Selección
    3. CheckBoxList

 

1- RadioButton

1.1 – Recuperar ítem Seleccionado

En las siguientes líneas se observara algunas de las formas posibles en que se puede recuperar el ítem seleccionado en un radio button.

 

function btnEj1SeleccionUsandoClass_OnClick() {

    var valor = $('.Ej1radio:checked').val()

    $('#Ej1Resultado').html(valor);
}

function btnEj1SeleccionUsandoName_OnClick() {

    var valor = $("input[name='Ej1radio']:checked").val()

    $('#Ej1Resultado').html(valor);
}

function btnEj1SeleccionUsandoTabla_OnClick() {

    var valor = $("#Ej1Table :radio:checked").val()

    $('#Ej1Resultado').html(valor);
}

Básicamente en todas se hace uso del selector :checked para tomar los radio marcados por el usuario, al saber que solo uno puede seleccionarse, se recupera el valor mediante el método val()

1- se usa una clase asignada a los distintos radio button, por el uso del punto, este es el selector de clase de jquery

2- se selecciona por medio de atributo name, todos los radio deben tener el mismo

3- se selecciona la tabla que contiene los controles , luego los radio (mediante :radio), hay que mencionar que si en la misma tabla existe otro grupo de radio button este tipo de selección no podría ser aplicado, porque tomaría todos los radio sin discriminar cada agrupación

1.2 – Asignar selección

La asignación de un valor especifico a los radio se reduce a cambiar el atributo checked.

function btnEj2SeleccionarConTabla_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $("#Ej2Table :radio[value='" + valor + "']").attr('checked', true);

}


function btnEj2SeleccionarPorName_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;
        
    $(":radio[name='Ej2radio'][value='" + valor + "']").attr('checked', true);

}

En ambos caso se asigna el atributo “checked” a true para que este se desmarque, como los radio trabajaban en grupo no hace falta desmarcar al resto, ya que esto se hace automáticamente.

1- se selecciona la tabla que contiene el radio y se usa como filtro el valor ingresado en el textbox

2- se usa el nombre que define al grupo de radio buttons, luego sobre estos filtrar por el valor

También existen otras formas de seleccionar un ítem:

function btnEj2SeleccionarAtributoJavascript_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    var option = $(":radio[name='Ej2radio'][value='" + valor + "']");

    if (option.length > 0)
        option[0].checked = true;

}

function btnEj2SeleccionarConForEach_OnClick() {
    
    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $.each($("#Ej2Table :radio"), function() {

        if (this.value == valor) {
            this.checked = true;
        }

    });

}

1- se busca el option que coincide, pero la acción de checkear se logra por medio de la propiedad provista por javascript. El usar el ítem cero [0], no es porque la selección devuelve varios resultados en la búsqueda en un array, sino que se esta tomando el control javascript de la selección de jquery.

2- aquí la búsqueda se realiza mediante un loop por todos los radio, comparando su valor con el buscado, el que coincida será marcado

1.3 – RadioButtonList

En esta sección se hará uso de control propuesto por asp.net para representar un conjunto de radios.

Si se analiza con el “IE Developer Tools”, al cual se accede presionado F12, se podrá inspeccionar el html generado por el control de lista de radio buttons

Como se observa la opción esta compuesta por el input común de html, pero la descripción esta dentro de un label

function btnEj3SeleccionUsandoName_OnClick() {

    var option = $(":radio[name='rdlEj3']:checked");
    var texto = $('label', option.parent());

    var msg = 'texto: {0}, valor: {1}'.format(texto.html(), option.val());

    $('#Ej3Resultado').html(msg);
}

Es por eso que luego de tomar la option seleccionado se usa el parent() para subir un nivel, ahora estaremos posicionado en el tag <td>, y dentro de este se recupera el label, de esta forma se obtendrá la descripción del option marcado.

Algo interesante aquí es que en la línea

$('label', option.parent())

se esta usando un selector de un selector, o sea se buscara solo el label que este dentro de ese tag <td> y no en todo el sitio, es por eso que se separa por coma, a la izquierda iría la selección del ámbito donde aplica el selector de la derecha.

2- CheckBox

2.1 - Recuperar ítem Seleccionado

En la selección del checkbox se necesita algo mas de código, ya que pueden ser varios los ítems marcados.

En todos los ejemplos se recorren los checks elegidos y se vuelca a una array para mostrar el resultado en pantalla, aquí se aplica la misma técnica que en el artículo previo, cuando se analizo el ListBox.

function btnEj1SeleccionUsandoClass_OnClick() {

    var list = new Array();

    $.each($('.Ej1check:checked'), function() {

        var msg = 'valor: {0}'.format($(this).val());
        list.push(msg);

    });

    $('#Ej1Resultado').html(list.join('
'));
}

function btnEj1SeleccionUsandoName_OnClick() {
    
    var list = new Array();

    $.each($("input[name='Ej1check']:checked"), function() {

        var msg = 'valor: {0}'.format($(this).val());
        list.push(msg);

    });

    $('#Ej1Resultado').html(list.join('
'));
    
}

function btnEj1SeleccionUsandoTabla_OnClick() {

    var list = new Array();

    $.each($("#Ej1Table :checkbox:checked"), function() {

        var msg = 'valor: {0}'.format($(this).val());
        list.push(msg);

    });

    $('#Ej1Resultado').html(list.join('
'));
    
}

1- se hace uso de la clase asignada a los ckeck para determinar cuales fueron marcados, al igual que los radio se usa el punto de jquery como selector

2- el mismo nombre es usado en cada check que forma parte del grupo, este es usando este como selector, el name es un atributo por eso es que se define entre []

3- se selecciona la tabla que contiene los checks, recorriendo los seleccionados

2.2 - Asignar Selección

Aquí también hay varias formas de lograrlo al igual que los radio, pero a diferencia del control anterior si es necesario un paso previo que limpie la selección previa, es por eso que se hace uso del metodo attr() que asigna el atributo checked en false

$("#Ej2Table :checkbox").attr('checked', false);

puede variar la forma en que se aplica la selección de jquery, pero la idea es obtener todos los checks y de un golpe desmarcarlos.

En estos ejemplo se puede ingresar en el TextBox varios valores separados por coma, es por eso que se recorre cada numero ingresado para tomar el check que mapea con este valor y marcarlo. Se logra este objetivo por medio del $.each y el split() de la cadena de valores ingresados.

 

function btnEj2SeleccionarConTabla_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $("#Ej2Table :checkbox").attr('checked', false);
    
    $.each(valor.split(','), function() {
    
        $("#Ej2Table :checkbox[value='" + this + "']").attr('checked', true);

    });

}


function btnEj2SeleccionarPorName_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $(":checkbox[name='Ej2check']").attr('checked', false);

    $.each(valor.split(','), function() {

        $(":checkbox[name='Ej2check'][value='" + this + "']").attr('checked', true);

    });

}

1- se hace uso de la tabla para tomar los check que están contenidos

2- se recuperan los check por el atributo name asignado al grupo

 

function btnEj2SeleccionarAtributoJavascript_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $(":checkbox[name='Ej2check']").attr('checked', false);

    $.each(valor.split(','), function() {

        var option = $(":checkbox[name='Ej2check'][value='" + this + "']");
        
        if (option.length > 0)
            option[0].checked = true;
    });

}

3- se hace uso de la propiedad checked de javascript para marcar el control, aquí la selección recupera el check concreto, pero luego con el uso de [0] se toma el control puro en javasscript que contiene esa propiedad checked

Remarco que la propiedad checked no es parte de jquery, este usa el attr() para cambiar el valor, solo javascript posee esta propiedad, por eso el uso del [0] en el resultado del selector de jquery

 

function btnEj2SeleccionarConForEach_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;
    
    $(":checkbox[name='Ej2check']").attr('checked', false);
    
    
    $.each($("#Ej2Table :checkbox"), function() {

        var check = this;
        
        $.each(valor.split(','), function() {

            if (check.value == this)
                check.checked = true;

        });

    });

}

4- se aplica la misma técnica del punto 3, pero se realiza un doblo loop, primero por cada check en la tabla y luego por los valores ingresados. No digo que esta sea la forma mas optima de implementarlo, pero me pareció interesante mostrarlo para conocer como unir dos ciclos.

 

function btnEj2SeleccionarUniendoValores_OnClick() {

    var valor = $('#txtEj2').val();

    if (valor == null)
        return;

    $(":checkbox[name='Ej2check']").attr('checked', false);

    var valuesList = Array();

    $.each(valor.split(','), function() {

        var value = "[value='{0}']".format(this);
        valuesList.push(value);

    });

    $(":checkbox[name='Ej2check']").filter(valuesList.join(',')).attr('checked', true);

}

5- esta ultima técnica de selección es bastante particular ya que arma un array de filtros que es ejecutado mediante el método filter() de jquery sobre la selección de todos los checks. Es necesario marcar que cada ítem del filtro deberá ser separado por comas.

2.3- CheckBoxList

Al igual que con el RadioButtonList, es una buena idea inspeccionar el html resultante del render

Algo interesante que se puede observar es que el control rendiza una tabla de nombre “rdlEj3” que podría ser usada para seleccionar los check contenidos en esta.

function btnEj3SeleccionUsandoName_OnClick() {
   
    var options = $("#rdlEj3 :checkbox:checked");

    var list = new Array();

    $.each(options, function() {

        var option = $(this);
        var valor = $(this).parent().attr('hiddenValue');
        var texto = $('label', option.parent());

        var msg = 'texto: {0}, valor: {1}'.format(texto.html(), valor);
        list.push(msg);

    });

    $('#Ej3Resultado').html(list.join('
'));
}

En este caso se toma los check dentro de la tabla que asp.net crea para contener estos controles, tomando de esta solo los tildados.

Algo que debe marcarse es que el CheckBoxList no ingresa el valor de cada ítem, es por eso que en el código .net en el evento Page_Load cuando se asignan los ítems, se pusieron unas líneas que agregan el valor del ítem

foreach (ListItem item in rdlEj3.Items)
{
    item.Attributes["hiddenValue"] = item.Value; 
}

Esto se rendizan en el html dentro de un tag <span>, es por eso que en las líneas 9 al 11 se hace uso del parent() para subir un elemento al seleccionado y recuperar esta información.

El control base que provee asp.net solo crea el atributo value=”on” como puede verse en la imagen, el atributo “hiddenValue” es creado al en el loop de cada ítem luego de cargar la lista.

 

Ejemplo de código

 

viernes, 29 de abril de 2011

[jQuery] Trabajo con ListBox y Combos

 

Introducción

Este artículo se pretende continuar lo empezado en:

[jQuery] Por donde comenzar

En esta ocasión trataremos un conjunto diferente de controles, estos muy usados en un desarrollo web.

En este caso particular trataremos controles de lista: ListBox y Combos. Notando cuan similares son a la hora de trabajarlos.

Selección de Ítems

En los ejemplos indicados como 1 y 2, se recuperan los ítems marcados por parte del usuario, pudiendo manipularlos y trabajar con la información.

 

        function btnEj1Seleccion_OnClick() {

            var list = new Array();

            $.each($('#Ej1Select :selected'), function() {
               
                var msg = 'texto: {0}, valor: {1} 
'.format($(this).text(), $(this).val());
                list.push(msg);

            });

            $('#Ej1Resultado').html(list.join(''));
            
        }

        function btnEj2Seleccion_OnClick() {
           
            var optionSelected = $('#Ej2Select :selected');

            var msg = 'texto: {0}, valor: {1}'.format(optionSelected.text(), optionSelected.val());

            $('#Ej2Resultado').html(msg);

        }

Los selectores usados en ambos casos son idénticos, solo lo diferencia la selección múltiple que puede brindar un ListBox, es por eso que aplica un loop por cada ítem marcado por el usuario.

En este código hay que marcar algunos puntos de interés:

La función $.each() recorre la selección de una lista de ítems y aplica una función por cada uno de ellos.

En este caso en concreto se recorre cada ítem seleccionado de la lista, o sea se obtiene cada <option> tomando de este el valor y el texto. Es importante notar que esta función tiene acceso a cada ítem por medio del this, pero se trata de un objeto javascript y no jquery, es por eso que se aplica nuevamente un selector $(this), para tener acceso a los métodos que permiten acceder recuperar la información.

Con respecto al selector, este accede al control por medio del sus id, por eso el uso del #, pero de este se filtra además los seleccionados usando el :selected.

Por fuera de la función se crea un array de javascript para ubicar cada dato marcado, se inserta el texto resultante de cada item por medio de push(), y al final se lo une por medio del join(), para lograr un solo string.

Seguramente se habrá notado además el uso del una función format() para reemplazar en el texto ciertas posiciones con un dato en concreto, bien esta función en si no es estándar de javascript, sino que se encuentra definida en utils.js, y básicamente aplicaría como un método de extensión para los tipos de datos string.

El trabajo con combos es idéntico a la lista, pero solo se toma un ítem accediendo de forma directa, no requiere ser recorrido sus ítems, por eso es algo más simple aplicar los selectores.

 

Agregar / Remover Ítems

La sección marcada para el Ejemplo 3, representa la operación de agregar y remover elementos en una lista.

        function btnEj3Agregar_OnClick() {

            var ej3Texto = $('#txtEj3Texto');
            var ej3Valor = $('#txtEj3Valor');

            var newItem = $('<option/>').text(ej3Texto.val()).val(ej3Valor.val());
            $("#Ej3Select").append(newItem);

        }
        
        function btnEj3AgregarDespuesSeleccion_OnClick() {

            var itemsSelected = $('#Ej3Select :selected');

            if (itemsSelected.length == 0) {
                alert('Debe seleccionar un item');
                return;
            }
                
            var ej3Texto = $('#txtEj3Texto');
            var ej3Valor = $('#txtEj3Valor');

            var newItem = $('<option/>').text(ej3Texto.val()).val(ej3Valor.val());
            newItem.insertAfter(itemsSelected[0]);

        }
        

        function btnEj3Seleccion_OnClick() {
        
            var list = new Array();

            $.each($('#Ej3Select :selected'), function() {

                var msg = 'texto: {0}, valor: {1} 
'.format($(this).text(), $(this).val());
                list.push(msg);

            });

            $('#Ej3Resultado').html(list.join(''));
            
        }

        function btnEj3RemoverSeleccion_OnClick() {

            $.each($('#Ej3Select :selected'), function() {

                $(this).remove();

            });
        }

El agregar una nueva opción requiere que el html de <opción> sea creado, es por eso que se define este en el selector, para luego disponer de método de text() y val() para asignar la información proveniente del input del usuario.

Una vez creado el nuevo ítem solo se hace un append() a la lista existente, también se podrían usar métodos como ser insertBefore() o insertAfter() para controlar la ubicación del nuevo elemento, pero esto requiere que se indique que elemento será tomado como objetivo. En el ejemplo al hacer uso de estos métodos se debe asegurar que al menos un ítem esta marcado para que funcione correctamente, si mas de uno se ha seleccionado solo se toma el primero.

El remover un ítem es muy simple, solo se recorre la selección y se aplica el remove(), al igual que en la selección, el this representa cada ítem en el loop, aquí también es necesario aplicar nuevamente el selector para tener acceso a los método de jquery implementa.

Trabajar con combos es idéntico a las listas, solo que se opera con un solo ítem a la vez, el ejemplo 4 implementa justamente estas acciones en un combo:

function btnEj4Agregar_OnClick() {

    var ej4Texto = $('#txtEj4Texto');
    var ej4Valor = $('#txtEj4Valor');

    var newItem = $('<option/>').text(ej4Texto.val()).val(ej4Valor.val());
    $("#Ej4Select").append(newItem);

}

function btnEj4Seleccion_OnClick() {

    var optionSelected = $('#Ej4Select :selected');

    var msg = 'texto: {0}, valor: {1}'.format(optionSelected.text(), optionSelected.val());

    $('#Ej4Resultado').html(msg);

}

function btnEj4RemoverSeleccion_OnClick() {

    $('#Ej4Select :selected').remove()

}

Algo que quizás no he marcado en los otros ejemplos es que al utilizar algo como esto

var optionSelected = $('#Ej4Select :selected');

se debe comprender que se esta asignado a la variable la selección del objeto completo, es por eso que luego se puede acceder a la funcionalidad provista por jquery.

 

Asignar Selección

En algún momento seguramente se requiera marcar un valor de la lista o combo sin que el usuario realice la acción, sino que será por medio de código, por suerte jquery hace esto muy simple, implementándose de igual forma no importa de que lista se trate.

En los ejemplos 5 y 6, se puede apreciar la implementación:

function btnEj5SeleccionarPorValor_OnClick() {

    var valor = $('#txtEj5').val();
    
    $('#Ej5Select').val(valor);

}

function btnEj6SeleccionarPorValor_OnClick() {

    var valor = $('#txtEj6').val();

    $('#Ej6Select').val(valor);

}

Si se escribe un valor correcto en el TextBox, solo hará falta asignar el val() al selector del control y eso es todo.

Listas ASP.NET

Hasta ahora hemos estado trabajando en los ejemplos con controles html, pero se debió a que operar con estos o con el control de lista o combo del asp.net es idéntico.

En el ejemplo 7 se podrá apreciar justamente este punto:

 

        function btnEj7Agregar_OnClick() {

            var ej7Texto = $('#txtEj7Texto');
            var ej7Valor = $('#txtEj7Valor');

            var newItem = $('<option/>').text(ej7Texto.val()).val(ej7Valor.val());
            $('#<%=Ej7Select.ClientID%>').append(newItem);

        }

        function btnEj7Seleccion_OnClick() {

            var list = new Array();

            $.each($('#<%=Ej7Select.ClientID%> :selected'), function() {

                var msg = 'texto: {0}, valor: {1} 
'.format($(this).text(), $(this).val());
                list.push(msg);

            });

            $('#Ej7Resultado').html(list.join(''));

        }
        
        function btnEj7RemoverSeleccion_OnClick() {

            $.each($('#<%=Ej7Select.ClientID%> :selected'), function() {

                $(this).remove();

            });
        }

Aplicar selectores a un control de asp.net solo requiere el uso de la propiedad ClientID, si es que se usa el id del control para acceder al mismo, esto se aplica por si asp.net renombra esta propiedad al realizar el render del html que envía al cliente.

 

Código de Ejemplo