You are here

class Table in Drupal 10

Same name in this branch
  1. 10 core/lib/Drupal/Core/Render/Element/Table.php \Drupal\Core\Render\Element\Table
  2. 10 core/modules/views/src/Plugin/views/style/Table.php \Drupal\views\Plugin\views\style\Table
Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Render/Element/Table.php \Drupal\Core\Render\Element\Table
  2. 9 core/lib/Drupal/Core/Render/Element/Table.php \Drupal\Core\Render\Element\Table

Provides a render element for a table.

Note: Although this extends FormElement, it can be used outside the context of a form.

Properties:

  • #header: An array of table header labels.
  • #rows: An array of the rows to be displayed. Each row is either an array of cell contents or an array of properties as described in table.html.twig Alternatively specify the data for the table as child elements of the table element. Table elements would contain rows elements that would in turn contain column elements.
  • #empty: Text to display when no rows are present.
  • #responsive: Indicates whether to add the drupal.tableresponsive library providing responsive tables. Defaults to TRUE.
  • #sticky: Indicates whether to add the drupal.tableheader library that makes table headers always visible at the top of the page. Defaults to FALSE.

Usage example:

$form['contacts'] = array(
  '#type' => 'table',
  '#caption' => $this
    ->t('Sample Table'),
  '#header' => array(
    $this
      ->t('Name'),
    $this
      ->t('Phone'),
  ),
);
for ($i = 1; $i <= 4; $i++) {
  $form['contacts'][$i]['#attributes'] = array(
    'class' => array(
      'foo',
      'baz',
    ),
  );
  $form['contacts'][$i]['name'] = array(
    '#type' => 'textfield',
    '#title' => $this
      ->t('Name'),
    '#title_display' => 'invisible',
  );
  $form['contacts'][$i]['phone'] = array(
    '#type' => 'tel',
    '#title' => $this
      ->t('Phone'),
    '#title_display' => 'invisible',
  );
}
$form['contacts'][]['colspan_example'] = array(
  '#plain_text' => 'Colspan Example',
  '#wrapper_attributes' => array(
    'colspan' => 2,
    'class' => array(
      'foo',
      'bar',
    ),
  ),
);

Plugin annotation

@FormElement("table");

Hierarchy

Expanded class hierarchy of Table

See also

\Drupal\Core\Render\Element\Tableselect

1 file declares its use of Table
FieldUiTable.php in core/modules/field_ui/src/Element/FieldUiTable.php
14 string references to 'Table'
ckeditor5.ckeditor5.yml in core/modules/ckeditor5/ckeditor5.ckeditor5.yml
core/modules/ckeditor5/ckeditor5.ckeditor5.yml
Core::mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem in core/modules/ckeditor5/src/Plugin/CKEditor4To5Upgrade/Core.php
drupal6.php in core/modules/migrate_drupal/tests/fixtures/drupal6.php
A database agnostic dump for testing purposes.
editor.editor.full_html.yml in core/profiles/standard/config/install/editor.editor.full_html.yml
core/profiles/standard/config/install/editor.editor.full_html.yml
editor.editor.full_html.yml in core/profiles/demo_umami/config/install/editor.editor.full_html.yml
core/profiles/demo_umami/config/install/editor.editor.full_html.yml

... See full list

61 #type uses of Table
BanAdmin::buildForm in core/modules/ban/src/Form/BanAdmin.php
BlockLibraryController::listBlocks in core/modules/block/src/Controller/BlockLibraryController.php
Shows a list of blocks that can be added to a theme's layout.
BlockListBuilder::buildBlocksForm in core/modules/block/src/BlockListBuilder.php
Builds the main "Blocks" portion of the form.
BookAdminEditForm::bookAdminTable in core/modules/book/src/Form/BookAdminEditForm.php
Builds the table portion of the form for the book administration page.
BookController::adminOverview in core/modules/book/src/Controller/BookController.php
Returns an administrative overview of all books.

... See full list

File

core/lib/Drupal/Core/Render/Element/Table.php, line 60

Namespace

Drupal\Core\Render\Element
View source
class Table extends FormElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = static::class;
    return [
      '#header' => [],
      '#rows' => [],
      '#empty' => '',
      // Properties for tableselect support.
      '#input' => TRUE,
      '#tree' => TRUE,
      '#tableselect' => FALSE,
      '#sticky' => FALSE,
      '#responsive' => TRUE,
      '#multiple' => TRUE,
      '#js_select' => TRUE,
      '#process' => [
        [
          $class,
          'processTable',
        ],
      ],
      '#element_validate' => [
        [
          $class,
          'validateTable',
        ],
      ],
      // Properties for tabledrag support.
      // The value is a list of arrays that are passed to
      // drupal_attach_tabledrag(). Table::preRenderTable() prepends the HTML ID
      // of the table to each set of options.
      // @see drupal_attach_tabledrag()
      '#tabledrag' => [],
      // Render properties.
      '#pre_render' => [
        [
          $class,
          'preRenderTable',
        ],
      ],
      '#theme' => 'table',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {

    // If #multiple is FALSE, the regular default value of radio buttons is used.
    if (!empty($element['#tableselect']) && !empty($element['#multiple'])) {

      // Contrary to #type 'checkboxes', the default value of checkboxes in a
      // table is built from the array keys (instead of array values) of the
      // #default_value property.
      // @todo D8: Remove this inconsistency.
      if ($input === FALSE) {
        $element += [
          '#default_value' => [],
        ];
        $value = array_keys(array_filter($element['#default_value']));
        return array_combine($value, $value);
      }
      else {
        return is_array($input) ? array_combine($input, $input) : [];
      }
    }
  }

  /**
   * #process callback for #type 'table' to add tableselect support.
   *
   * @param array $element
   *   An associative array containing the properties and children of the
   *   table element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   *
   * @return array
   *   The processed element.
   */
  public static function processTable(&$element, FormStateInterface $form_state, &$complete_form) {
    if ($element['#tableselect']) {
      if ($element['#multiple']) {
        $value = is_array($element['#value']) ? $element['#value'] : [];
      }
      else {
        $element['#js_select'] = FALSE;
      }

      // Add a "Select all" checkbox column to the header.
      // @todo D8: Rename into #select_all?
      if ($element['#js_select']) {
        $element['#attached']['library'][] = 'core/drupal.tableselect';
        array_unshift($element['#header'], [
          'class' => [
            'select-all',
          ],
        ]);
      }
      else {
        array_unshift($element['#header'], '');
      }
      if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
        $element['#default_value'] = [];
      }

      // Create a checkbox or radio for each row in a way that the value of the
      // tableselect element behaves as if it had been of #type checkboxes or
      // radios.
      foreach (Element::children($element) as $key) {
        $row =& $element[$key];

        // Prepare the element #parents for the tableselect form element.
        // Their values have to be located in child keys (#tree is ignored),
        // since Table::validateTable() has to be able to validate whether input
        // (for the parent #type 'table' element) has been submitted.
        $element_parents = array_merge($element['#parents'], [
          $key,
        ]);

        // Since the #parents of the tableselect form element will equal the
        // #parents of the row element, prevent FormBuilder from auto-generating
        // an #id for the row element, since
        // \Drupal\Component\Utility\Html::getUniqueId() would automatically
        // append a suffix to the tableselect form element's #id otherwise.
        $row['#id'] = HtmlUtility::getUniqueId('edit-' . implode('-', $element_parents) . '-row');

        // Do not overwrite manually created children.
        if (!isset($row['select'])) {

          // Determine option label; either an assumed 'title' column, or the
          // first available column containing a #title or #markup.
          // @todo Consider to add an optional $element[$key]['#title_key']
          //   defaulting to 'title'?
          unset($label_element);
          $title = NULL;
          if (isset($row['title']['#type']) && $row['title']['#type'] == 'label') {
            $label_element =& $row['title'];
          }
          else {
            if (!empty($row['title']['#title'])) {
              $title = $row['title']['#title'];
            }
            else {
              foreach (Element::children($row) as $column) {
                if (isset($row[$column]['#title'])) {
                  $title = $row[$column]['#title'];
                  break;
                }
                if (isset($row[$column]['#markup'])) {
                  $title = $row[$column]['#markup'];
                  break;
                }
              }
            }
            if (isset($title) && $title !== '') {
              $title = t('Update @title', [
                '@title' => $title,
              ]);
            }
          }

          // Prepend the select column to existing columns.
          $row = [
            'select' => [],
          ] + $row;
          $row['select'] += [
            '#type' => $element['#multiple'] ? 'checkbox' : 'radio',
            '#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $element_parents)),
            // @todo If rows happen to use numeric indexes instead of string keys,
            //   this results in a first row with $key === 0, which is always FALSE.
            '#return_value' => $key,
            '#attributes' => $element['#attributes'],
            '#wrapper_attributes' => [
              'class' => [
                'table-select',
              ],
            ],
          ];
          if ($element['#multiple']) {
            $row['select']['#default_value'] = isset($value[$key]) ? $key : NULL;
            $row['select']['#parents'] = $element_parents;
          }
          else {
            $row['select']['#default_value'] = $element['#default_value'] == $key ? $key : NULL;
            $row['select']['#parents'] = $element['#parents'];
          }
          if (isset($label_element)) {
            $label_element['#id'] = $row['select']['#id'] . '--label';
            $label_element['#for'] = $row['select']['#id'];
            $row['select']['#attributes']['aria-labelledby'] = $label_element['#id'];
            $row['select']['#title_display'] = 'none';
          }
          else {
            $row['select']['#title'] = $title;
            $row['select']['#title_display'] = 'invisible';
          }
        }
      }
    }
    return $element;
  }

  /**
   * #element_validate callback for #type 'table'.
   *
   * @param array $element
   *   An associative array containing the properties and children of the
   *   table element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $complete_form
   *   The complete form structure.
   */
  public static function validateTable(&$element, FormStateInterface $form_state, &$complete_form) {

    // Skip this validation if the button to submit the form does not require
    // selected table row data.
    $triggering_element = $form_state
      ->getTriggeringElement();
    if (empty($triggering_element['#tableselect'])) {
      return;
    }
    if ($element['#multiple']) {
      if (!is_array($element['#value']) || !count(array_filter($element['#value']))) {
        $form_state
          ->setError($element, t('No items selected.'));
      }
    }
    elseif (!isset($element['#value']) || $element['#value'] === '') {
      $form_state
        ->setError($element, t('No item selected.'));
    }
  }

  /**
   * #pre_render callback to transform children of an element of #type 'table'.
   *
   * This function converts sub-elements of an element of #type 'table' to be
   * suitable for table.html.twig:
   * - The first level of sub-elements are table rows. Only the #attributes
   *   property is taken into account.
   * - The second level of sub-elements is converted into columns for the
   *   corresponding first-level table row.
   *
   * Simple example usage:
   * @code
   * $form['table'] = array(
   *   '#type' => 'table',
   *   '#header' => array($this->t('Title'), array('data' => $this->t('Operations'), 'colspan' => '1')),
   *   // Optionally, to add tableDrag support:
   *   '#tabledrag' => array(
   *     array(
   *       'action' => 'order',
   *       'relationship' => 'sibling',
   *       'group' => 'thing-weight',
   *     ),
   *   ),
   * );
   * foreach ($things as $row => $thing) {
   *   $form['table'][$row]['#weight'] = $thing['weight'];
   *
   *   $form['table'][$row]['title'] = array(
   *     '#type' => 'textfield',
   *     '#default_value' => $thing['title'],
   *   );
   *
   *   // Optionally, to add tableDrag support:
   *   $form['table'][$row]['#attributes']['class'][] = 'draggable';
   *   $form['table'][$row]['weight'] = array(
   *     '#type' => 'textfield',
   *     '#title' => $this->t('Weight for @title', array('@title' => $thing['title'])),
   *     '#title_display' => 'invisible',
   *     '#size' => 4,
   *     '#default_value' => $thing['weight'],
   *     '#attributes' => array('class' => array('thing-weight')),
   *   );
   *
   *   // The amount of link columns should be identical to the 'colspan'
   *   // attribute in #header above.
   *   $form['table'][$row]['edit'] = array(
   *     '#type' => 'link',
   *     '#title' => $this->t('Edit'),
   *     '#url' => Url::fromRoute('entity.test_entity.edit_form', ['test_entity' => $row]),
   *   );
   * }
   * @endcode
   *
   * @param array $element
   *   A structured array containing two sub-levels of elements. Properties used:
   *   - #tabledrag: The value is a list of $options arrays that are passed to
   *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
   *     $options array.
   *
   * @return array
   *
   * @see template_preprocess_table()
   * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
   * @see drupal_attach_tabledrag()
   */
  public static function preRenderTable($element) {
    foreach (Element::children($element) as $first) {
      $row = [
        'data' => [],
      ];

      // Apply attributes of first-level elements as table row attributes.
      if (isset($element[$first]['#attributes'])) {
        $row += $element[$first]['#attributes'];
      }

      // Turn second-level elements into table row columns.
      // @todo Do not render a cell for children of #type 'value'.
      // @see https://www.drupal.org/node/1248940
      foreach (Element::children($element[$first]) as $second) {

        // Assign the element by reference, so any potential changes to the
        // original element are taken over.
        $column = [
          'data' => &$element[$first][$second],
        ];

        // Apply wrapper attributes of second-level elements as table cell
        // attributes.
        if (isset($element[$first][$second]['#wrapper_attributes'])) {
          $column += $element[$first][$second]['#wrapper_attributes'];
        }
        $row['data'][] = $column;
      }
      $element['#rows'][] = $row;
    }

    // Take over $element['#id'] as HTML ID attribute, if not already set.
    Element::setAttributes($element, [
      'id',
    ]);

    // Add sticky headers, if applicable.
    if (count($element['#header']) && $element['#sticky']) {
      $element['#attached']['library'][] = 'core/drupal.tableheader';

      // Add 'sticky-enabled' class to the table to identify it for JS.
      // This is needed to target tables constructed by this function.
      $element['#attributes']['class'][] = 'sticky-enabled';
    }

    // If the table has headers and it should react responsively to columns hidden
    // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
    // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
    if (count($element['#header']) && $element['#responsive']) {
      $element['#attached']['library'][] = 'core/drupal.tableresponsive';

      // Add 'responsive-enabled' class to the table to identify it for JS.
      // This is needed to target tables constructed by this function.
      $element['#attributes']['class'][] = 'responsive-enabled';
    }

    // If the custom #tabledrag is set and there is an HTML ID, add the table's
    // HTML ID to the options and attach the behavior.
    if (!empty($element['#tabledrag']) && isset($element['#attributes']['id'])) {
      foreach ($element['#tabledrag'] as $options) {
        $options['table_id'] = $element['#attributes']['id'];
        drupal_attach_tabledrag($element, $options);
      }
    }
    return $element;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FormElement::processAutocomplete public static function Adds autocomplete functionality to elements.
FormElement::processPattern public static function #process callback for #pattern form element property.
FormElement::validatePattern public static function #element_validate callback for #pattern form element property.
MessengerTrait::$messenger protected property The messenger. 13
MessengerTrait::messenger public function Gets the messenger. 13
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition.
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function
PluginBase::getDerivativeId public function
PluginBase::getPluginDefinition public function 1
PluginBase::getPluginId public function
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 24
RenderElement::preRenderAjaxForm public static function Adds Ajax information about an element to communicate with JavaScript.
RenderElement::preRenderGroup public static function Adds members of this group as actual elements for rendering.
RenderElement::processAjaxForm public static function Form element processing handler for the #ajax form property. 1
RenderElement::processGroup public static function Arranges elements into groups.
RenderElement::setAttributes public static function Sets a form element's class attribute. Overrides ElementInterface::setAttributes
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use.
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
Table::getInfo public function Returns the element properties for this element. Overrides ElementInterface::getInfo 2
Table::preRenderTable public static function #pre_render callback to transform children of an element of #type 'table'.
Table::processTable public static function #process callback for #type 'table' to add tableselect support.
Table::validateTable public static function #element_validate callback for #type 'table'.
Table::valueCallback public static function Determines how user input is mapped to an element's #value property. Overrides FormElement::valueCallback 1