Search code examples
javascriptphpdrupal

How do i use onHourShow callback in a date_popup control with the Drupal Form API


I have a drupal form with a single date_popup field. I would like it to just provide the options of 12, 15 and 17 for the hours.

$form['order-group']['delivery'] = array(
    '#title'                => t('I Need The Equipment Ready By'),
    '#type'                 => 'date_popup',
    '#date_format'          => 'd-M-Y H:i',
    '#timepicker'           => 'timepicker',
    '#timepicker_options'   => array(
        'rows'            => 3,
        'hours'         => array(
            'starts'    => 12,
            'ends'      => 17,
        ),
        'onHourShow'   => 'onHourShowCallback',
        'minutes'         => array(
            'starts'    => 0,
            'ends'      => 0,
        ),
        'showCloseButton' => TRUE,
        'closeButtonText' => t('Close'),
    ),
    '#default_value'  => date('Y-m-d 12:00',time()),
    '#date_label_position'  => '',
);

I then added the following to my javascript file (included with drupal_add_js, earlier in the script)

function onHourShowCallback(hour) {
    return hour == 12 || hour == 15 || hour == 17;
};

However I get the following error:

Uncaught TypeError: onHourShow.apply is not a function
    at Timepicker._generateHTMLHourCell (jquery.ui.timepicker.js?psw6eo:797)
    at Timepicker._generateHTML (jquery.ui.timepicker.js?psw6eo:622)
    at Timepicker._updateTimepicker (jquery.ui.timepicker.js?psw6eo:467)
    at Timepicker._setTimeFromField (jquery.ui.timepicker.js?psw6eo:1112)
    at Timepicker._attachTimepicker (jquery.ui.timepicker.js?psw6eo:190)
    at HTMLInputElement.<anonymous> (jquery.ui.timepicker.js?psw6eo:1474)
    at Function.each (jquery.min.js?v=1.7.2:2)
    at $.fn.init.each (jquery.min.js?v=1.7.2:2)
    at $.fn.init.$.fn.timepicker (jquery.ui.timepicker.js?psw6eo:1470)
    at Object.attach (date_popup_timepicker.timepicker.js?psw6eo:8)

I tried asking this in the Drupal stack exchange but was put "On Hold" and told this is a programming question, so I am asking here instead.


Solution

  • jQuery UI Timepicker invokes callback functions such as 'beforeShow', 'onSelect', etc. using the method apply(), e.g. :

    var beforeShow = $.timepicker._get(inst, 'beforeShow');
    extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {}));
    

    Thus, the type of the callback must be a function - not a string.

    The problem is that the module date_popup_timepicker does not map string callbacks (passed in from the server) to actual js function callbacks before initializing the timepicker, so once jquery.ui.timepicker.js code runs it throws an Uncaught TypeError: <callback>.apply is not a function.

    On the client side, the datePopup settings object (from Drupal.settings) potentially contains string callbacks, so any of these strings must be converted to its corresponding function before calling the timepicker() method. What was missing to get there :

    var timepicker_callbacks = {
      beforeShow: null,
      onSelect: null,
      onClose: null,
      onHourShow: null,
      onMinuteShow: null
    };
    
    function timepicker_callbacks_assign (settings) {
      for (var setting in settings) {
        if (setting in timepicker_callbacks && typeof settings[setting] === 'string') {
          var namespace = window,
              ns_callback = settings[setting].split('.'),
              func = ns_callback.pop();
          for (var i = 0; i < ns_callback.length; i++) {
            namespace = namespace[ns_callback[i]];
          }
          settings[setting] = namespace[func];
        }
      }
    }
    

    The timepicker() initialization occurs in Drupal.behaviors.DatePopupTimepicker, we just need to execute timepicker_callbacks_assign() first for the timepicker to get the proper settings.

    I created an issue and submitted a patch on drupal.org, it still "Needs Review" so don't hesitate to apply from there and review it.