Bricolage

Creating a CCK Field module with an "invisible" widget

Most CCK Field modules have a widget where the user adds information upon creating a node which is then saved with the node. For the recent Etherpad module I wrote, I needed an “invisible” widget which saved with each new node some information from the field definition as well as autogenerated information. As I didn’t any documentation on how to do this, I thought I’d document it here quickly.

The first thing you do is define your database columns for your field in hookfieldsettings.

<?php
function etherpad_field_settings($op, $field) {
  switch ($op) {
    // Code removed.
    case 'database columns':
      return array(
        'etherpad_url' => array('type' => 'varchar', 'length' => 1024, 'not null' => FALSE,),
        'etherpad_text' => array('type' => 'text', 'not null' => TRUE, 'size' => 'big'),
        'attributes' => array('type' => 'text', 'size' => 'medium', 'not null' => FALSE),
      );
  }
}
?>

Next, you define your widget form inside hookwidget. Some tutorials I saw suggest you define your widget form in hookelements/hookprocess. I did that at first but decided against it as a) I never got it to work and b) it just adds needless complexity. Generally you’ll just want to define your widget in hookwidgets.

Two really important things here to get your “invisible” widget to work correctly. First, you must name your form keys the same as you named your database column names in hookfieldsettings. This tripped me up for a long time. CCK saves data by magic (you never explicitly save anything from a widget) and this is the key to getting the incantation to take. Second, using the ”value” field type was the key to creating an “invisible” field and getting my data saved correctly.

<?php
/**
 * Implementation of hook_widget().
 */
function etherpad_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element['etherpad_url'] = array(
    '#type' => 'value',
    '#value' => (isset($items[$delta]['etherpad_url']) && !empty($form['nid']['#value'])) ? $items[$delta]['etherpad_url'] : $field['etherpad_url'] . etherpad_generate_padid($field['etherpad_url']),
  );
  $element['etherpad_text'] = array(
    '#type' => 'value',
    '#value' => (isset($items[$delta]['etherpad_text']) && !empty($form['nid']['#value'])) ? $items[$delta]['etherpad_text'] : "default value for now until we have a function to generate one",
  );
  $element['attributes'] = array(
    '#type' => 'value',
    '#value' => (isset($items[$delta]['attributes']) && !empty($form['nid']['#value'])) ? $items[$delta]['attributes'] : serialize($field['attributes']),
  );
  // Used so that hook_field('validate') knows where to
  // flag an error in deeply nested forms.
  if (empty($form['#parents'])) {
    $form['#parents'] = array();
  }
  $element['_error_element'] = array(
    '#type' => 'value',
    '#value' => implode('][', array_merge($form['#parents'], array('value'))),
  );

  return $element;
}
?>

And that’s it! Read this and you’ll save yourself hours of frustration :)

One other note, the Devel module’s “reinstall module” function is very useful as you’ll be reinstalling the module often to reset the database w/ your changes. Enable the Devel block to access it.

This documentation doesn’t cover most of what you’ll need to know to write a CCK module. I relied heavily on the following tutorials.

Tagged with drupal | etherpad

Posted November 19, 2010


Kyle Mathews lives and works in San Francisco building useful things. You should follow him on Twitter