Themeing a form into a table with draggable rows in Drupal 7

Versions used: 

What I want is a form themed into a table, but I also want to to make the rows draggable so I can change the weight of each form entry. Below is an example of one way this can be done in Drupal 7. As an example, lets say we have an array of the main characters in the BBC series Red Dwarf (not including Holly). We want to display those characters into a form so our user can change those details, if they so wish. Furthermore, we want to allow the user to change the rank of each of the characters, and we do this by themeing the form into a table and making each row draggable. The form will look like this:

[wa_tokens:wa_tokens_form_table]

Go ahead and drag the rows (using the handles on the left of each row) to change the weight (rank) of the characters, then press submit to see the resulting array reflect those changes. You can play around and change the text if you want, but it won't be saved anywhere in this demo.

Okay, so to start, we need to create our custom form:

<?php
/**
 * Form: Form Table Example.
 */
function form_table_example_form($form, &$form_state) {

 
// the characters
 
$red_dwarf = array(
    array(
     
'name' => 'Dave Lister',
     
'type' => 'Human',
     
'rank' => 1,
    ),
    array(
     
'name' => 'Arnold Rimmer',
     
'type' => 'Hologram',
     
'rank' => 2,
    ),
    array(
     
'name' => 'Cat',
     
'type' => 'Humanoid',
     
'rank' => 3,
    ),
    array(
     
'name' => 'Kryten',
     
'type' => 'Android',
     
'rank' => 4,
    ),
  );
 
 
// create a parent element and use our custom theme
 
$form['characters'] = array(
   
'#prefix' => '<div id="red-dwarf">',
   
'#suffix' => '</div>',
   
'#tree' => TRUE,
   
'#theme' => 'form_table_theme_name'
 
);
 
 
// create the form elements for each character
 
foreach ($red_dwarf as $key => $characters) {
   
$form['characters'][$key]['name'] = array(
     
'#type' => 'textfield',
     
'#default_value' => $characters['name'],
    );
   
$form['characters'][$key]['type'] = array(
     
'#type' => 'textfield',
     
'#default_value' => $characters['type'],
    );
   
$form['characters'][$key]['weight'] = array(
     
'#type' => 'textfield',
     
'#default_value' => $characters['rank'],
     
'#size' => 3,
     
'#attributes' => array('class' => array('rank-weight')), // needed for table dragging
   
);
  }
 
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Submit'),
  );

  return
$form;
}
?>

Lets assume we are putting all our code in a module called form_table. In the form above we have an array of characters and for each of them we create the relevant form elements corresponding to the array values. Note that '#tree' => TRUEis needed so form values are not flattened which is important. '#theme' => 'form_table_theme_name'tells the form element to use our custom theme which we will now make. To do so, we need to first register the theme:

<?php
/**
 * Implements hook_theme().
 */
function form_table_theme($existing, $type, $theme, $path) {
  return array(
   
'form_table_theme_name' => array(
     
'render element' => 'element'
   
),
  );
}
?>

We now have to create the theme implementation:

<?php
function theme_form_table_theme_name($vars) {
 
$element = $vars['element'];
 
drupal_add_tabledrag('form_id', 'order', 'sibling', 'rank-weight'); // needed for table dragging
 
 
$header = array(
   
'name' => t('Name'),
   
'type' => t('Type'),
   
'weight' => t('Rank'),
  );
 
 
$rows = array();
  foreach (
element_children($element) as $key) {
   
$row = array();
   
   
$row['data'] = array();
    foreach (
$header as $fieldname => $title) {
     
$row['data'][] = drupal_render($element[$key][$fieldname]);
     
$row['class'] = array('draggable'); // needed for table dragging
   
}
   
$rows[] = $row;
  }
 
  return
theme('table', array(
   
'header' => $header,
   
'rows' => $rows,
   
'attributes' => array('id' => 'form_id'), // needed for table dragging
 
));
}
?>

In the theme function above, the form elements are passed to the function which allows us to loop through them and build the table data. Where I have place the comment 'needed for table dragging', you can read further information about how that works at http://api.drupal.org/api/drupal/includes!common.inc/function/drupal_add_tabledrag/7. Please find a download for a module below.

Downloads: