Archive for Octubre, 2009

Casos y Cosas ASP.NET MVC-SelectList-DropdownList

Octubre 29th, 2009

Hace algunos días estaba con otro proyecto en MVC, y mientras trabajaba con un formulario se dio algo bastante extraño. Tenia un DropDownList al cual queria llenar con 3 opciones y sus correspondientes Valores de esta manera:

<select name="status" id="status">
<option value="E">Empty</option>
<option value="F">Full</option>
<option value="E/F" selected="selected">Empty/Full</option>
</select>

En MVC cuando se necesita representar data en formularios de esta manera generalmente se utiliza un patrón ViewModel, una clase que se encarga de hacer el cambio de data pura (ids de BD) a algo entendible por el usuario por ejemplo yo como desarrollador se que el cogido “E” en BD representa Empty, pero el usuario puede no saberlo y no vamos a enviarle solo la letra E como opción en el DropDownList, asi que hay que hacer la traducción con un ViewModel.

Asi que cree una clase llamada EventViewModel con estas dos propiedades:

public Event Eventx { get; set; }
public SelectList status { get; set; }


y un constructor que recibe como parámetro la data pura (que por lo general es un clase mapeda de una BD), y realiza los pases necesarios para realizar la traducción al construirse a si misma.

public EventViewModel(Event Event)
{
Eventx = Event == null ? new Event() : Event;
status = new SelectList(pl, "value", "text", Event == null ? pl.First().value : Event.status.Trim());
}


status va a alojar mi objeto de tipo Selectlist que toma como parametros la lista pl, el nombre del elemento de la lista pl que actua como value del dropdown, (en mi caso casualmente también lo llame value), asi mismo el texto que sera el que el usuario vera desplegado y por ultimo el elemento que sera seleccionado por defecto, que como ven tiene un inline IF donde verifica el valor de Event (en mi caso si event es null es porque se trata de un Create Event) entonces simplemente selecciono el primero de la lista, en caso contrario selecciono la propiedad status del evento Event.

Con esto en su lugar podemos pasar a la vista y decirle:

<% = Html.DropDownList("status",Model.status)%>

Aquí Model es de tipo EventViewModel, y status de tipo SelectList así que en este punto todo debería funcionar, pero no es así..

El Problema..
Por alguna razón el DropDownList siempre aparece con el primer elemento seleccionado, sin importar lo que diga el SelectList(status), que deberia ser el que indique lo que debe o no estar seleccionado..
Entonces cual es el problema bueno si miramos la pequeña documentacion del helper DropDownList veremos que para el primer parametro string name dice:

The name of the form field and used as a key to look up possible options. If ViewData[name] Implements IEnumerable of System.Web.Mvc.SelectListItem

En pocas palabras el nombre que especifiques como primer parametro sera el name y id del elemento html generado y además sera la llave para buscar en el ViewData algun elemento de ese nombre que implemente IEnumerable of System.Web.Mvc.SelectListItem. Ahora no se si yo lo interpreto mal pero lo que no te dice es que ese mismo nombre sera utilizado para buscar el valor del selected Value para el DropDownList en el ViewData. Y este ultimo punto es el problema, ya que aunque no declaramos explicitamente un ViewData asi:
ViewData["status"]
Si tenemos una definición de ViewData.Model.status , que es donde alojamos el elemento SelectList(recordando que model es de tipo EventViewModel).

Eso quiere decir que cada vez que el DropDownList es generado busca algun elemento en el ViewData Con el nombre “status” y asume que el valor que aparezca aqui (sin importar el tipo que sea) sera el valor que debe aparecer seleccionando en el DropDownList, ignorando totalmente lo que dice el IEnumerable. Lo que deduzco es que como encuentra un ViewData.Model.status y determina que el valor de este no se encuentra en la lista pl simplemente no agrega el selected=”selected” a ningún elemento y el navegador por defecto al no ver el selected=”selected” por ningun lado selecciona el primer valor.

Mi solución fue simplemente cambiar el nombre de la propiedad del EventViewModel de status a statusx, así el helper no tendrá mas opción que tomar el valor del modelo de tipo Model.statusx que le especifique explícitamente acá:

<% = Html.DropDownList("status",Model.statusx)%>

Entradas Relacionadas:

  • No hay Entradas Relacionadas

jQuery plugin: Autocomplete con ASP.NET MVC

Octubre 3rd, 2009

Una de las cosas que mas gustan de ASP.NET MVC es la posibilidad de utilizar librerias como jQuery de manera muy facil, vamos a utilizar el plugin Autocomplete para jQuery diponible en http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/.

Antes que nada para poder utilizar el plugin debemos referenciar los archivos correspondientes en nuestro SiteMaster, en la declaración del head de la pagina colocamos:

    <%--jQuery Core --%>
    <script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
    <%--end jQuery Core--%>
    <%--jQuery Autocomplete--%>
    <link href="../../Scripts/jquery.autocomplete.css" rel="stylesheet" type="text/css" />
    <script src="../../Scripts/jquery.autocomplete.js" type="text/javascript"></script>
    <%--end jQuery Autocomplete--%>

Ya que tenemos las referencias, vamos a crear el controlador que nos servirá como proveedor de datos:

public ActionResult Completer(string q)
        {
            if (q != "")
            {
                NWDataContext db = new NWDataContext();
                var suggest = from i in db.Customers
                                  .Where(e => e.ContactName.ToLower().StartsWith(q.ToLower()))
                                   select (i.ContactName + "|" + i.CustomerID);
                return Content(String.Join("\n", suggest.ToArray()));
            }
            return new EmptyResult();
        }

Esta ActionResult recibe como parámetro un string q, que sera enviado por el Autocomplete Plugin al momento que el usuario empiece a llenar el TexBox. Verificamos si la q no esta vacia (”" – solo por si acaso), instan ciamos nuestro proveedor de datos o repositorio, en mi caso es un DataContext de LINQtoSQL, y realizo la busque en la tabla Customers donde el contact name empiece con q.

NOTA: pase todo a Lower para evitarme un posible dolor de cabeza con el case sensitive.

Al final retorno un arreglo con este formato para cada elemento:
Ana Trujillo|ANATR
Antonio Moreno|ANTON
Ann Devon|EASTC
Aria Cruz|FAMIA
André Fonseca|GOURL
Annette Roulet|LAMAI
Alexander Feuer|MORGK
Alejandra Camino|ROMEY
Art Braunschweiger|SPLIR
Anabela Domingues|TRADH

Ustedes no necesariamente deben utilizar este tipo de estructura, también pueden utilizar JSON si lo desean.

Con este método nos basta para realizar muchas cosas, vamos a implementar 5 variantes de este autocomplete:

  • Autocomplete Simple.
  • Autocomplete Multiples Selecciones.
  • Autocomplete Incluyendo el ID del elemento elegido en un campo Oculto.
  • Autocomplete Forzando la seleccion de uno de los sugeridos.
  • Autocomplete Con Imagenes (Fancy).

AutoComplete Simple

AutoComplete Simple - jQuery Plugin

AutoComplete Simple - jQuery Plugin


Lo que corresponde de HTML, generar el texbox con la ayuda de los helpers de MVC.

<span>Autocomplete Simple</span>
    <hr />
    <% =Html.TextBox("Clientes") %>

Y setear el Autocomplete dentro de un SCRIPT TAG.

<script type="text/javascript">
        $(function() {
 
 
            $('#Clientes').autocomplete(' <% =Url.Action("Completer","Home") %>', {
                width: 300,
                formatItem: formatItemNoID,
                formatResult: formatResult
            });
    });
    </script>

$(’#Clientes’).autocomplete setea el comportamiento del input con ID Clientes, para que sea autocompletado.
El primar parametro del autocomplete es la url, que como ven utilice el helper de MVC para que me genere un link hacia el ActionResult “Completer” del Controlador “Home”, la q es adjuntada como queryString al URL que le proporcionen, al final seran links de este tipo:
http://localhost/Home/Completer?q=a&limit=10&timestamp=1254590546937, el limit es la cantidad de resultados que serán sugeridos al usuario por defecto 10 elementos.
El segundo parámetro es una colección de opciones con figurables que trae el plugin, no entrare en detalle ya que todas las opciones son bastante digeribles a la primera, pero si les dejo una url donde pueden ver todas las opciones disponibles: http://docs.jquery.com/Plugins/Autocomplete/autocomplete#url_or_dataoptions.

Lo que si voy a explicar son las funciones para parsear el resultado del autocomplete:

            function formatItem(row) {
                return row[0] + " (<strong>id: " + row[1] + "</strong>)";
            }
            function formatItemWIMG(row) {
                return "<img src='../../Content/img/es.jpg'/> " + row[0];
            }
            function formatItemNoID(row) {
                return row[0];
            }
            function formatResult(row) {
                return row[0].replace(/(<.+?>)/gi, '');
            }

Lo que realizan estas funciones es dar forma a la lista de sugerencias del autocomplete, ya sea que queremos ver imágenes, formatear el texto, en general la forma en que se ve la lista.

Autocomplete Multiples Selecciones
Acá la diferencia es que se pueden seleccionar mas de un elemento, separados por comas.

Multiples Selecciones - jQuery Plugin

Multiples Selecciones - jQuery Plugin


<span>Autocomplete Multiples Selecciones</span><br />
    <hr />
    <% =Html.TextArea("ClientesMultiples") %>
    <br />
    <br />

$('#ClientesMultiples').autocomplete(' <% =Url.Action("Completer","Home") %>', {
                width: 300,
                multiple: true,
                formatItem: formatItemNoID,
                formatResult: formatResult
            });

La diferencia esta al configurar el autocomplete, ahora la opción multiple es TRUE.

Autocomplete Incluyendo el ID del elemento elegido en un campo Oculto
Algunas veces lo que necesitamos es el ID del cliente, así que ahora cuando configuramos el autocomplete utilizamos el evento .result (se ejecuta cuando el usuario selecciona algún elemento de la lista sugerida).

Autocomplete con ID - jQuery Plugin

Autocomplete con ID - jQuery Plugin

Ahora cuando el usuario selecciona Ana, ademas de completar la palabra en el primer texbox, el ID de Ana sera colocado en la casilla de la derecha.

Setear el ID del elemento selecionado

Setear el ID del elemento selecionado


<span>Incluyendo el ID en un campo Oculto</span>
    <hr />
    <% =Html.TextBox("ClienteName") %> <% =Html.TextBox("hiddenID", null, new { style = "width:50px", disabled = "disabled" })%>
    <br />
    <br />


$('#ClienteName').autocomplete(' <% =Url.Action("Completer","Home") %>', {
                width: 300,
                formatItem: formatItem,
                formatResult: formatResult
            });
            $('#ClienteName').result(function(event, data, formated) {
                $('#hiddenID').val(data[1]);
            });

En esta sección le agregue el evento result, y acá le indico que cuando el usuario seleccione algún elemento, envié el id seleccionado al campo oculto.

Porque data[1]?
data representa el elemento seleccionado, al parsearlo data[0] venrdia siendo el nombre del cliente y data[1] el ID del mismo. Recuerden que en el controlador devuelvo un array de esta forma:
Nombre del Cliente | ID
Que en este contexto es:
Data [0] | Data[1]

Autocomplete Forzando la seleccion de uno de los sugeridos
SI lo que queremos es que el usuario no nos este inventando nombres de clientes, podemos setear el autocomplete para que solo acepte elementos existentes dentro de los sugeridos, si el usuario escribe cualquier otra cosa que no hace match con algún nombre de cliente existente, se resetea el texbox.
En lo personal no me agreda mucho esta opcion, me parece algo agresivo con el usuario limpiar todo lo que ha escrito sin enviarle algún mensaje de lo que esta pasando.

Para lograr este efecto simplemente colocamos la opcion mustMatch: true.

$('#ClientesForced').autocomplete(' <% =Url.Action("Completer","Home") %>', {
                width: 300,
                mustMatch: true,
                formatItem: formatItem,
                formatResult: formatResult
            });

Autocomplete Con Imagenes (Fancy)
Ya como últimos punto, si queremos lograr algo mas llamativo imaginen que tuviésemos a disposición una imagen del rostro de los clientes, entonces podemos desplegar en las sugerencias junto con el nombre una imagen del cliente.

AutoComplete con imgs - jQuery Plugin

AutoComplete con imgs - jQuery Plugin


<span>Autocomplete Con Imagenes</span><br />
    <hr />
    <% =Html.TextBox("ClienteFace") %>


$('#ClienteFace').autocomplete(' <% =Url.Action("Completer","Home") %>', {
                width: 300,
                formatItem: formatItemWIMG,
                formatResult: formatResult
            });


Acá lo único distinto es la función que utilizamos para darle formato a los elementos formatItemWIMG.

Pueden descargar el proyecto de ejemplo aqui

Entradas Relacionadas: