Same name and namespace in other branches
  1. 8.9.x 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

Hierarchy

Expanded class hierarchy of Table

1 file declares its use of Table
FieldUiTable.php in core/modules/field_ui/src/Element/FieldUiTable.php
9 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
Maps a CKEditor 4 button to the CKEditor 5 equivalent, if it exists.
MediaLibraryTestBase::openMediaLibraryForField in core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTestBase.php
Clicks a button that opens a media widget and confirms it is open.
MediaLibraryTestBase::switchToMediaLibraryTable in core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTestBase.php
Switches to the table display of the widget view.
MediaOverviewTest::testAdministrationPage in core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
Tests that the Media Library's administration page works as expected.

... See full list

File

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

Namespace

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

  /**
   * {@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 2
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. 10
MessengerTrait::messenger public function Gets the messenger. 10
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. 1
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 Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 38
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. 1
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