How to remove tabledrag rearranging from multiple-value field widgets

Posted By Brad Czerniak on Tuesday, March 19, 2013 - 15:03

Drupal developers tend to treat overriding theme functions as a last resort. However, it's sometimes necessary in order to eliminate default functionality provided by core.

One instance we recently found where the default theming was not to our client's expectations was the "unlimited" field widget (the widget is the form element on the edit screen, while a field formatter themes the output on the view screen). Unlimited widgets display with tabledrag re-ordering, without any option to disable it.

If you'd like to disable tabledrag re-ordering for unlimited field widgets, check out the solution below. For the sake of demonstration, I'm using a module named 'multi_widget_remove_tabledrag'.

Alter the Theme Registry

The first step is to change the theming function for multiple_value_form widgets:

  1. /**
  2.  * Implements hook_theme_registry_alter().
  3.  */
  4. function multi_widget_remove_tabledrag_theme_registry_alter(&$theme_registry) {
  5.   if (isset($theme_registry['field_multiple_value_form'])) {
  6.     $theme_registry['field_multiple_value_form']['type'] = 'module';
  7.     $theme_registry['field_multiple_value_form']['theme path'] = drupal_get_path('module', 'multi_widget_remove_tabledrag');
  8.     $theme_registry['field_multiple_value_form']['function'] = 'multi_widget_remove_tabledrag_theme_field_multiple_value_form';
  9.   }
  10. }

Override the theme function

All multi-value field widgets use theme_field_multiple_value_form(), unless they're overridden elsewhere. We're going to override the default theme function using a new function named in theme registry alter:

  1. /**
  2.  * Theme function override for multiple-value form widgets.
  3.  *
  4.  * @see theme_field_multiple_value_form()
  5.  */
  6. function multi_widget_remove_tabledrag_theme_field_multiple_value_form($variables) {
  7.   $element = $variables['element'];
  8.   $output = '';
  9.  
  10.   // The first condition is the override.
  11.   if (($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($element[0]['#nodrag'])) {
  12.     $table_id = drupal_html_id($element['#field_name'] . '_values');
  13.     $required = !empty($element['#required']) ? theme('form_required_marker', $variables) : '';
  14.  
  15.     $header = array(
  16.       array(
  17.         'data' => '<label>' . t('!title !required', array('!title' => $element['#title'], '!required' => $required)) . "</label>",
  18.         'class' => array('field-label'),
  19.       ),
  20.     );
  21.     $rows = array();
  22.  
  23.     // Sort items according to weight
  24.     $items = array();
  25.     foreach (element_children($element) as $key) {
  26.       if ($key === 'add_more') {
  27.         $add_more_button = &$element[$key];
  28.       }
  29.       else {
  30.         $items[] = &$element[$key];
  31.       }
  32.     }
  33.     usort($items, '_field_sort_items_value_helper');
  34.  
  35.     // Add the items as table rows.
  36.     foreach ($items as $key => $item) {
  37.       // We don't want the weight to render.
  38.       unset($item['_weight']);
  39.       $cells = array(
  40.         drupal_render($item),
  41.       );
  42.       $rows[] = array(
  43.         'data' => $cells,
  44.       );
  45.     }
  46.  
  47.     $output = '<div class="form-item">';
  48.     $output .= theme('table', array(
  49.       'header' => $header,
  50.       'rows' => $rows,
  51.       'attributes' => array(
  52.         'id' => $table_id,
  53.         'class' => array('field-multiple-table'),
  54.       ),
  55.     ));
  56.     $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
  57.     $output .= '<div class="clearfix">' . drupal_render($add_more_button) . '</div>';
  58.     $output .= '</div>';
  59.   }
  60.   elseif ($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
  61.     $table_id = drupal_html_id($element['#field_name'] . '_values');
  62.     $order_class = $element['#field_name'] . '-delta-order';
  63.     $required = !empty($element['#required']) ? theme('form_required_marker', $variables) : '';
  64.  
  65.     $header = array(
  66.       array(
  67.         'data' => '<label>' . t('!title !required', array('!title' => $element['#title'], '!required' => $required)) . "</label>",
  68.         'colspan' => 2,
  69.         'class' => array('field-label'),
  70.       ),
  71.       t('Order'),
  72.     );
  73.     $rows = array();
  74.  
  75.     // Sort items according to '_weight' (needed when the form comes back after
  76.     // preview or failed validation).
  77.     $items = array();
  78.     foreach (element_children($element) as $key) {
  79.       if ($key === 'add_more') {
  80.         $add_more_button = &$element[$key];
  81.       }
  82.       else {
  83.         $items[] = &$element[$key];
  84.       }
  85.     }
  86.     usort($items, '_field_sort_items_value_helper');
  87.  
  88.     // Add the items as table rows.
  89.     foreach ($items as $key => $item) {
  90.       $item['_weight']['#attributes']['class'] = array($order_class);
  91.       $delta_element = drupal_render($item['_weight']);
  92.       $cells = array(
  93.         array(
  94.           'data' => '',
  95.           'class' => array('field-multiple-drag'),
  96.         ),
  97.         drupal_render($item),
  98.         array(
  99.           'data' => $delta_element,
  100.           'class' => array('delta-order'),
  101.         ),
  102.       );
  103.       $rows[] = array(
  104.         'data' => $cells,
  105.         'class' => array('draggable'),
  106.       );
  107.     }
  108.  
  109.     $output = '<div class="form-item">';
  110.     $output .= theme('table', array(
  111.       'header' => $header,
  112.       'rows' => $rows,
  113.       'attributes' => array(
  114.         'id' => $table_id,
  115.         'class' => array('field-multiple-table'),
  116.       ),
  117.     ));
  118.     $output .= $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '';
  119.     $output .= '<div class="clearfix">' . drupal_render($add_more_button) . '</div>';
  120.     $output .= '</div>';
  121.  
  122.     drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
  123.   }
  124.   else {
  125.     foreach (element_children($element) as $key) {
  126.       $output .= drupal_render($element[$key]);
  127.     }
  128.   }
  129.  
  130.   return $output;
  131. }

This function just adds a condition that does new stuff for preprocessed elements, while leaving the Drupal defaults intact. The first if-statement looks for the first element to have a "nodrag" property. If it does, the code inside the if-statement builds the form element, albeit without the weights and tabledragging of the defaults.

Modify the Elements you want

The last step is to add an array row with the key "#nodrag" to the desired elements. You can use any logic you desire to do this, and even accomplish it at the widget level (if you're defining your own widget). If you just wish to modify an otherwise-default widget, hook_field_widget_form_alter() is a good choice:

  1. /**
  2.  * Implements hook_field_widget_form_alter().
  3.  */
  4. function multi_widget_remove_tabledrag_field_widget_form_alter(&$element, &$form_state, $context) {
  5.   if (isset($element['#field_name'])) {
  6.     switch ($element['#field_name']) {
  7.       case 'field_long_test':
  8.         $element['#nodrag'] = TRUE;
  9.       default:
  10.         break;
  11.     }
  12.   }
  13. }

As you can see, the example removes tabledrag from a field named 'field_long_test' specifically. This is the only part you'd have to edit if you were building this module yourself.

Wrapping Up

This solution took quite a bit of time and effort to finally pull off. I sincerely hope this post saves you some time if you have a similar problem in Drupal 7. Have a great day!

free 10 step drupal security audit guide