Как подключить 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 результат

Комментарии (12)

Аватар пользователя senya
senya

спасибо за статью , но как сделать тоже самое, только не для поля textfied , а для select

Аватар пользователя Benya
Benya

На сколько мне известно, библиотека не умеет подключаться к селектам, но если поставить целью добиться результата, то получится и к селекту подключить

Аватар пользователя Виталий
Виталий

А можешь скинуть этот модуль полностью?

Аватар пользователя Branson
Branson

Полезная статья. А как сделать чтоб календарь был постоянно на форме? Знаю что нужно обернуть в див, но как это сделать программно?

Аватар пользователя Benya
Benya

Вы хотите, чтобы календарь был независим от поля, а постоянно отображался без привязки к другим элементам?

Аватар пользователя Branson
Branson

Это не важно, привязан или нет. Главное чтобы на форме был всегда.

Аватар пользователя Ert Warol
Ert Warol

Большое спосибо за потраченое вами время!))) Было очень полезно. Использовал ваше решение на своем проекте.

Аватар пользователя Mike
Mike

Беня, ты крут! Спасибо за статью, как раз пригодилось!