I've just launched www.jogmymemory.co.za and people are complaining that the calendar time-selector is difficult to use.

How can I create a new time-field for the forms API? I've read some docs on the site but they didn't make much sense.

I'd basically be happy with a date field that has drop downs for year, month, day, hour & minute.

Comments

Scott Reynolds’s picture

JSTools has a nice calendar widget for you. http://drupal.org/project/jstools

There is also #date for form api: http://api.drupal.org/api/4.7/file/developer/topics/forms_api_reference....

norio’s picture

Thanks for your reply, Scott :)

I'm already using the widget from JSTools -- that's what people are complaining about :/

As for #date, does it allow me to specify the time via hours and minutes dropdowns?

norio’s picture

Finally got it right using hook_elements but what a mission!

nicholasthompson’s picture

How did you do this? I'm interested in this code as I need it for a data export module i'm writing...

norio’s picture

It's basically a modified version of the date widget. Anyway, here you go :)

(Oh and I don't use Drupal's built-in date functions which means that the time is always the same timezone as the server it's on.


// new time type
function yourmodule_elements() {
  $type["time"] = array(
    "#input"       => true,
    "#process"     => array("expand_time" => array())
  );

  return $type;
}
function expand_time($element) {
  // set current time default
  if (!isset($element['#value'])) {
    $element['#value'] = array(
      'hour'   => date('H'),
      'minute' => date('i')
    );
  }

  $element['#tree'] = TRUE;

  // output multi-selector
  $order = array("hour", "minute");
  foreach ($order as $type) {
    switch ($type) {
      case 'hour':
        $options = drupal_map_assoc(range(0, 23));
        break;
      case 'minute':
        $options = drupal_map_assoc(range(0, 59));
        break;
    }
    // padding with zeroes
    foreach ($options as $key => $value) {
      $options[$value] = sprintf("%02s", $value);
    }
    
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
  }

  return $element;
}
function theme_time($element) {
  $output = '<div class="container-inline">'. $element['#children'] .'</div>';
  return theme('form_element', $element['#title'], $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element));
}
mcantelon’s picture

Nice... just tried it and it looks like it's working. :) Thanks!

-Mike Cantelon
http://mikecantelon.com

pateld’s picture

If you use the code posted above in Drupal 5.1, you won't able to see title and description. I did minor modification and it works like charm.

Change,

function theme_time($element) {
  $output = '<div class="container-inline">'. $element['#children'] .'</div>';
  return theme('form_element', $element['#title'], $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element));
}

To,

function theme_time($element) {
  $output = '<div class="container-inline">'. $element['#children'] .'</div>';
  return theme('form_element', $element, $output);
}

Regards,
Dhaval

mpare’s picture

Here is my rendition of the time form type element. I would have prefered to have it a text field with user inputed values but all the regular expressions and logic to allow for any valid input made my head spin and my regex knowledge isn't what I would like. So here you go... This code was built and tested on drupal 5.2.x

<?php
function <your_module's_name>_elements() {
  $type["time"] = array(
    "#input"       => true,
    "#process"     => array("expand_time" => array()),
  );

  return $type;
}

function expand_time($element) {
  // Default to current time
  if (empty($element['#value'])) {
    $element['#value'] = array(
      'hour' => format_date(time(), 'custom', 'h'),
      'minute' => format_date(time(), 'custom', 'i'),
      'meridiem' => format_date(time(), 'custom', 'a')
    );
  }
  
  $element['#tree'] = TRUE;

  foreach ($element['#value'] as $type => $value) {
    switch ($type) {
      case 'hour':
        $options = range(0, 12);
        break;
      case 'minute':
        $options = range(0, 59);
        break;
      case 'meridiem':
        $options = drupal_map_assoc(array('am', 'pm'));
        break;
    }
    
    if ($type != 'meridiem') {
      foreach ($options as $option) {
        strlen($option) <= 1 ? $options[$option] = 0 . $option : '';
      }
    }
    
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
  }

  return $element;
}

function time_value() {
  // null function guarantees default_value doesn't get moved to #value.
}

function theme_time($element) {
  $output = '<div class="container-inline">'. $element['#children'] .'</div>';
  return theme('form_element', $element, $output);
}
?>

Peace,
-mpare
www.paretech.com

Did you figure out how to do something? Did you find documentation on Drupal.org inadequate? Well now it's your turn. Document your Success!

mpotter’s picture

The above code uses the 'h' format for Hour and 'i' format for Minutes, which return values including leading zeros. However, to act as a proper default value for the HTML SELECT tag, leading zeros do not work. In the SELECT tag you'll notice that the actual *value* of each option doesn't have any leading zero...just the display string has the leading zeros to keep the values properly sorted.

So the above code only properly pre-selects the correct default hour and minute if the hour is 10,11,12 and the minute is >= 10. Here is the modified code I used to fix this. I've included the full code to make it easier for people to use it, but the change is just in the first few lines. Also added the $parents code taken from the existing expand_date code in form.inc and removed the unneeded time_value function.

/**
 * Implementation of hook_elements().
 */
function modulename_elements() {
  $type["time"] = array(
    "#input"       => true,
    "#process"     => array("expand_time"),
  );

  return $type;
}

/**
 * Define a Time form element.  Adapted from Date element type in form.inc
 * 
 * @param mixed $element
 */
function expand_time($element) {
  // Default to current time
  if (empty($element['#value'])) {
    $element['#value'] = array(
    // note we need the defaults WITHOUT leading zeros
    // so we use a trick for minutes since PHP doesn't have a format for it
      'hour' => format_date(time(), 'custom', 'g'),
      'minute' => intval( format_date(time(), 'custom', 'i')),
      'meridiem' => format_date(time(), 'custom', 'a')
    );
  }

  $element['#tree'] = TRUE;

  foreach ($element['#value'] as $type => $value) {
    switch ($type) {
      case 'hour':
        $options = range(0, 12);
        break;
      case 'minute':
        $options = range(0, 59);
        break;
      case 'meridiem':
        $options = drupal_map_assoc(array('am', 'pm'));
        break;
    }
   
   // pad with zeros
    if ($type != 'meridiem') {
      foreach ($options as $option) {
        strlen($option) <= 1 ? $options[$option] = 0 . $option : '';
      }
    }
   
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
  }

  return $element;
}

function theme_time($element) {
  $output = '<div class="container-inline">'. $element['#children'] .'</div>';
  return theme('form_element', $element, $output);
}
samlogan’s picture

Norio Hi,

I saw that your site offers SMS notification services.
Is there some ready-to-use functionality in Drupal for generating SMS?

Thanks,
Sam

norio’s picture

I needed this code again, and thanks to Dhaval, I could quickly and easily get it working in Drupal 5. Thanks :)