Creando el proyecto de ejemplo
Para realizar el ejemplo voy a crear un nueva Aplicación ASP.NET MVCDateTimePicker en nuestro Visual Studio, seleccionando la plantilla vacía y marcando el check para incluir las referencias a MVC.
En el proyecto de prueba he añadido una clase Persona en la carpeta Models. Para el ejemplo he creado dos propiedades de tipo fecha: FechaNacimiento y UltimoAcceso. La propiedad FechaNacimiento la he decorado con un atributo DataType para especificar que el tipo de datos es de sólo fecha (sin hora). De esta forma tendremos una propiedad de tipo fecha y otra de tipo fecha/hora con las que poder probar nuestras plantillas.
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; namespace MVCDateTimePicker.Models { public class Persona { public string Nombre { get; set; } public string Apellido { get; set; } public string Ciudad { get; set; } [Display(Name="Fecha de Nacimiento")] [DataType(DataType.Date)] public DateTime FechaNacimiento { get; set; } [Display(Name="Último Acceso")] public DateTime UltimoAcceso { get; set; } } }
Imports System.ComponentModel.DataAnnotations Public Class Persona Property Nombre As String Property Apellido As String Property Ciudad As String <Display(Name:="Fecha de Nacimiento")> _ <DataType(DataType.Date)> _ Property FechaNacimiento As DateTime <Display(Name:="Último Acceso")> _ Property UltimoAcceso As DateTime End Class
En la carpeta Shared dentro de Views he creado una nueva página de diseño (layout) _Layout.cshtml/_Layout.vbhtml.
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> </body> </html>
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewData("Title")</title> </head> <body> <div> @RenderBody() </div> </body> </html>
En la carpeta Controllers he creado un controlador HomeController con una única acción Index que se encarga de enviar una instancia de la clase Persona a la vista.
using MVCDateTimePicker.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MVCDateTimePicker.Controllers { public class HomeController : Controller { public ActionResult Index(Persona persona) { return View(persona); } } }
Imports System.Web.Mvc Namespace Controllers Public Class HomeController Inherits Controller Function Index(persona As Persona) As ActionResult Return View(persona) End Function End Class End Namespace
Para terminar he creado la vista Index para la acción del controlador en la carpeta Views. La vista utiliza la clase Persona como modelo y genera un formulario para el objeto recibido. He añadido un botón Validar para devolver los datos al servidor y que se ejecuten las correspondientes validaciones.
@model MVCDateTimePicker.Models.Persona @using (Html.BeginForm()) { @Html.EditorForModel() <br/> <input type="submit" value="Validar" /> }
@ModelType MVCDateTimePicker.Persona @Using (Html.BeginForm()) @Html.EditorForModel() @:<br /> @:<input type="submit" value="Validar" /> End Using
Si compilamos y arrancamos nuestra aplicación podremos ver el problema: no sólo el editor generado para los campos de tipo fecha resulta pobre, sino que además se muestra un editor diferente dependiendo del tipo de datos y del navegador utilizado. En la siguiente imagen podemos ver el interfaz generado por los navegadores Chrome 41.0 (a la izquierda) e Internet Explorer 10 (a la derecha).
Creando las plantillas para fechas
Voy a basar las plantillas en el plugin DateTimePicker de jQuery. Para ello lo primero que necesitaremos es tener las librerías de jQuery incluidas en el proyecto. Podemos descargarlas de la página de jQuery o incluirlas a través de la administración de paquetes NuGet.
Pulsando con el botón derecho sobre el proyecto en la ventana Explorador de soluciones seleccionamos la opción Administrar Paquetes NuGet.... En la ventana de administración de paquetes seleccionamos la opción En línea (a la derecha), buscamos el paquete correspondiente a jQuery y pulsamos en Instalar:
A continuación descargamos el plugin DateTimePicker desde GitHub e incluimos el archivo jquery.datetimepicker.js en la carpeta Scripts y el archivo jquery.datetimepicker.css en la carpeta Content.
Añadimos en la página de diseño principal de la aplicación (_Layout.cshtml/_Layout.vbhtml) una referencia tanto a los dos ficheros del plugin como a la librería jQuery en la sección head.
<head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/jquery.datetimepicker.css" rel="stylesheet" /> <script src="~/Scripts/jquery-2.1.3.min.js"></script> <script src="~/Scripts/jquery.datetimepicker.js"></script> </head>
<head> <meta name="viewport" content="width=device-width" /> <title>@ViewData("Title")</title> <link href="~/Content/jquery.datetimepicker.css" rel="stylesheet" /> <script src="~/Scripts/jquery-2.1.3.min.js"></script> <script src="~/Scripts/jquery.datetimepicker.js"></script> </head>
Por convención los editores de MVC para cada tipo de datos se guardan en la carpeta Views/Shared/EditorTemplates con el nombre del tipo de datos para el que es el editor.
Así si queremos crear un editor para el tipo de datos DateTime crearemos una vista parcial DateTime.cshtml/DateTime.vbhtml en la carpeta Views/Shared/EditorTemplates. Y para la propiedad a la que hemos establecido su tipo de datos como DataType.Date a través del atributo DataTypeAttribute deberemos crear una vista Date.cshtml/Date.vbhtml.
Éste es el código del archivo DateTime.cshtml/DateTime.vbhtml.
@model DateTime <script> $(function () { $(".datetimepicker").datetimepicker({ lang: 'es', format: 'd/m/Y H:i', formatDate: 'd/m/Y', dayOfWeekStart: 1, mask: true }); }); </script> @Html.TextBox("", (Model == DateTime.MinValue ? null : Model.ToString("dd/MM/yyyy hh:mm")) , new { @class = "datetimepicker" })
@ModelType DateTime <script> $(function () { $(".datetimepicker").datetimepicker({ lang: 'es', format: 'd/m/Y H:i', formatDate: 'd/m/Y', dayOfWeekStart: 1, mask: true }); }); </script> @Html.TextBox("", IIf(Model = DateTime.MinValue, Nothing, Model.ToString("dd/MM/yyyy hh:mm")) _ , New With {.class = "datetimepicker"})
En el editor utilizo el método TextBox del HtmlHelper para generar un elemento html del tipo input type="text" en lugar del input type="datetime" que genera el editor por defecto del tipo DateTime. De esta forma evito que el navegador intente utilizar su propia implementación para la edición de fechas.
Al método TextBox le pasó tres parámetros:
- El primero establece el nombre del elemento html. Como MVC ya le va a asignar el nombre al elemento en función del nombre de la propiedad a la que se enlace, lo dejo vacío.
- El segundo establece el valor del elemento. Paso el valor del modelo, con el mismo formato que utilizo en la inicialización del datetimepicker, salvo que el valor sea DateTime.MinValue, en tal caso dejo el valor vacío.
- Por último le asigno el estilo datetimepicker a través del atributo class para poder identificar los controles a los que aplicar el plugin.
El script de javascript simplemente selecciona los elementos del tipo datetimepicker e inicializa el plugin con una serie de valores iniciales. Se puede consultar la lista completa de opciones de personalización en la documentación del plugin.
Para el editor para el tipo de datos Date simplemente voy a cambiar el formato de visualización de la fecha (tanto en la creación del TextBox como en la inicialización del plugin). Además he cambiado el estilo del control por datepicker y he añadido un nuevo valor de inicialización al plugin para que no muestre la selección de horas:
@model DateTime <script> $(function () { $(".datepicker").datetimepicker({ lang: 'es', format: 'd/m/Y', formatDate: 'd/m/Y', dayOfWeekStart: 1, mask: true, timepicker: false }); }); </script> @Html.TextBox("", (Model == DateTime.MinValue ? null : Model.ToString("dd/MM/yyyy")) , new { @class = "datepicker" })
@ModelType DateTime <script> $(function () { $(".datepicker").datetimepicker({ lang: 'es', format: 'd/m/Y', formatDate: 'd/m/Y', dayOfWeekStart: 1, mask: true, timepicker: false }); }); </script> @Html.TextBox("", IIf(Model = DateTime.MinValue, Nothing, Model.ToString("dd/MM/yyyy")) _ , New With {.class = "datepicker"})
Si trabajas con Visual Basic .Net, en Visual Studio 2013, si se intentas añadir la vista Date a través del formulario de Scaffolding recibirás un error indicando que no se puede crear porque Date es una palabra reservada. Puedes crear la vista utilizando las plantillas de Visual Studio o creándola con otro nombre y modificándolo después. Este es un error que, por lo que he visto, persiste en la versión preview de Visual Studio 2015.
Si arrancamos la aplicación veremos cómo se ha modificado el interfaz de edición de fechas, y éste es independiente del navegador.
Cuestiones pendientes
Hemos creado los editores para fecha y fecha/hora. Sin embargo la solución desarrollada presenta algunos problemas. Para empezar he incluido la referencia al plugin datetimepicker en la página de diseño de la aplicación (_Layout.cshtml/_Layout.vbhtml) por lo que el archivo se carga en todas las páginas de la aplicación, independientemente de si existen controles de fecha o no.
El segundo problema también tiene que ver con el código javascript y es más grave aún. En cada uno de los editores he incluido código para inicializar los controles de edición de fechas. En una sola instrucción inicializa todos los controles del mismo tipo que haya en la página. Sin embargo, si existen varios controles del mismo tipo, la instrucción se cargará y ejecutará tantas veces como controles existan en la página. Evidentemente no es una solución nada óptima.
Para solucionar este segundo problema podría meter las dos instrucciones de los editores en un archivo js e incluirlo en la página de diseño principal. De esta forma se ejecutarían una única vez en todas las páginas, independientemente de que existan controles o no. Esta solución también tiene otro problema, dividimos la funcionalidad de los editores en diferentes ubicaciones del proyecto, con lo que esto penaliza a la mantenibilidad del código (no hay más que pensar en las modificaciones a realizar si necesitamos cambiar los nombres de los estilos utilizados).
Estos problemas pueden resultar asumibles en sitios pequeños pero, en grandes proyectos con cientos de scripts, pueden desembocar en un problema importante de rendimiento y mantenibilidad.
En el próximo artículo (ASP.NET MVC. Gestión de Scripts en Plantillas y Vistas Parciales) explicaré cómo podemos resolver estos problemas, consiguiendo que los scripts se carguen únicamente en las pantallas en las que son necesarios y ejecutándose una única vez.
El código completo de este artículo y el anterior (ASP.NET MVC. Gestión de Scripts en Plantillas y Vistas Parciales), tanto en C# como en Visual Basic, está disponible en:
Códigos de muestra - Ejemplos MSDN
Hola Buen día, y para hacerlo funcionar en una pagina que no es MVC
ResponderEliminarcomo lo debo llamar en el Diseño o en el Script
¿A qué te refieres con "una página que no es MVC"? Si te refieres a utilizar el plugin DateTimePicker en una página WebForms puedes consultar el artículo: Integrating JQuery UI Datepicker with model binding and web forms
ResponderEliminarPerdon Mil disculpas, me aparce el siguiente error:
ResponderEliminarEl objeto que acepta valores Null debe tener un valor.
Hola Francisco, ¿en qué punto exactamente te aparece el error?
EliminarBuenas como hago para sumir una fecha con dias y me de la fecha de ese resultado gracias
ResponderEliminaren el calendario solo me aparece la fecha no la hora no se si me hizo falta algo o es por que mi visual es 2015 pero no creo
ResponderEliminar