I wanted to display some data in a table. Not a very big table. But some of the cells in the table would be form elements; select boxes or text entry fields. That kind of thing.

There are a goodly number of examples out there about how to use theme('table', ...) to display a bunch of data as a table, but I tried it and I couldn't get it to work for form elements. Basically, standard technique for describing the data to display ended up rendering the form elements twice, once in the table, and then again below the table. Not what I wanted.

After much trial and error, I came up with a reasonably flexible way to do what I wanted. I'm sharing it here in case others find it useful. (And in the hope that others might point out improvements.)

This is Drupal 6.

Basically, here's the idea. The table description is built as a fieldset. There are three required elements in the fieldset:

  • #table_name is a tag for this table within the overall form.
  • #table_rows is an array of identifiers for table rows.
  • #table_cols is an array describing the table columns. Each element in the array is a a pair. The first element in the pair is the column tag, and the second is the title for the table header.

Then the rest of the table is a series of elements with indexes "<table tag>.<column tag>.<row tag>." The content of each element is the value (or form element) to go into that cell.

Here's a simple test example to create a table:

function demo_test_form(&$form_state) {
  $form = array();
  $rids = array(100,200,300);
  $form['#table_cols'] = array(
      array('title', t('Title')),
      array('lead', t('Select time')),
      array('email',t('Email address')),
      );
  $form['#table_rows'] = $rids;
  $form['#table_name'] = 'rem';
  foreach ($rids as $rid) {
    $form["rem_title_$rid"] = array('#value' => 'title ' . $rid);
    $form["rem_lead_$rid"] = array(
      '#type' => 'select',
      '#default_value' => 4,
      '#options' => array (2,4,6),
      );
    $form["rem_email_$rid"] = array(
        '#type' => 'textfield',
        '#size' => 50,
        '#resizable' => true,
        '#default_value' => 'email.' . $rid . "@foo.com",
    );
  }
  $form['#theme'] = 'demo_table';
  
  $supform = array();
  $supform = array (
    '#type' => 'fieldset',
    '#collapsable' => 'false'
  );
  $supform['rem'] = $form;
  $supform['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit Test'),
  );
  $supform['#submit'][] = 'demo_test_form_submit';
  return $supform;
}

And then a theme routine to render it:

function theme_demo_table($form) {
  $header = array();
  $rows = array();
  foreach ($form['#table_cols'] as $c) {
    $header[] = $c[1];
  }
  $tbl = $form['#table_name'];
  foreach ($form['#table_rows'] as $rtag) {
    $row = array();
    foreach ($form['#table_cols'] as $c) {
      $ctag = $c[0];
      $row[] = drupal_render($form["${tbl}_${ctag}_$rtag"]);
    }
    $rows[] = $row;
  }
  $output = theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

I make no claim that this is production-quality code. At the minimum the theme function should be tolerant of empty cells. I also will want to extend it to support a selector box for each row. But those are exercises left to the reader.

I'll post an image of the table that the above produces, but need to figure out how to upload it. If anyone has a pointer to how to do that, please let me know.