В Яндекс.Метрика подсмотрел, что по такому поисковому запросу приходят в мой блог и решил написать статью об этом. Задача довольно простая - позволить пользователю выбирать дату из красивого календаря, такого как jQuery UI Datepicker
Решение “в лоб”.
Как известно, ASP.NET MVC поддерживает шаблоны для редакторов полей. Первый вариант - использовать такой шаблон “в лоб”. Создадим файл с названием “DateTime.cshtml” и положим его в “/Views/Shared/EditorTemplates/”. Содержание файла будет таким:
@model DateTime?
@Html.TextBox("") @* "" - мы говорим, что для названия поля будет использоваться название модели *@
<script type="text/javascript">
(function($) {
$(function() {
var settings = {
//здесь стандартные настройки для datepicker
};
$('#@Html.IdForModel()').datepicker(settings);
});
})(jQuery);
</script>
Теперь, каждый раз, когда вы будете писать @Html.EditorFor(x => x.FieldOfDateTimeType)
будет использоваться данный шаблон.
У этого решения есть недостатки:
- Решение не гибкое - не позволяет использовать разные настройки для разных редакторов. Для того, чтобы использовать другие настройки календаря необходимо создать отдельный шаблон.
- Каждый раз, при отрисовке календарика в разметку будет добавляться дополнительный
Делаем жизнь проще.
Очевидно, что дублирующийся в конечном HTML скрипт нужно куда-то вынести. Вынесем его в базовый слой, или в отдельный файл. Для этого, нам необходимо модифицировать наш sizzle-селектор для того, чтобы он находил все календарики. Для этого пометим наш календарик с помощью аттрибута role="datepicker"
и для поддержки html5 присвоим ему тип - “date”:
DateTime.cshtml:
@model DateTime?
@Html.TextBox("", null, new { role = "datepicker", type = "date" })
_Layout.cshtml:
<script type="text/javascript">
(function($) {
$(function() {
var settings = {
//здесь стандартные настройки для datepicker
};
$('input[role=datepicker], input[type=date]').datepicker(settings);
});
})(jQuery);
</script>
Данное решение более элегантно, чем решение в лоб, но при этом остается почти таким же не гибким.
Решение с использованием MvcExtensions + unobtrusive javascript.
Предыдущее решение уже unobtrusive, но пока не позволяет передавать дополнительные параметры в календарик. Для этого к нам на помощь придут наши друзья IModelMetadataAdditionalSetting
s. Для их использования мы создадим метод расширения AsDatePicker
:
public static class ModelMetadataItemBuilderDatePickerExtensions {
public static ModelMetadataItemBuilder<T> AsDatepicker<T>(this ModelMetadataItemBuilder<T> self,
DateTime? minDate = null, DateTime? maxDate = null) {
self.Template("DateTime");
var setting = self.Item.GetAdditionalSettingOrCreateNew<DatePickerSettings>();
setting.MinDate = minDate;
setting.MaxDate = maxDate;
return self;
}
}
Здесь всё просто: выставляется шаблон и создаются настройки для календарика. В шаблоне мы будет отрисовывать это следующим образом:
@{var settings = ViewData.ModelMetadata.GetAdditionalSettingOrCreateNew<DatePickerSettings>(); }
@Html.TextBoxFor(_ => _, settings.ToHtmlAttributes() /*html attributes*/)
Здесь настройки сериализуются в data-* аттрибуты у элемента <input />
. При этом, будет соблюдена конвенция, что знак подчеркивания будет преобразован в дефис.
Класс DatePickerSettings
:
public class DatePickerSettings : IModelMetadataAdditionalSetting {
public DateTime? MinDate { get; set; }
public DateTime? MaxDate { get; set; }
public object ToHtmlAttributes() {
return new
{
role = "datepicker",
type = "date",
data_min_date = MinDate,
data_max_date = MaxDate,
};
}
}
jQuery-UI для настроек использует camelCase нотацию, а, jQuery, к счастью, автоматически преобразуте дефисы в data-* аттрибутах в такую нотацию. Таким образом нам нужно просто для каждого <input>
взять его data-* аттрибуты и просто передать в качестве настроек в datapicker. Это будет сделано скриптом в _Layout.cshtml
:
<script type="text/javascript">
(function($) {
$(function() {
$('[role=datepicker]', input[type=date]').each(function () {
var self = $(this);
self.datepicker(self.data());
});
});
})(jQuery);
</script>
В метаданных это будет использоваться так:
public class MyModelMetadata: ModelMetadataConfiguration<MyModel> {
public MyModelMetadata() {
Configure(m => m.FieldOfDateTimeType)
.AsDatepicker(minDate: DateTime.Today, maxDate: DateTime.Today.AddDays(10));
}
}
Вместо заключения
Данное решение обладает абсолютной гибкостью и позволяет управлять любыми свойствами для каждого из ваших календарей (и не только календарей) через метаданные формы.
По материалам доклада “Metadata + JavaScript = ♥” Рахматиллаева Тимура на dotnetconf.
UPDATE: Добавил примеры к статье.
- Примеры доступны здесь: http://hazzik-samples.apphb.com/DatePicker
- Исходиники примеров здесь: https://github.com/hazzik/Samples-From-My-Blog