Commit 770e6f08 authored by catch's avatar catch
Browse files

task: #3035343 Deprecate drupal_attach_tabledrag(). Move its logic in Table form element

By: andypost
By: catch
By: finnsky
By: voleger
By: naresh_bavaskar
By: nod_
By: alexpott
By: daffie
By: berdir
By: claudiu.cristea
By: kim.pepper
By: smustgrave
By: nicxvan
parent 2f56cc33
Loading
Loading
Loading
Loading
Loading
+7 −143
Changes for core/includes/common.inc: 7 added lines, 143 removed lines.
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheTagsPurgeInterface;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Render\Element\Table;

/**
 * @defgroup php_wrappers PHP wrapper functions
@@ -127,153 +128,16 @@ function base_path() {
/**
 * Assists in attaching the tableDrag JavaScript behavior to a themed table.
 *
 * Draggable tables should be used wherever an outline or list of sortable items
 * needs to be arranged by an end-user. Draggable tables are very flexible and
 * can manipulate the value of form elements placed within individual columns.
 *
 * To set up a table to use drag and drop in place of weight select-lists or in
 * place of a form that contains parent relationships, the form must be themed
 * into a table. The table must have an ID attribute set and it
 * may be set as follows:
 * @code
 * $table = [
 *   '#type' => 'table',
 *   '#header' => $header,
 *   '#rows' => $rows,
 *   '#attributes' => [
 *     'id' => 'my-module-table',
 *   ],
 * ];
 * return \Drupal::service('renderer')->render($table);
 * @endcode
 *
 * In the theme function for the form, a special class must be added to each
 * form element within the same column, "grouping" them together.
 *
 * In a situation where a single weight column is being sorted in the table, the
 * classes could be added like this (in the theme function):
 * @code
 * $form['my_elements'][$delta]['weight']['#attributes']['class'] = ['my-elements-weight'];
 * @endcode
 *
 * Each row of the table must also have a class of "draggable" in order to
 * enable the drag handles:
 * @code
 * $row = [...];
 * $rows[] = [
 *   'data' => $row,
 *   'class' => ['draggable'],
 * ];
 * @endcode
 *
 * When tree relationships are present, the two additional classes
 * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
 * - Rows with the 'tabledrag-leaf' class cannot have child rows.
 * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
 *
 * Calling drupal_attach_tabledrag() would then be written as such:
 * @code
 * drupal_attach_tabledrag('my-module-table', [
 *   'action' => 'order',
 *   'relationship' => 'sibling',
 *   'group' => 'my-elements-weight',
 * ];
 * @endcode
 *
 * In a more complex case where there are several groups in one column (such as
 * the block regions on the admin/structure/block page), a separate subgroup
 * class must also be added to differentiate the groups.
 * @code
 * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = ['my-elements-weight', 'my-elements-weight-' . $region];
 * @endcode
 *
 * The 'group' option is still 'my-element-weight', and the additional
 * 'subgroup' option will be passed in as 'my-elements-weight-' . $region. This
 * also means that you'll need to call drupal_attach_tabledrag() once for every
 * region added.
 *
 * @code
 * foreach ($regions as $region) {
 *   drupal_attach_tabledrag('my-module-table', [
 *     'action' => 'order',
 *     'relationship' => 'sibling',
 *     'group' => 'my-elements-weight',
 *     'subgroup' => 'my-elements-weight-' . $region,
 *   ]);
 * }
 * @endcode
 *
 * In a situation where tree relationships are present, adding multiple
 * subgroups is not necessary, because the table will contain indentations that
 * provide enough information about the sibling and parent relationships. See
 * MenuForm::BuildOverviewForm for an example creating a table
 * containing parent relationships.
 *
 * @param array $element
 *   A form element to attach the tableDrag behavior to.
 * @param array $options
 *   These options are used to generate JavaScript settings necessary to
 *   configure the tableDrag behavior appropriately for this particular table.
 *   An associative array containing the following keys:
 *   - 'table_id': String containing the target table's id attribute.
 *     If the table does not have an id, one will need to be set,
 *     such as <table id="my-module-table">.
 *   - 'action': String describing the action to be done on the form item.
 *      Either 'match' 'depth', or 'order':
 *     - 'match' is typically used for parent relationships.
 *     - 'order' is typically used to set weights on other form elements with
 *       the same group.
 *     - 'depth' updates the target element with the current indentation.
 *   - 'relationship': String describing where the "action" option
 *     should be performed. Either 'parent', 'sibling', 'group', or 'self':
 *     - 'parent' will only look for fields up the tree.
 *     - 'sibling' will look for fields in the same group in rows above and
 *       below it.
 *     - 'self' affects the dragged row itself.
 *     - 'group' affects the dragged row, plus any children below it (the entire
 *       dragged group).
 *   - 'group': A class name applied on all related form elements for this
 *      action.
 *   - 'subgroup': (optional) If the group has several subgroups within it, this
 *     string should contain the class name identifying fields in the same
 *     subgroup.
 *   - 'source': (optional) If the $action is 'match', this string should
 *     contain the classname identifying what field will be used as the source
 *     value when matching the value in $subgroup.
 *   - 'hidden': (optional) The column containing the field elements may be
 *     entirely hidden from view dynamically when the JavaScript is loaded. Set
 *     to FALSE if the column should not be hidden.
 *   - 'limit': (optional) Limit the maximum amount of parenting in this table.
 * @deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. Use
 *   \Drupal\Core\Render\Element\Table::attachTabledrag() instead.
 *
 * @see https://www.drupal.org/node/3035565
 * @see MenuForm::BuildOverviewForm()
 * @see \Drupal\Core\Render\Element\Table::attachTabledrag()
 */
function drupal_attach_tabledrag(&$element, array $options): void {
  // Add default values to elements.
  $options = $options + [
    'subgroup' => NULL,
    'source' => NULL,
    'hidden' => TRUE,
    'limit' => 0,
  ];

  $group = $options['group'];

  $tabledrag_id = &drupal_static(__FUNCTION__);
  $tabledrag_id = (!isset($tabledrag_id)) ? 0 : $tabledrag_id + 1;

  // If a subgroup or source isn't set, assume it is the same as the group.
  $target = $options['subgroup'] ?? $group;
  $source = $options['source'] ?? $target;
  $element['#attached']['drupalSettings']['tableDrag'][$options['table_id']][$group][$tabledrag_id] = [
    'target' => $target,
    'source' => $source,
    'relationship' => $options['relationship'],
    'action' => $options['action'],
    'hidden' => $options['hidden'],
    'limit' => $options['limit'],
  ];

  $element['#attached']['library'][] = 'core/drupal.tabledrag';
  @trigger_error('drupal_attach_tabledrag() is deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. Use \Drupal\Core\Render\Element\Table::attachTabledrag() instead. See https://www.drupal.org/node/3035565', E_USER_DEPRECATED);
  Table::attachTabledrag($element, $options);
}

/**
+172 −5
Changes for core/lib/Drupal/Core/Render/Element/Table.php: 172 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -111,6 +111,11 @@
#[FormElement('table')]
class Table extends FormElementBase {

  /**
   * The cache ID used for the tableDrag instance counter.
   */
  protected const string TABLE_DRAG_CACHE_ID = 'tabledrag:instance_counter';

  /**
   * {@inheritdoc}
   */
@@ -135,9 +140,9 @@ public function getInfo() {
      ],
      // Properties for tabledrag support.
      // The value is a list of arrays that are passed to
      // drupal_attach_tabledrag(). Table::preRenderTable() prepends the HTML ID
      // Table::attachTabledrag(). Table::preRenderTable() prepends the HTML ID
      // of the table to each set of options.
      // @see drupal_attach_tabledrag()
      // @see Table::attachTabledrag()
      '#tabledrag' => [],
      // Render properties.
      '#pre_render' => [
@@ -388,7 +393,7 @@ public static function validateTable(&$element, FormStateInterface $form_state,
   *   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
   *     Table::attachTabledrag(). The HTML ID of the table is added to each
   *     $options array.
   *
   * @return array
@@ -396,7 +401,7 @@ public static function validateTable(&$element, FormStateInterface $form_state,
   *
   * @see \Drupal\Core\Theme\ThemePreprocess::preprocessTable()
   * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
   * @see drupal_attach_tabledrag()
   * @see \Drupal\Core\Render\Element\Table::attachTabledrag()
   */
  public static function preRenderTable($element) {
    foreach (Element::children($element) as $first) {
@@ -446,13 +451,175 @@ public static function preRenderTable($element) {
    // 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'])) {
      $element['#attributes']['class'][] = 'tabledrag-enabled';
      $element['#attributes']['data-drupal-tabledrag'] = $element['#attributes']['id'];
      foreach ($element['#tabledrag'] as $options) {
        $options['table_id'] = $element['#attributes']['id'];
        drupal_attach_tabledrag($element, $options);
        static::attachTabledrag($element, $options);
      }
    }

    return $element;
  }

  /**
   * Assists in attaching the tableDrag JavaScript behavior to a themed table.
   *
   * Draggable tables should be used wherever an outline or list of sortable
   * items needs to be arranged by an end-user. Draggable tables are very
   * flexible and can manipulate the value of form elements placed within
   * individual columns.
   *
   * To set up a table to use drag and drop in place of weight select-lists or
   * in place of a form that contains parent relationships, the form must be
   * themed into a table. The table must have an ID attribute set and it
   * maybe set as follows:
   * @code
   * $table = [
   *   '#type' => 'table',
   *   '#header' => $header,
   *   '#rows' => $rows,
   *   '#attributes' => [
   *     'id' => 'my-module-table',
   *   ],
   * ];
   * return \Drupal::service('renderer')->render($table);
   * @endcode
   *
   * In the theme function for the form, a special class must be added to each
   * form element within the same column, "grouping" them together.
   *
   * In a situation where a single weight column is being sorted in the table,
   * the classes could be added like this (in the theme function):
   * @code
   * $form['my_elements'][$delta]['weight']['#attributes']['class'] = ['my-elements-weight'];
   * @endcode
   *
   * Each row of the table must also have a class of "draggable" in order to
   * enable the drag handles:
   * @code
   * $row = [...];
   * $rows[] = [
   *   'data' => $row,
   *   'class' => ['draggable'],
   * ];
   * @endcode
   *
   * When tree relationships are present, the two additional classes
   * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
   * - Rows with the 'tabledrag-leaf' class cannot have child rows.
   * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
   *
   * Calling Table::attachTabledrag() would then be written as such:
   * @code
   * Table::attachTabledrag('my-module-table', [
   *   'action' => 'order',
   *   'relationship' => 'sibling',
   *   'group' => 'my-elements-weight',
   * ];
   * @endcode
   *
   * In a more complex case where there are several groups in one column (such
   * as the block regions on the admin/structure/block page), a separate
   * subgroup class must also be added to differentiate the groups.
   * @code
   * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = ['my-elements-weight', 'my-elements-weight-' . $region];
   * @endcode
   *
   * The 'group' option is still 'my-element-weight', and the additional
   * 'subgroup' option will be passed in as 'my-elements-weight-' . $region.
   * This also means that you'll need to call Table::attachTabledrag() once for
   * every region added.
   *
   * @code
   * foreach ($regions as $region) {
   *   Table::attachTabledrag('my-module-table', [
   *     'action' => 'order',
   *     'relationship' => 'sibling',
   *     'group' => 'my-elements-weight',
   *     'subgroup' => 'my-elements-weight-' . $region,
   *   ]);
   * }
   * @endcode
   *
   * In a situation where tree relationships are present, adding multiple
   * subgroups is not necessary, because the table will contain indentations
   * that provide enough information about the sibling and parent relationships.
   * See MenuForm::BuildOverviewForm for an example creating a table containing
   * parent relationships.
   *
   * @param array $element
   *   A form element to attach the tableDrag behavior to.
   * @param array $options
   *   These options are used to generate JavaScript settings necessary to
   *   configure the tableDrag behavior appropriately for this particular table.
   *   An associative array containing the following keys:
   *   - 'table_id': String containing the target table's id attribute. If the
   *     table does not have an id, one will need to be set, such as
   *     <table id="my-module-table">.
   *   - 'action': String describing the action to be done on the form item.
   *      Either 'match' 'depth', or 'order':
   *     - 'match' is typically used for parent relationships.
   *     - 'order' is typically used to set weights on other form elements with
   *       the same group.
   *     - 'depth' updates the target element with the current indentation.
   *   - 'relationship': String describing where the "action" option should be
   *     performed. Either 'parent', 'sibling', 'group', or 'self':
   *     - 'parent' will only look for fields up the tree.
   *     - 'sibling' will look for fields in the same group in rows above and
   *       below it.
   *     - 'self' affects the dragged row itself.
   *     - 'group' affects the dragged row, plus any children below it (the
   *       entire dragged group).
   *   - 'group': A class name applied on all related form elements for this
   *     action.
   *   - 'subgroup': (optional) If the group has several subgroups within it,
   *     this string should contain the class name identifying fields in the
   *     same subgroup.
   *   - 'source': (optional) If the $action is 'match', this string should
   *     contain the classname identifying what field will be used as the source
   *     value when matching the value in $subgroup.
   *   - 'hidden': (optional) The column containing the field elements may be
   *     entirely hidden from view dynamically when the JavaScript is loaded.
   *     Set to FALSE if the column should not be hidden.
   *   - 'limit': (optional) Limit the maximum amount of parenting in this
   *     table.
   *
   * @see \Drupal\menu_ui\MenuForm::buildOverviewForm()
   */
  public static function attachTabledrag(array &$element, array $options): void {
    // Add default values to elements.
    $options += [
      'subgroup' => NULL,
      'source' => NULL,
      'hidden' => TRUE,
      'limit' => 0,
    ];

    $group = $options['group'];

    // The counter for tableDrag instances is stored in the memory cache
    // instead of a static property, so it does not bleed across requests in
    // long-running PHP processes (e.g., FrankenPHP, RoadRunner), where memory
    // cache bins are reset between requests.
    $cache = \Drupal::cache('memory');
    $cache_item = $cache->get(static::TABLE_DRAG_CACHE_ID);
    $tabledrag_id = $cache_item ? $cache_item->data + 1 : 0;
    $cache->set(static::TABLE_DRAG_CACHE_ID, $tabledrag_id);

    // If a subgroup or source isn't set, assume it is the same as the group.
    $target = $options['subgroup'] ?? $group;
    $source = $options['source'] ?? $target;
    $element['#attached']['drupalSettings']['tableDrag'][$options['table_id']][$group][$tabledrag_id] = [
      'target' => $target,
      'source' => $source,
      'relationship' => $options['relationship'],
      'action' => $options['action'],
      'hidden' => $options['hidden'],
      'limit' => $options['limit'],
    ];

    $element['#attached']['library'][] = 'core/drupal.tabledrag';
  }

}
+1 −0
Changes for core/misc/cspell/dictionary.txt: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -197,6 +197,7 @@ fodt
fouc
fourcol
francais
franken
français
fraîche
frontpage
+4 −1
Changes for core/misc/tabledrag.js: 4 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -47,7 +47,10 @@
      }

      Object.keys(settings.tableDrag || {}).forEach((base) => {
        initTableDrag($(once('tabledrag', `#${base}`, context)), base);
        initTableDrag(
          $(once('tabledrag', `[data-drupal-tabledrag="${base}"]`, context)),
          base,
        );
      });
    },
  };
+4 −3
Changes for core/modules/field_ui/src/Element/FieldUiTable.php: 4 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ public function getInfo() {
   *   - #region_callback: A callback that provides the region of the table to
   *     place the row in.
   *   - #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
   *     Table::attachTabledrag(). The HTML ID of the table is added to each
   *     $options array.
   *
   * @return array
@@ -42,6 +42,7 @@ public function getInfo() {
   *
   * @see \Drupal\Core\Render\RendererInterface::render()
   * @see \Drupal\Core\Render\Element\Table::preRenderTable()
   * @see \Drupal\Core\Render\Element\Table::attachTabledrag()
   */
  public static function tablePreRender($elements) {
    $js_settings = [];
@@ -122,7 +123,7 @@ public static function tablePreRender($elements) {
    if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
      foreach ($elements['#tabledrag'] as $options) {
        $options['table_id'] = $elements['#attributes']['id'];
        drupal_attach_tabledrag($elements, $options);
        static::attachTabledrag($elements, $options);
      }
    }

@@ -136,7 +137,7 @@ public static function tablePreRender($elements) {
   *   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
   *     Table::attachTabledrag(). The HTML ID of the table is added to each
   *     $options array.
   *
   * @return array
Loading