Как подключить jQuery UI Datepicker к форме в Drupal 7

Не так давно мне была поставлена задача написать модуль для резервации номеров в отеле. Выбор дат в форме резервации я решил реализовать с помощью виджета jQuery UI Datepicker. Весь модуль мы разбирать не будем, а остановимся на основных моментах с которыми столкнулся я в процессе разработки.

Сперва нам надо создать форму, к которой мы будем подключать виджет jQuery UI Datepicker, я покажу только кусок кода из модуля:

/**
 * Form constructor for the reservation form.
 *
 * @ingroup forms
 */
function mymodule_reservation_form($form, &$form_state) {

  $form['arrival'] = array(
    '#type' => 'textfield',
    '#title' => t('Arrival'),
    '#default_value' => date('d.m.Y', REQUEST_TIME), // Устанавливаем в качестве значения по умолчанию сегодняшний день.
    '#required' => TRUE,
    '#size' => 10,
    '#maxlength' => 10,
  );

  $form['departure'] = array(
    '#type' => 'textfield',
    '#title' => t('Departure'),
    '#default_value' => date('d.m.Y', REQUEST_TIME + 86400), // Устанавливаем в качестве значения по умолчанию завтрашний день.
    '#required' => TRUE,
    '#size' => 10,
    '#maxlength' => 10,
  );

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Verify'),
  );

  return $form;
}

Переходим непосредственно к подключению виджета jQuery UI Datepicker. Добавляем в функцию, которая формирует форму следующие строки:

$form['#attached']['library'][] = array('system', 'ui.datepicker');
$form['#attached']['js'][] = drupal_get_path('module', 'mymodule') . '/js/mymodule.js';

Создаем mymodule.js и ложим его в каталог mymodule/js/mymodule.js. Содержимое этого файла:

(function ($) {
  Drupal.behaviors.myModule = {
    attach: function (context){
      $('#edit-arrival', context).datepicker();
      $('#edit-departure', context).datepicker();  
    }
  }
})(jQuery);

В принципе на этом уже можно было бы остановиться, но мне сразу же бросилось в глаза, что дата выводится в формате mm/dd/yy. Такой вывод лично для меня не удобен и я поменял его на следующий формат dd.mm.yy:

$('#edit-arrival', context).datepicker({
  dateFormat: 'dd.mm.yy'
});

$('#edit-departure', context).datepicker({
  dateFormat: 'dd.mm.yy'
});

Поскольку я писал форму резервации номеров отеля, то мне потребовалось заблокировать прошедшие дни, причем дата въезда и выезда должны отличаться минимум в 1 день:

$('#edit-arrival', context).datepicker({
  dateFormat: 'dd.mm.yy',
  minDate: 0 // Устанавливаем в качестве минимальной даты сегодняшний день.
});

$('#edit-departure', context).datepicker({
  dateFormat: 'dd.mm.yy',
  minDate: 1 // Устанавливаем в качестве минимальной даты завтрашний день.
});

Уже лучше, но теперь необходимо после выбора даты въезда заблокировать в поле даты выезда уже выбранную дату въезда и все даты, которые перед ней:

$('#edit-arrival', context).change(function() {
  var strDate = $('#edit-arrival', context).attr('value');
  var dateParts = strDate.split('.');

  $('#edit-departure', context).datepicker('option', 'minDate', new Date(dateParts[2], dateParts[1] - 1, parseInt(dateParts[0]) + 1));
}); 

Далее мне захотелось справа от полей добавить иконки календарей, клик по которым вызывает виджет. Для этого в функцию, которая формирует форму, добавляем следующие строки:

$form['#attached']['js'][] = array(
  'data' => array(
    'mymodule_path' => drupal_get_path('module', 'mymodule'),
  ),
  'type' => 'setting',
);  

Теперь мы можем в mymodule.js получить путь к модулю и указать полный путь к изображению календаря:

$('#edit-arrival', context).datepicker({
  showOn: 'both', // Показываем виджет как по фокусу input'а, так и по клику на изображение календаря.
  buttonImage: Drupal.settings.basePath + Drupal.settings.mymodule_path + '/images/calendar.png', // Указываем путь к изображению с календарем.
  buttonImageOnly: true, // Если установлено в false, то отображается button c бэкграундом календаря, если в true, то выводится обычная картинка.
  dateFormat: 'dd.mm.yy',
  minDate: 0 // Устанавливаем в качестве минимальной даты сегодняшний день.
});

$('#edit-departure', context).datepicker({
  showOn: 'both', // Показываем виджет как по фокусу input'а, так и по клику на изображение календаря.
  buttonImage: Drupal.settings.basePath + Drupal.settings.mymodule_path + '/images/calendar.png', // Указываем путь к изображению с календарем.
  buttonImageOnly: true, // Если установлено в false, то отображается button c бэкграундом календаря, если в true, то выводится обычная картинка.
  dateFormat: 'dd.mm.yy',
  minDate: 1 // Устанавливаем в качестве минимальной даты завтрашний день.
});

Теперь уже точно можно было бы завершить урок по работе с виджетом jQuery UI Datepicker, если бы не одно но. Сайт к которому я писал модуль на двух языках, поэтому необходимо показывать виджет на языке сайта (по умолчанию он выводится на английском). Для этого воспользуемся возможностями модуля Locale, который входит в ядро. В функцию, которая формирует форму, добавляем следующие строки:

global $language;

$form['#attached']['js'][] = drupal_get_path('module', 'locale') . '/locale.datepicker.js';
$form['#attached']['js'][] = array(
  'data' => array(
    'jqueryuidatepicker' => array(
      'rtl' => $language->direction == LANGUAGE_RTL,
      'firstDay' => variable_get('date_first_day', 0),
    ),
  ),
  'type' => 'setting',
);

Итоговый вид функции, которая формирует форму:

/**
 * Form constructor for the reservation form.
 *
 * @ingroup forms
 */
function mymodule_reservation_form($form, &$form_state) {
  global $language;

  $form['#attached']['library'][] = array('system', 'ui.datepicker');
  $form['#attached']['js'][] = drupal_get_path('module', 'locale') . '/locale.datepicker.js';
  $form['#attached']['js'][] = drupal_get_path('module', 'mymodule') . '/js/mymodule.js';
  $form['#attached']['js'][] = array(
    'data' => array(
      'mymodule_path' => drupal_get_path('module', 'mymodule'),
      'jqueryuidatepicker' => array(
        'rtl' => $language->direction == LANGUAGE_RTL,
        'firstDay' => variable_get('date_first_day', 0),
      ),
    ),
    'type' => 'setting',
  );

  $form['arrival'] = array(
    '#type' => 'textfield',
    '#title' => t('Arrival'),
    '#default_value' => date('d.m.Y', REQUEST_TIME), // Устанавливаем в качестве значения по умолчанию сегодняшний день.
    '#required' => TRUE,
    '#size' => 10,
    '#maxlength' => 10,
  );

  $form['departure'] = array(
    '#type' => 'textfield',
    '#title' => t('Departure'),
    '#default_value' => date('d.m.Y', REQUEST_TIME + 86400), // Устанавливаем в качестве значения по умолчанию завтрашний день.
    '#required' => TRUE,
    '#size' => 10,
    '#maxlength' => 10,
  );

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Verify'),
  );

  return $form;
}

Файл mymodule.js:

(function ($) {
  Drupal.behaviors.myModule = {
    attach: function (context){
      $('#edit-arrival', context).datepicker({
        showOn: 'both', // Показываем виджет как по фокусу input'а, так и по клику на изображение календаря.
        buttonImage: Drupal.settings.basePath + Drupal.settings.mymodule_path + '/images/calendar.png', // Указываем путь к изображению с календарем.
        buttonImageOnly: true, // Если установлено в false, то отображается button c бэкграундом календаря, если в true, то выводится обычная картинка.
        dateFormat: 'dd.mm.yy',
        minDate: 0 // Устанавливаем в качестве минимальной даты сегодняшний день.
      });

      $('#edit-departure', context).datepicker({
        showOn: 'both', // Показываем виджет как по фокусу input'а, так и по клику на изображение календаря.
        buttonImage: Drupal.settings.basePath + Drupal.settings.mymodule_path + '/images/calendar.png', // Указываем путь к изображению с календарем.
        buttonImageOnly: true, // Если установлено в false, то отображается button c бэкграундом календаря, если в true, то выводится обычная картинка.
        dateFormat: 'dd.mm.yy',
        minDate: 1 // Устанавливаем в качестве минимальной даты завтрашний день.
      });
      
      $('#edit-arrival', context).change(function() {
        var strDate = $(this, context).attr('value');
        var dateParts = strDate.split('.');

        $('#edit-departure', context).datepicker('option', 'minDate', new Date(dateParts[2], dateParts[1] - 1, parseInt(dateParts[0]) + 1));
      });      
    }
  }

})(jQuery);

Результат

jQuery UI Datepicker результат
jQuery UI Datepicker результат
Benya