Element handler plugins: implement new form element types

Last updated on
30 April 2025

Finder element types, e.g. autocomplete, select, buttons, etc.. are handled by CTools plugins defined by the Finder module by a kind of plugin called an element handler.

This is a guide for how module developers can create their own element handlers.

1. Make Finder aware of your plugin

To create your own element handler, you must first implement hook_ctools_plugin_directory().

You would typically add something like this to your main module file:

<?php
/**
 * Implements hook_ctools_plugin_directory().
 */
function YOURMODULE_ctools_plugin_directory($module, $plugin) {
  if ($module == 'finder') {
    return "plugins/$plugin";
  }
}
?>

The "plugins/$plugin" bit means that you will put a plugin file in the directory at "plugins/element_handler" within your module's directory, you can change that, but this is the recommended way to do it.

2. Create the plugin file

The next step is to create a file for your plugin. If your new form element type was going to be called mywidget then you would create the file under your module directory at the path /plugins/element_handler/mywidget.inc and it will be found after a cache rebuild once you define the plugin.

You can create more than one of these files, one for each plugin you wish to define.

3. Define the plugin

The contents of the plugin file should start with a global variable called $plugin, which is an array containing one key; the machine name of your element type. The value of this key is a data array with at least three array elements:

  • 'title' - The translatable title of this element handler.
  • 'description' - The translatable description.
  • 'settings callback' - Normally you supply a function name for this which is the function that will be used to add settings for the finder element. You can also supply an array with keys 'file', 'path', and 'function' if the function is in another file. Keep reading to find out how to implement that function.
  • 'element callback' - Normally you supply a function name for this which is the function that will be used to create the form element. You can also supply an array with keys 'file', 'path', and 'function' if the function is in another file (note that you should test your elements with ajax submissions turned on in finder's settings to ensure the file is getting included properly - you may need to use form_load_include() trickery to avoid problems if for example your validate function is not in the main plugin file). See below for information on writing that function.

Here is an example of the top of the 'select.inc' file which is one of the element handler plugins that Finder comes with:

<?php

/**
 * @file
 * The finder select element handler plugin.
 */

/**
 * The CTools plugin definition.
 */
$plugin = array(
  'select' => array(
    'title' => t('Select list'),
    'description' => t('A drop-down menu of choices or scrolling selection box.'),
    'settings callback' => 'finder_select_settings',
    'element callback' => 'finder_select_element',
  ),
);
?>

There are also some optional settings you can add to your plugin.

  • 'menu' - Provide hook_menu items. Finder will add these to a hook_menu implementation on your behalf.

    Example from autocomplete.inc:

    <?php
        'menu' => array(
          'finder_autocomplete/autocomplete' => array(
            'page callback' => 'finder_autocomplete_autocomplete',
            'access arguments' => array('use finder'),
            'type' => MENU_CALLBACK,
            'file' => 'autocomplete.inc',
            'file path' => drupal_get_path('module', 'finder') . '/plugins/element_handler/',
          ),
        ),
    ?>
    
  • 'theme' - Provide hook_theme items. Finder will add these to a hook_theme implementation on your behalf.

    Example from autocomplete.inc:

    <?php
        'theme' => array(
          'finder_autocomplete_textfield' => array(
            'render element' => 'element',
            'file' => 'autocomplete.inc',
            'path' => drupal_get_path('module', 'finder') . '/plugins/element_handler/',
          ),
        ),
    ?>
    
  • 'permission' - Provide hook_permission items. Finder will add these to a hook_permission implementation on your behalf.

    Example:

    <?php
        'permission' => array(
          'use mywidget' => array(
            'title' => t('Use mywidget'),
            'description' => t('This allows users to use mywidget.'),
          ),
        ),
    ?>
    
  • 'skip element callback' - Sometimes an element does not need to be included in the form building process. If that's the case provide a callback name here to a function that takes parameters $element and $form_state. If the function returns TRUE the element and it's children will be skipped during form building. You can also supply an array with keys 'file', 'path', and 'function' if the function is in another file.

In addition to these, CTools provides some additional options you can add here. See the Chaos Tools help which is viewable through the Advanced Help module.

4. Implement the settings callback

The function named as the settings callback for the plugin will take three parameters:

  • &$data
    A reference to an array of data that will be used to create the settings in the Finder UI module.

    It is the goal of your implementation to modify this array. Your function does not return anything.

    Notable items in the $data array are the list of group data in $data['groups'] which is an array keyed by the column number that the groups are in, and $data['items'] which is an array of all the settings, this is usually the main thing you'd be interested in.

  • $finder
    The finder object.
  • $finder_element_id
    The machine name of the element. You can get the actual element object from $finder->elements[$finder_element_id].

Here is a shortened example of an implementation of a settings function, it is documented line by line:

<?php

/**
 * Settings callback.
 */
function finder_text_settings(&$data, $finder, $finder_element_id) {
  // Set up a reference for convenience.
  $items = &$data['items'];
  // Get the element object.
  $element = $finder->elements[$finder_element_id];

  // Remove the 'choices' group as it is not applicable to this element handler.
  unset($items['groups'][2]['choices']);

  // Change an existing setting's form element type to a textarea, as that makes more sense for this element type.
  $items['default_value']['#form']['#settings']['default_value']['#type'] = 'textarea';

  // Add a settings group called 'maxlength' (this doesn't have to be the same name as the setting itself.
  $items['maxlength'] = array(
    // Put this in the 'form' group, as defined in $data['groups'].
    '#group' => 'form',
    // Define the settings item for the UI.
    '#item' => array(
      // Give the item a translated title.
      '#title' => t('Max length'),
      // This is the text for the clickable link that launches the popup.
      '#value' => $finder->esetting($element, 'maxlength') ? $finder->esetting($element, 'maxlength') : t('No'),
    ),
    // Create a form definition (the form to appear in the popup).
    '#form' => array(
      // For the setting to be automatically saved into the finder settings array, it has to be under 'settings' like so:
      'settings' => array(
        // Define a form element to configure the setting.  This setting's value will be available later at $finder->esetting($element, 'maxlength').
        'maxlength' => array(
          '#type' => 'textfield',
          '#title' => t('Max length'),
          '#default_value' => $finder->esetting($element, 'maxlength'),
          '#description' => t('The maximum amount of characters to accept as input.'),
          // This is a special property for integration with i18nstrings.  This indicates not to translate this setting.
          '#translatable' => FALSE,
        ),
      ),
    ),
  );

}

?>

For more thorough examples see the element handler plugins defined by Finder.

5. Implement the element callback

The element callback is where you will define your form element, and customise it according to the settings you created.

An element callback takes two arguments:

  • $element
    The finder element object. The actual finder is at $element->finder.
  • &$form_element
    This is a reference to the form element. Your function does not return anything, but rather modifies this form element.

Here is an example from text.inc of an element implementation. It even sets it's own validation callback.

<?php
/**
 * Element callback.
 */
function finder_text_element($element, &$form_element) {
  $finder = $element->finder;
  $properties = array();
  if ($finder->esetting($element, 'rows')) {
    $form_element['#type'] = 'textarea';
    $properties[] = 'rows';
  }
  else {
    $form_element['#type'] = 'textfield';
    $properties[] = 'maxlength';
    $properties[] = 'minlength';
    $properties[] = 'size';
  }
  foreach ($properties as $property) {
    $value = $finder->esetting($element, $property);
    if ($value) {
      $form_element['#' . $property] = $value;
    }
  }
  $form_element['#element_validate'][] = 'finder_text_element_validate';
}
?>

6. Elements that need choices

Some elements, like autocompletes, and selects, need a way to get a list of choices to use.

This is achieved by programatically executing finder in a 'choices' mode which Finder is built to handle. In such cases Finder tries to return a unique list of key/value pairs to be used as choices. You can still supply 'keywords' for the finder, which is useful in cases like autocomplete elements.

Here is an example of how a select element get it's options:

<?php
  $finder->find = array(
    'mode' => 'choices',
    'keywords' => array($element->id => array(NULL)),
    'element' => $element,
  );
  $finder->find();
  $form_element['#options'] = !empty($finder->find['results']) ? $finder->find['results'] : array();
?>

The first thing it does is set some values in $finder->find, which is an array of temporary settings relating to the execution of a finder 'find' operation.
It is important to pass the $element as 'element', and to set keywords - even if there are none - because the element id itself in the key of the keywords will trigger the correct behaviour, and to set 'mode' to 'choices'.
Then $finder->find(); is executed, and the resulting data appears in $finder->find['results'];

Here is another example from autocomplete.inc:

<?php
  $finder->find = array(
    'mode' => 'choices',
    'match' => $finder->esetting($element, 'autocomplete_match'),
    'field_logic' => $finder->esetting($element, 'autocomplete_field_logic'),
    'value_logic' => $finder->esetting($element, 'autocomplete_value_logic'),
    'nesting_order' => $finder->esetting($element, 'autocomplete_nesting_order'),
    'pager' => $finder->esetting($element, 'max_suggestions'),
    'keywords' => array($element->id => $keywords),
    'element' => $element,
  );
  $finder->find();
  $choices = !empty($finder->find['results']) ? $finder->find['results'] : array();
?>

Notice that in this case a whole lot of extra settings are put into $finder->find. This is to ensure the correctly configured behaviour of the query for autocompletes. Admittedly, these properties are only supported because the autocomplete element handler plugin needed them, but they can also be exploited by other plugins.

Conclusion

So that's pretty much it. If you have any other files like js or css to go with your plugin, just put them in the same directory as the plugin file. CTools will only scan '.inc' files for plugin definitions.

See the file finder.api.php, in the finder module directory, for a list of hooks you can use to manipulate finders.

Help improve this page

Page status: Not set

You can: