Building and rendering a table required to write a custom/dedicated theme function for most tables in Drupal 7. TableSelect (bulk operations) and TableDrag (drag & drop) support had to be added and attached manually when necessary. This led to many duplicate/unnecessary theme functions, which in turn made it hard for other modules to enhance or alter a table.
Drupal 8 introduces a new element '#type' => 'table'
, which comes with built-in, optional support for TableDrag and TableSelect, and which requires no more custom plumbing for most use-cases.
Note: The following conversion example shows both TableDrag and TableSelect in a single table for brevity, but they are usually not combined in a single table.
Drupal 7
/**
* Form constructor for the administrative listing/overview form.
*/
function mymodule_admin_overview_form($form, &$form_state) {
$form['mytable'] = array(
'#tree' => TRUE,
// Note: For TableSelect instead of TableDrag, you would have specified
// 'tableselect' as render element #type instead of this #theme.
'#theme' => 'mymodule_admin_overview_form_table',
);
foreach ($entities as $id => $entity) {
// Some table columns.
$form['mytable'][$id]['title'] = $entity->title;
$form['mytable'][$id]['name'] = $entity->name;
// Prepare for TableDrag support.
$form['mytable'][$id]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $entity->title)),
'#title_display' => 'invisible',
'#default_value' => $entity->weight,
);
// Operation columns.
$form['mytable'][$id]['edit'] = array(
'#type' => 'link',
'#title' => t('edit'),
'#href' => "admin/config/mymodule/$id",
);
$form['mytable'][$id]['delete'] = array(
'#type' => 'link',
'#title' => t('delete'),
'#href' => "admin/config/mymodule/$id/delete",
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save changes'),
);
return $form;
}
/**
* Form validation handler for mymodule_admin_overview_form().
*
* Note: Only applies to TableSelect (bulk operation) forms, not TableDrag.
*/
function mymodule_admin_overview_form_validate($form, &$form_state) {
// Throw an error in case no items have been selected.
if (!is_array($form_state['values']['mytable']) || !count(array_filter($form_state['values']['mytable']))) {
form_set_error('', t('No items selected.'));
}
}
/**
* Implements hook_theme().
*/
function mymodule_theme() {
$theme['mymodule_admin_overview_form_table'] = array(
'render element' => 'element',
'file' => 'mymodule.admin.inc',
);
return $theme;
}
/**
* Returns HTML for the table in the administrative listing/overview form.
*
* @param $variables
* An associative array containing:
* - element: A render element representing the table in the form.
*
* @ingroup themeable
*/
function theme_mymodule_admin_overview_form_table($variables) {
$element = $variables['element'];
$rows = array();
foreach (element_children($element) as $id) {
// Classify the weight element for TableDrag.
$element[$id]['weight']['#attributes']['class'] = array('mytable-order-weight');
// Mark the table row as draggable for TableDrag.
$row = array(
'data' => array(),
'class' => array('draggable'),
);
// Render the table columns.
$row['data'][] = drupal_render($element[$id]['title']);
$row['data'][] = drupal_render($element[$id]['name']);
$row['data'][] = drupal_render($element[$id]['weight']);
$row['data'][] = drupal_render($element[$id]['edit']);
$row['data'][] = drupal_render($element[$id]['delete']);
$rows[] = $row;
}
// Build the table header.
$header = array(
t('Title'),
t('Machine name'),
t('Weight'),
array('data' => t('Operations'), 'colspan' => 2),
);
// Render the table.
// Note: For TableSelect instead of TableDrag, you would have specified
// 'tableselect' as render element #type and passed the $rows as 'options'
// instead of 'rows'.
$output = theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => 'mytable-order'),
));
$output .= drupal_render_children($element);
// Attach TableDrag to the table ID and contained weight elements.
drupal_add_tabledrag('mytable-order', 'order', 'sibling', 'mytable-order-weight');
return $output;
}
Drupal 8
use Drupal\Component\Utility\String;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Form constructor for the administrative listing/overview form.
*/
function mymodule_admin_overview_form($form, FormStateInterface $form_state) {
$form['mytable'] = array(
'#type' => 'table',
'#header' => array(t('Label'), t('Machine name'), t('Weight'), t('Operations')),
'#empty' => t('There are no items yet. <a href="@add-url">Add an item.</a>', array(
'@add-url' => Url::fromRoute('mymodule.manage_add'),
)),
// TableSelect: Injects a first column containing the selection widget into
// each table row.
// Note that you also need to set #tableselect on each form submit button
// that relies on non-empty selection values (see below).
'#tableselect' => TRUE,
// TableDrag: Each array value is a list of callback arguments for
// drupal_add_tabledrag(). The #id of the table is automatically prepended;
// if there is none, an HTML ID is auto-generated.
'#tabledrag' => array(
array(
'action' => 'order',
'relationship' => 'sibling',
'group' => 'mytable-order-weight',
),
),
);
// Build the table rows and columns.
// The first nested level in the render array forms the table row, on which you
// likely want to set #attributes and #weight.
// Each child element on the second level represents a table column cell in the
// respective table row, which are render elements on their own. For single
// output elements, use the table cell itself for the render element. If a cell
// should contain multiple elements, simply use nested sub-keys to build the
// render element structure for drupal_render() as you would everywhere else.
foreach ($entities as $id => $entity) {
// TableDrag: Mark the table row as draggable.
$form['mytable'][$id]['#attributes']['class'][] = 'draggable';
// TableDrag: Sort the table row according to its existing/configured weight.
$form['mytable'][$id]['#weight'] = $entity->get('weight');
// Some table columns containing raw markup.
$form['mytable'][$id]['label'] = array(
'#plain_text' => $entity->label(),
);
$form['mytable'][$id]['id'] = array(
'#plain_text' => $entity->id(),
);
// TableDrag: Weight column element.
// NOTE: The tabledrag javascript puts the drag handles inside the first column,
// then hides the weight column. This means that tabledrag handle will not show
// if the weight element will be in the first column so place it further as in this example.
$form['mytable'][$id]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $entity->label())),
'#title_display' => 'invisible',
'#default_value' => $entity->get('weight'),
// Classify the weight element for #tabledrag.
'#attributes' => array('class' => array('mytable-order-weight')),
);
// Operations (dropbutton) column.
$form['mytable'][$id]['operations'] = array(
'#type' => 'operations',
'#links' => array(),
);
$form['mytable'][$id]['operations']['#links']['edit'] = array(
'title' => t('Edit'),
'url' => Url::fromRoute('mymodule.manage_edit', array('id' => $id)),
);
$form['mytable'][$id]['operations']['#links']['delete'] = array(
'title' => t('Delete'),
'url' => Url::fromRoute('mymodule.manage_delete', array('id' => $id)),
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save changes'),
// TableSelect: Enable the built-in form validation for #tableselect for
// this form button, so as to ensure that the bulk operations form cannot
// be submitted without any selected items.
'#tableselect' => TRUE,
);
return $form;
}
Note: You only want to use one of both, either #tabledrag or #tableselect in a single table, not both. This example only contains both for brevity.
In short:
- The render array structure defines the whole table.
- The corresponding theme function, form validation handler, and hook_theme() entry can be removed.
- Use #tabledrag to attach a TableDrag behavior for table rows to allow sorting.
- Use #tableselect to inject a TableSelect form widget as first column in all table rows.
Comments
Please, can someone add a
Please, can someone add a example how to use this with #tabledrag and multilevel?
Fernando Correa da Conceição
http://jaguaribe.net.br
Weight issue
When I try this example, the table lines are not ordered by the defined weight.Does someone has any idea ?
Don't know how, but it works now. Thanks for the example !
Use theme function!
For anyone doing more advanced type tabledrag forms. It's best to add the table drag element in a theme function (see /core/lib/Drupa/Core/Field/WidgetBase.php and /core/include/theme.inc for an example.) The problem is that adding certain elements into a draggable table in the form build can cause validation errors. An example of this is a DateTime field.
---------------------
HollyIT - Grab the Netbeans Drupal Development Tool at GitHub.
Thanks Jamie, but can you
Thanks Jamie, but can you please explain more how this procedure works? I tried to read the two files there, but I had no luck there.
This doesn't seem to be
This doesn't seem to be related to the table being draggable: https://www.drupal.org/node/2703941
Using TableSelect with TableDrag
But what if I DO want to use both, say to apply weights (TableDrag) AND to enable/disable an item (TableSelect) on the same form?
"Aut inveniam viam aut faciam" - I shall either find a way or make one.
A workaround I've come to...
A workaround I've come to was to add a checkbox element for each row while using TableDrag. There's no Select All behavior but that could possibly be implemented without much effort on the front-end.
"Aut inveniam viam aut faciam" - I shall either find a way or make one.
Before I read that, I had
Before I read that, I had created a table with both (in 7.x) using the Elements module, and it seems to work.I can't, for the life of me, figure out how you know what rows were selected.NancyDru
With this example I dont have
With this example I dont have a table weight column I needed to attach drupal.tabledrag but still the table is not sortable.