Edición elegante de fechas con jQuery Datepicker y MVC, paso a paso

ASP.NET MVC Las plantillas de edición, incorporadas con la versión 2 de ASP.NET MVC, permiten crear muy fácilmente interfaces de edición reutilizables, y asociarlas a tipos de datos concretos.

En el caso de las fechas se trata de una práctica muy habitual, puesto que la edición por defecto para este tipo de campos casi nunca es suficiente y siempre tendremos necesidad de modificar este comportamiento.

Es decir, pongamos una entidad del Modelo como la siguiente:

public class Persona
{
    [Required(ErrorMessage="Dime el nombre, anda")]
    public string Nombre { get; set; }
 
    [Required(ErrorMessage="Hey, este dato es fundamental")]
    [DisplayName("Fecha de nacimiento")]
    public DateTime FechaNacimiento { get; set; }
}

Si generamos desde Visual Studio la vista de creación o edición por defecto de esta entidad, obtendremos un código, más o menos como el siguiente:

Formulario de edición de la entidad

(Por cierto, ya he hablado por aquí en otra ocasión sobre cómo generar el código correcto del andamiaje de edición para las entidades del Modelo con propiedades de tipo DateTime, decimal o double).

Ejecutando ahora la solución y accediendo a esta vista de edición podremos comprobar que el resultado está bastante lejos del que seguro desearíamos obtener. En particular, la fecha se introduce en un cuadro de texto bastante desarbolado y muy poco profesional:

 Edición de fechas por defecto 

Vamos a ver paso a paso cómo mejorar esto utilizando los templated helpers, una característica introducida en ASP.NET MVC 2 que nos brinda un mecanismo de creación de plantillas de visualización y edición de propiedades muy potente y reutilizable.

Datepicker en acciónConcretamente utilizaremos el widget Datepicker, incluido en el framework jQuery UI, que además de permitir la introducción manual de fechas con el formato correcto, permite la selección visual a través de un bonito calendario.

El post es un poco largo, pues iremos muy despacio y comentando cada paso, pero una vez sepáis hacerlo no tardaréis más de cinco minutos en activar este editor para todos los controles de introducción de fechas de vuestro sitio web :-)

1. Descargamos jQuery UI

El primer paso es hacernos con la infraestructura necesaria para implementar la solución, por lo que debemos descargar la librería jQuery UI. Aunque es un procedimiento sencillo, hay que tener cuidado con algunos detalles.

image

La página de downloads de jQuery UI permite personalizar el contenido del paquete a descargar. Por ejemplo, si únicamente vamos a utilizar el widget “DatePicker”, podríamos desmarcar el resto de componentes para aligerar el peso del paquete final.

Para pruebas, lo más sencillo es dejar marcados todos los elementos, así no nos dejamos por detrás nada; en producción sería mejor descargar una versión más ligera, sólo con los componentes que vayamos a utilizar.

También debemos elegir el tema visual a emplear. Esto es importante, pues determina la apariencia visual de los widgets empleados. En nuestro caso, seleccionaremos el tema “Start”, aunque hay una gran variedad de ellos a elegir, e incluso desde el mismo sitio web puedes personalizarlos y crear temas nuevos.

Proyecto MVC tras incluirle los archivosUna vez hecho esto, pulsamos el botón download y salvamos en nuestro equipo el archivo .zip generado. De él tendremos que extraer:

  • por un lado, la hoja de estilos (.css)  e imágenes utilizadas por el tema elegido, disponibles dentro de la carpeta /css/start del archivo .zip que hemos descargado. Descomprimimos esta carpeta sobre el directorio /Content de nuestro proyecto MVC.
  • el archivo de scripts, jquery-ui-1.8.1.custom.min.js, disponible en la carpeta /js del fichero .zip, que descomprimimos sobre la el directorio /Scripts de nuestro proyecto.

Por último, debemos descargar el archivo de localización al español del widget, que nos permitirá mostrar las fechas y mensajes en nuestro idioma, que debemos dejar también en el directorio /Scripts del proyecto.

La captura de pantalla adjunta muestra cómo quedaría el proyecto MVC tras incluir estos elementos.

2. Modificamos la vista

Si acudimos a la vista de edición de la entidad, observamos que la propiedad de tipo fecha se edita a través del control generado por el siguiente código:

<%= Html.TextBoxFor(model => model.FechaNacimiento) %>

De esta forma estamos diciéndole al framework, poco más o menos: “eh, genera un cuadro de texto para editar la propiedad FechaNacimiento del modelo”, es decir, explícitamente le indicamos que la fecha de nacimiento sea editada con un text box.

Pero como en este caso lo que nos interesa no es utilizar un cuadro de texto sino un editor más potente, modificamos dicha línea, dejándola como sigue:

<%= Html.EditorFor(model => model.FechaNacimiento) %>

Y ahora la cosa ha cambiado; la sustitución de TextBoxFor por EditorFor viene a ser como hubiéramos cambiado la frase anterior por “eh, genera un editor específico para la propiedad FechaNacimiento del Modelo”.

Este editor específico se trata de una vista parcial, una plantilla que podemos implementar en un archivo .ascx a nuestro antojo, incluyendo todos aquellos elementos que consideremos interesantes a la hora de ayudar al usuario a editar dicha propiedad.

Existen diversas vías para indicar a ASP.NET MVC la plantilla de edición a utilizar en cada caso, aunque aquí utilizaremos la forma más potente: la selección basada en el tipo de datos, un mecanismo compartido por la tecnología de datos dinámicos (Dynamic Data), que simplifica enormemente la construcción de interfaces.

Su funcionamiento es muy simple:

  • Durante la ejecución de la llamada al helper Html.EditorFor(), el framework examinará la propiedad que deseamos editar, determinando que se trata, en este caso, de un tipo DateTime.
  • Si existe una plantilla llamada “DateTime.ascx” en un directorio definido por convención, renderizará su contenido como editor para la propiedad.
  • En caso contrario, intentará generar un editor por defecto.

La potencia que tiene este método es que podemos reutilizarlo a lo largo y ancho de la aplicación. Siempre que tengamos que editar una propiedad de tipo fecha, si utilizamos EditorFor(), se utilizará la plantilla que hayamos creado para ello. De lo más DRY, vaya :-)

Otra ventaja es que esta solución es aplicable incluso a toda clase de tipos de datos. Es decir, podemos crear plantillas como “Boolean.ascx” para crear editores para valores lógicos, o “Persona.ascx” si lo que queremos es definir la plantilla para la edición de la clase Persona del Modelo.

3. Creamos la plantilla de edición

Al tratarse de componentes de la Vista, las plantillas de edición de un tipo concreto las crearemos dentro de la carpeta /Views del proyecto, y más concretamente en:

  • /Views/{ControladorActual}/EditorTemplates, si lo que queremos es que la plantilla sea utilizada exclusivamente dentro del contexto de un controlador.
  • /Views/Shared/EditorTemplates, lo más frecuente, si queremos que la plantilla de edición sea utilizada desde cualquier punto del sistema.

Utilizaremos la segunda opción, creando en dicha carpeta la plantilla “DateTime.ascx” utilizando la opción “Añadir vista” del menú contextual sobre la misma:

image

Observad que dado que vamos a implementar un editor para la clase DateTime, estamos creando una vista parcial fuertemente tipada hacia dicho tipo. El código para la plantilla DateTime.ascx es el siguiente:

Código de la plantilla DateTime.ascx

Comentamos rápidamente lo que podemos ver ahí:

  • La vista parcial hereda de ViewUserControl<DateTime>, pues es el tipo de datos sobre el que va a operar. La propiedad Model a nivel de vista será por tanto una fecha.
  • Lo primero que hacemos es generar, ahora sí, un cuadro de edición para editar el valor suministrado. Aquí destacan varios detalles:
    • en primer lugar, el nombre o identificador de la propiedad está como cadena vacía. Esto es así porque es la vista que utiliza esta plantilla la que le suministrará el nombre; tened en cuenta que el mismo editor podría ser utilizado para varias propiedades, por lo que no tendría sentido introducir aquí el nombre de ninguna de ellas.
    • A continuación asignamos un valor inicial al control, que obtenemos formateando la fecha.
    • Por último, añadimos varios atributos HTML al tag <input>, como el tamaño máximo del texto o la clase CSS que queremos utilizar.
  • Incluimos también en la vista un texto fijo indicando el formato de la fecha (dd/mm/aaaa).
  • Finalmente, creamos el script que hace la magia. Detectamos si están presentes tanto jQuery como Datepicker y, en caso afirmativo, inicializamos el plugin para que tome el control del cuadro de edición que acabamos de generar.

4. Incluimos las referencias a las librerías de scripts y estilos

Por último, sólo nos falta añadir las referencias a las librerías y estilos en el punto donde nos interese:

  • si vamos a utilizar el editor u otras características de jQuery, o UI por toda la aplicación, lo más razonable es incluirlas en la página maestra del sitio,
  • en caso contrario, si se trata de un uso puntual, podemos añadirlas exclusivamente a la vista donde se necesite.

En cualquier caso, el código será el mismo. Ojo, que es importante tener en cuenta el orden de las inclusiones:

<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js")%>" 
        type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery-ui-1.8.1.custom.min.js")%>" 
        type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.ui.datepicker-es.js")%>" 
        type="text/javascript"></script>
<link href="<%= Url.Content("~/Content/start/jquery-ui-1.8.1.custom.css")%>" 
        rel="stylesheet" type="text/css" />

Como podéis observar, estamos utilizando el helper Url.Content para obtener la ruta absoluta de los recursos.

5. Y… ¡voilà!

¡Hemos terminado! Si ejecutamos ahora nuestra aplicación, podremos comprobar que tanto el aspecto como la usabilidad del resultado han mejorado notablemente:

image

Y lo mejor es que a partir de este momento todas las fechas que vayamos a editar en nuestra aplicación podrán beneficiarse de este editor simplemente introduciendo en las vistas la llamada al helper Html.EditorFor()  :-).

Como se puede comprobar, se trata de una solución muy elegante, productiva, fácil de mantener y respetuosa con el patrón MVC, de la que seguro no podréis prescindir en cuanto la utilicéis y veáis lo sencillo que resulta crearos vuestra propia biblioteca de editores personalizados.

Ah, dejo el proyecto de demostración, para Visual Studio 2008, en mi Skydrive, por si queréis comprobar “en directo” lo bien que queda 😉

Publicado en: Variable not found.

Parámetros opcionales: úsense con precaución

Los parámetros opcionales son una interesante ayuda que hace tiempo que está presente en otros lenguajes como Visual Basic .NET, y ha sido introducida en la versión 4.0 de C#, para alegría de muchos.

A grandes rasgos, esta característica nos permite especificar valores por defecto para los parámetros de nuestros métodos, ahorrándonos tiempo de codificación:

class Program
{
    public static void Main(string[] args)
    {
        Muestra();            // Imprime 1,1
        Muestra(8);           // Imprime 8,1
        Muestra(3,4);         // Imprime 3,4
        Console.ReadKey();
    }
 
    static void Muestra(int a=1, int b=1)
    {
        Console.WriteLine(a + "," + b);
    }
}

Desde siempre, ignorante de mí, había pensado que esto no era más que una triquiñuela del compilador, un azucarillo sintáctico destinado a facilitarnos la creación de sobrecargas de forma rápida, pero, ahora que lo he estudiado algo más a fondo, resulta que no es así. De hecho, los parámetros opcionales están soportados a nivel de plataforma, y funcionan de forma algo extraña (o al menos diferente a lo que podía esperarse), por lo que es conveniente conocerlos bien para no cometer determinados errores.

En primer lugar, me ha llamado la atención que la detección de la ausencia de parámetros en la llamada y la asignación de los valores por defecto no la realiza el método en el que se han definido. Es decir, sobre el ejemplo anterior, no es el método Muestra() el que comprueba si se han suministrado valores para los parámetros a y b, ni el que le asigna los valores por defecto en caso contrario. Esta “comprobación” se realiza en tiempo de compilación (!).

Esto lo demostramos muy fácilmente si descompilamos esta misma aplicación con ayuda del imprescindible Reflector, que nos mostrará el siguiente código:

class Program
{
    public static void Main(string[] args)
    {
        Muestra(1, 1);
        Muestra(8, 1);
        Muestra(3, 4);
        Console.ReadKey();
    }
 
    public static void Muestra([Optional, DefaultParameterValue(1)] int a, 
     [Optional, DefaultParameterValue(1)] int b)
    {
        Console.WriteLine(a + ", " + b);
    }
}

Como se puede observar, se ha generado un método Muestra() cuyos parámetros incluyen atributos que indican su opcionalidad y el valor por defecto en cada caso.

Pero lo curioso está en el método Main(), desde donde se hacen las llamadas, el que podemos observar que las invocaciones a Muestra() incluyen valores para todos los parámetros, como si se tratara de constantes especificadas directamente por el desarrollador.

Por tanto, no hay nada mágico en los métodos con parámetros opcionales, ni sobrecargas, ni código de comprobación o asignación insertado de forma automática. Es el propio compilador el que, en el momento de generar el código IL, extrae los valores por defecto de los parámetros no especificados en la llamada examinando los atributos presentes en la signatura y los introduce en la invocación.

Y es aquí justo donde hay que tener cuidado al utilizar los parámetros opcionales. Dado que el valor de los parámetros se determina en tiempo de compilación y se incluyen como constantes en el código IL generado, pueden producirse efectos no deseados si trabajamos con distintos ensamblados.

Veámoslo con un ejemplo, que, aunque poco real, creo que ilustrará un escenario donde los parámetros opcionales podrían jugarnos una mala pasada.

En el siguiente código, perteneciente al ensamblado LogicaNegocio.dll, vemos un método CalculaImporteconIva(), que retorna un importe aplicándole el impuesto (IVA) correspondiente:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.16)
    {
        return importe + importe*iva;
    }
}

Así, podemos tener un ensamblado externo, pongamos ERP.exe, que haga uso de este método de la siguiente forma:

public void muestraDesglose(double importe)
{
    double importeTotal = logica.CalculaImporteConIVA(importe)
    // Mostrarlo...
}

En el momento de compilación de ERP.exe, la llamada anterior quedará en el ensamblado resultante igual que si hubiéramos hecho esta llamada:

 
    double importeTotal = logica.CalculaImporteConIVA(importe, 0.16)

Si ahora se produce una subida de IVA (como lamentablemente va a ocurrir en breve), acudiríamos a modificar el valor por defecto del parámetro iva en el método CalculaImporteConIva() y recompilaríamos LogicaNegocio.dll:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.18)
    {
        return importe + importe*iva;
    }
}

Sin embargo, si no recompilamos ERP.EXE desde éste seguiríamos enviándole el valor anterior (0.16, recordad que este valor aparece como constante en el ensamblado), lo que podía provocar algún problema. Es decir, si queremos mantener la coherencia del sistema, nos veríamos obligados a recompilar todos los ensamblados que referencien LogicaNegocio.dll.

Conclusión: en métodos públicos, y especialmente en aquellos que serán consumidos desde ensamblados externos, es conveniente utilizar parámetros opcionales sólo cuando los valores constantes sean “verdades universales”, como las constantes matemáticas o datos que con toda seguridad no van a cambiar.  No es buena idea utilizarlos para reflejar valores variables o constantes de lógica de negocio, con posibilidades de cambio aunque sean remotas.

Por último, comentar que aunque este post está centrado en C#, todas estas precauciones son igualmente válidas para Visual Basic .NET.

Publicado en: Variable not found

¿Bug (sin importancia) en la plantilla de edición de MVC 2 con DateTime, decimal y double?

¿Habéis observado que al generar una vista de edición tipada sobre una entidad que contiene propiedades decimales o de fecha, Visual Studio os genera una llamada extraña al helper TextBoxFor()?

Vamos a verlo en detalle. Partimos de una entidad del Modelo como la siguiente:

Entidad del Modelo

Generamos ahora el andamiaje con Visual Studio, utilizando la opción correspondiente del menú contextual, indicando que se trata de una vista de edición, sobre la entidad Persona:

Añadiendo una vista tipada de edición

El código generado por el entorno será el habitual. Sin embargo, si nos fijamos en el control de edición que Visual Studio ha generado para la propiedad FechaNacimiento, podremos ver que contiene un parámetro extraño:

TextBoxFor con propiedades de tipo fecha

Durante mucho tiempo no me ha llamado la atención probablemente por la costumbre de verlo en llamadas al helper TextBox(), donde el segundo parámetro se utilizaba para especificar el valor por defecto del <input>. Además, dado que la edición de fechas suele realizarse de forma algo más seria (en breve hablaremos de eso por aquí) y el código generado suele eliminarse, tampoco me había fijado demasiado.

Sin embargo, en cuanto observas un poco las distintas sobrecargas de TextBoxFor(), es fácil darse cuenta de que esta llamada no es correcta. De hecho, si observamos la porción del código fuente HTML generado relativa a la edición de esta propiedad, nos encontraremos con esto:

Etiqueta input mal generada

¿Ein? ¿Length=”15”? ¿Te suena esto a algo? Seguro que sí 😉

Efectivamente, el atributo extraño de la etiqueta <input> no es más que la propiedad Length de la cadena que estamos pasando a TextBoxFor en su segundo parámetro, reservado en la  sobrecarga de TextBoxFor que encaja con la llamada para especificar los atributos HTML personalizados:

public static MvcHtmlString TextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression,
    Object htmlAttributes
)

Curiosamente, el TextBoxFor() sólo es generado así en las vistas de edición (Edit), y sobre propiedades de tipo DateTime, decimal y double; en las vistas de creación (Create) no ocurre este problema, ni con otros tipos de datos.

Pero bueno, ya que tenemos el destornillador en la mano, cuesta muy poco trabajo dejar las cosas en su sitio ;-). Como vimos en otra ocasión, el código de las vistas que genera Visual Studio está basado en plantillas T4, y es realmente sencillo modificarlo, así que vamos a ello.

En primer lugar, acudimos a la carpeta del IDE donde se almacenan estas plantillas:

  • (IDE)Common7IDEItemTemplatesCSharpWebMVC 2CodeTemplatesAddView

Siendo (IDE) el directorio raíz de Visual Studio, normalmente dentro de Archivos de programa.

A continuación hacemos una copia de seguridad de la plantilla llamada Edit.tt (que más vale prevenir…), la abrimos con cualquier editor de texto y buscamos en su la cadena “TextBoxFor”, que encontraremos en una porción de código como la siguiente:

Porción de Edit.tt original

Ahora lo único que tenemos que hacer es eliminar la porción <#= property.Value #>, que es la que se encarga de emitir los atributos incorrectos. De hecho, sería simplemente dejar la línea tal y como ésta aparece en la plantilla de creación (Create.tt):

Porción de edit.tt modificada

Hecho esto, al generar de nuevo vistas de edición, ya las fechas y decimales aparecerán como deben:

Edición de una fecha con TextBoxFor, corregida

En fin, se trata de un pequeño detalle en las plantillas de edición que hacen que se genere un código incorrecto al cliente. Nada importante, pero sin duda una buena excusa para profundizar en los mecanismos de andamiaje del framework.

Publicado en: Variable not found.

Consejos imprescindibles por si algo falla

Desarrolladores tatuados ¡Ah, cómo nos parecemos los desarrolladores! Independientemente de la tecnología, el tipo de aplicación, o el cliente, parece que llevamos tatuados de serie unos comportamientos que repetimos una y otra vez ante escenarios similares.

Debe ser por eso que me divierten y me identifico tanto con los posts que enumeran estos hábitos tan comunes, como el clásico las “20 respuestas más utilizadas por los desarrolladores cuando sus programas no funcionan”, que pulula por la red desde hace muchos años y del que he encontrado versiones en todos los idiomas, por lo que soy absolutamente incapaz de citar la fuente exacta.

El hecho es que justo en el momento en que se nos informa de que algo ha fallado en una de nuestras aplicaciones, el cerebro comienza a trabajar aceleradamente en busca de una excusa explicación, por lo que he pensado que combinando esta lista de frases con unos sabios consejos, seguro que puedo ayudar a más de uno a evitar las tensiones derivadas de los errores en programación.

Y es que, al final, seguro que todos acabamos diciendo aproximadamente lo mismo… (continuar leyendo en Variable not found).