MvcExtensions - как подключить jquery-ui date picker в ASP.NET MVC. Unobtrusive way.

В Яндекс.Метрика подсмотрел, что по такому поисковому запросу приходят в мой блог и решил написать статью об этом. Задача довольно простая - позволить пользователю выбирать дату из красивого календаря, такого как 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) будет использоваться данный шаблон.

У этого решения есть недостатки:

  1. Решение не гибкое - не позволяет использовать разные настройки для разных редакторов. Для того, чтобы использовать другие настройки календаря необходимо создать отдельный шаблон.
  2. Каждый раз, при отрисовке календарика в разметку будет добавляться дополнительный

Делаем жизнь проще.

Очевидно, что дублирующийся в конечном 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, но пока не позволяет передавать дополнительные параметры в календарик. Для этого к нам на помощь придут наши друзья IModelMetadataAdditionalSettings. Для их использования мы создадим метод расширения 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: Добавил примеры к статье.

comments powered by Disqus