Adding a custom element type & expanding elements

The start and end date selection boxes proved to be a particular challenge in my upgrade of eventrepeat module. I handled it by creating my own form element type.

Hook_elements

First, use hook_elements() to declare the new type: 'eventrepeat_date' is the name of the new type. Also, if you want any default values, be sure to declare them either here or in the process function:

<?php
function eventrepeat_elements() {
  
$type['eventrepeat_date'] = array('#input' => TRUE);
   return
$type;
}
?>

Dynamic expand function

Normally, #process would be declared as a "the" expand function in the element type declaration (hook_elements()), but since I needed to pass $edit and $node as function arguments in this case, I declare it for each instance of the element type. Hence a dynamic #process handler.
A normal expand function is more limited, because you can provide less context. Sending $user as a parameter would be possible though.

Here's how the element type is invoked when in a $form array. Notice in particular how the #process attribute is declared here as an array. The key is the name of the callback used to expand the type into multiple elements (you may not need a function such as this if your new type is simple). The value is an array of arguments to be passed to the callback function.

<?php
$form
['end_controls']['end_date'] = array(
 
'#type' => 'eventrepeat_date',
 
'#title' => t('Repeat end date'),
 
'#process' => array('_eventrepeat_form_date' => array($edit, $node, 'eventrepeat_end')),
);
?>

The expand function

Next, write the expand function. The first argument is always the element that is being passed for processing and the other arguments are the optional arguments I added in the #process attribute. Notice that I'm adding to $element and returning it:

<?php
function _eventrepeat_form_date($element, $edit = NULL, $node = NULL, $prefix = NULL) {
   
 
// Get current year, and drop next 10 years into an array.
 
$date = getdate(time());
 
$curyear = $date['year']; 
 
$years = array(0 => '--'. t('Select') .'--');
  while (
$i < 10) {
   
$years[$curyear + $i] = $curyear + $i;
   
$i++;
  }

 
// Months array.
 
$months = array(
   
'--'. t('Select') .'--',
   
t('January'),
   
t('February'),
   
t('March'),
   
t('April'),
   
t('May'),
   
t('June'),
   
t('July'),
   
t('August'),
   
t('September'),
   
t('October'),
   
t('November'),
   
t('December')
  );
 
 
// Days array.
 
$days = array(0 => '--'. t('Select') .'--');
  for (
$i = 1; $i <= 31; $i++) {
     
$days[$i] = $i;
  }

 
// Compose the select boxes, and add the exception editor button if necessary.
 
$element[$prefix .'month'] = array(
   
'#type' => 'select',
   
'#default_value' => $edit ? $edit[$prefix.'month'] : ($node->{$prefix.'month'} ? $node->{$prefix.'month'} : 0),
   
'#options' => $months,
  );
 
$element[$prefix .'day'] = array(
   
'#type' => 'select',
   
'#default_value' => $edit ? $edit[$prefix.'day'] : ($node->{$prefix.'day'} ? $node->{$prefix.'day'} : 0),
   
'#options' => $days,
  );
 
$element['comma'] = array(
   
'#type' => 'markup',
   
'#value' => ', ',
  );
 
$element[$prefix .'year'] = array(
   
'#type' => 'select',
   
'#default_value' => $edit ? $edit[$prefix.'year'] : ($node->{$prefix.'year'} ? $node->{$prefix.'year'} : 0),
   
'#options' => $years,
  );

  if (
$prefix == 'eventrepeat_EXDATE_edit') {
   
$element['exception_button'] = array(
     
'#type' => 'submit',
     
'#value' => t('Add/Delete Exception'),
    );
  }

  return
$element;
}
?>

The theming function

Then, a theming function to pretty it up. This is basically a theming function for a form item, but I put an inline container and $element['#children'] (which is the constructed select boxes, etc. from my expand function) into it. Naming convention for the theme function is theme_elementname.
Without a theming function, you won't get any output.

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

 
 

Drupal is a registered trademark of Dries Buytaert.