diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 269aa67..9ac9d90 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -53,8 +53,17 @@ function field_ui_help($route_name, RouteMatchInterface $route_match) { function field_ui_theme() { return array( 'field_ui_table' => array( - 'render element' => 'elements', - 'function' => 'theme_field_ui_table', + 'variables' => array( + 'header' => NULL, + 'rows' => NULL, + 'footer' => NULL, + 'attributes' => array(), + 'caption' => NULL, + 'colgroups' => array(), + 'sticky' => FALSE, + 'responsive' => TRUE, + 'empty' => '', + ), ), ); } @@ -218,75 +227,8 @@ function field_ui_entity_form_mode_delete(EntityFormModeInterface $form_mode) { * * @ingroup themeable */ -function theme_field_ui_table($variables) { - $elements = $variables['elements']; - $table = array('#type' => 'table'); - - // Add table headers and attributes. - foreach (array('#header', '#attributes') as $key) { - if (isset($elements[$key])) { - $table[$key] = $elements[$key]; - } - } - - // Determine the colspan to use for region rows, by checking the number of - // columns in the headers. - $columns_count = 0; - foreach ($table['#header'] as $header) { - $columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1); - } - - // Render rows, region by region. - foreach ($elements['#regions'] as $region_name => $region) { - $region_name_class = Html::getClass($region_name); - - // Add region rows. - if (isset($region['title']) && empty($region['invisible'])) { - $table['#rows'][] = array( - 'class' => array('region-title', 'region-' . $region_name_class . '-title'), - 'no_striping' => TRUE, - 'data' => array( - array('data' => $region['title'], 'colspan' => $columns_count), - ), - ); - } - if (isset($region['message'])) { - $class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated'); - $table['#rows'][] = array( - 'class' => array('region-message', 'region-' . $region_name_class . '-message', $class), - 'no_striping' => TRUE, - 'data' => array( - array('data' => $region['message'], 'colspan' => $columns_count), - ), - ); - } - - // Add form rows, in the order determined at pre-render time. - foreach ($region['rows_order'] as $name) { - $element = $elements[$name]; - - $row = array('data' => array()); - if (isset($element['#attributes'])) { - $row += $element['#attributes']; - } - - // Render children as table cells. - foreach (Element::children($element) as $cell_key) { - $child = &$element[$cell_key]; - // Do not render a cell for children of #type 'value'. - if (!(isset($child['#type']) && $child['#type'] == 'value')) { - $cell = array('data' => drupal_render($child)); - if (isset($child['#cell_attributes'])) { - $cell += $child['#cell_attributes']; - } - $row['data'][] = $cell; - } - } - $table['#rows'][] = $row; - } - } - - return drupal_render($table); +function template_preprocess_field_ui_table(&$variables) { + template_preprocess_table($variables); } /** diff --git a/core/modules/field_ui/src/Element/FieldUiTable.php b/core/modules/field_ui/src/Element/FieldUiTable.php index 361a17e..f12f23c 100644 --- a/core/modules/field_ui/src/Element/FieldUiTable.php +++ b/core/modules/field_ui/src/Element/FieldUiTable.php @@ -7,23 +7,207 @@ namespace Drupal\field_ui\Element; -use Drupal\Core\Render\Element\RenderElement; +use Drupal\Component\Utility\Html; +use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\Table; /** * Provides a field_ui table element. * * @RenderElement("field_ui_table") */ -class FieldUiTable extends RenderElement { +class FieldUiTable extends Table { /** * {@inheritdoc} */ public function getInfo() { - return array( - '#theme' => 'field_ui_table', - '#regions' => array('' => array()), - ); + $class = get_class($this); + $info = parent::getInfo(); + $info['#regions'] = ['' => []]; + $info['#theme'] = 'field_ui_table'; + // Prepend FieldUiTable's prerender. + // $info['#pre_render'][] = [$class, 'tablePreRender']; + array_unshift($info['#pre_render'], [$class, 'tablePreRender']); + return $info; + } + + /** + * Performs pre-render tasks on field_ui_table elements. + * + * @param array $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 + * $options array. + * + * @return array + * + * @see drupal_render() + * @see \Drupal\Core\Render\Element\Table::preRenderTable() + */ + public static function tablePreRender($elements) { + $js_settings = array(); + + // For each region, build the tree structure from the weight and parenting + // data contained in the flat form structure, to determine row order and + // indentation. + $regions = $elements['#regions']; + $tree = array('' => array('name' => '', 'children' => array())); + $trees = array_fill_keys(array_keys($regions), $tree); + + $parents = array(); + $children = Element::children($elements); + $list = array_combine($children, $children); + + // Iterate on rows until we can build a known tree path for all of them. + while ($list) { + foreach ($list as $name) { + $row = &$elements[$name]; + $parent = $row['parent_wrapper']['parent']['#value']; + // Proceed if parent is known. + if (empty($parent) || isset($parents[$parent])) { + // Grab parent, and remove the row from the next iteration. + $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array(); + unset($list[$name]); + + // Determine the region for the row. + $region_name = call_user_func($row['#region_callback'], $row); + + // Add the element in the tree. + $target = &$trees[$region_name]['']; + foreach ($parents[$name] as $key) { + $target = &$target['children'][$key]; + } + $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']); + + // Add tabledrag indentation to the first row cell. + if ($depth = count($parents[$name])) { + $children = Element::children($row); + $cell = current($children); + $indentation = array( + '#theme' => 'indentation', + '#size' => $depth, + ); + $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : ''); + } + + // Add row id and associate JS settings. + $id = Html::getClass($name); + $row['#attributes']['id'] = $id; + if (isset($row['#js_settings'])) { + $row['#js_settings'] += array( + 'rowHandler' => $row['#row_type'], + 'name' => $name, + 'region' => $region_name, + ); + $js_settings[$id] = $row['#js_settings']; + } + } + } + } + + // Determine rendering order from the tree structure. + foreach ($regions as $region_name => $region) { + $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array('Drupal\field_ui\Element\FieldUiTable', 'reduceOrder')); + } + + $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings; + + // If the custom #tabledrag is set and there is a HTML ID, add the table's + // HTML ID to the options and attach the behavior. + // @see \Drupal\Core\Render\Element\Table::preRenderTable() + if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) { + foreach ($elements['#tabledrag'] as $options) { + $options['table_id'] = $elements['#attributes']['id']; + drupal_attach_tabledrag($elements, $options); + } + } + + // Determine the colspan to use for region rows, by checking the number of + // columns in the headers. + $columns_count = 0; + foreach ($elements['#header'] as $header) { + $columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1); + } + + $rows = []; + foreach (Element::children($elements) as $key) { + $rows[$key] = $elements[$key]; + unset($elements[$key]); + } + + // Render rows, region by region. + foreach ($elements['#regions'] as $region_name => $region) { + $region_name_class = Html::getClass($region_name); + + // Add region rows. + if (isset($region['title']) && empty($region['invisible'])) { + $elements['#rows'][] = array( + 'class' => array('region-title', 'region-' . $region_name_class . '-title'), + 'no_striping' => TRUE, + 'data' => array( + array('data' => $region['title'], 'colspan' => $columns_count), + ), + ); + } + if (isset($region['message'])) { + $class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated'); + $elements['#rows'][] = array( + 'class' => array('region-message', 'region-' . $region_name_class . '-message', $class), + 'no_striping' => TRUE, + 'data' => array( + array('data' => $region['message'], 'colspan' => $columns_count), + ), + ); + } + + // Add form rows, in the order determined at pre-render time. + foreach ($region['rows_order'] as $name) { + $element = $rows[$name]; + + $row = array('data' => array()); + if (isset($element['#attributes'])) { + $row += $element['#attributes']; + } + + // Render children as table cells. + foreach (Element::children($element) as $cell_key) { + $child = $element[$cell_key]; + // Do not render a cell for children of #type 'value'. + if (!(isset($child['#type']) && $child['#type'] == 'value')) { + $cell = array('data' => $child); + if (isset($child['#cell_attributes'])) { + $cell += $child['#cell_attributes']; + } + $row['data'][] = $cell; + } + } + $elements['#rows'][] = $row; + } + } + + return $elements; + } + + /** + * Determines the rendering order of an array representing a tree. + * + * Callback for array_reduce() within + * \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender(). + */ + public static function reduceOrder($array, $a) { + $array = !isset($array) ? array() : $array; + if ($a['name']) { + $array[] = $a['name']; + } + if (!empty($a['children'])) { + uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement')); + $array = array_merge($array, array_reduce($a['children'], array('Drupal\field_ui\Element\FieldUiTable', 'reduceOrder'))); + } + + return $array; } } diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php index e32d73b..43e2219 100644 --- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php +++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php @@ -159,17 +159,12 @@ public function form(array $form, FormStateInterface $form_state) { $table = array( '#type' => 'field_ui_table', - '#pre_render' => array(array($this, 'tablePreRender')), - '#tree' => TRUE, '#header' => $this->getTableHeader(), '#regions' => $this->getRegions(), '#attributes' => array( 'class' => array('field-ui-overview'), 'id' => 'field-display-overview', ), - // Add Ajax wrapper. - '#prefix' => '
', - '#suffix' => '
', '#tabledrag' => array( array( 'action' => 'order', @@ -678,119 +673,6 @@ public function multistepAjax($form, FormStateInterface $form_state) { } /** - * Performs pre-render tasks on field_ui_table elements. - * - * @param array $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 - * $options array. - * - * @return array - * - * @see drupal_render() - * @see \Drupal\Core\Render\Element\Table::preRenderTable() - */ - public function tablePreRender($elements) { - $js_settings = array(); - - // For each region, build the tree structure from the weight and parenting - // data contained in the flat form structure, to determine row order and - // indentation. - $regions = $elements['#regions']; - $tree = array('' => array('name' => '', 'children' => array())); - $trees = array_fill_keys(array_keys($regions), $tree); - - $parents = array(); - $children = Element::children($elements); - $list = array_combine($children, $children); - - // Iterate on rows until we can build a known tree path for all of them. - while ($list) { - foreach ($list as $name) { - $row = &$elements[$name]; - $parent = $row['parent_wrapper']['parent']['#value']; - // Proceed if parent is known. - if (empty($parent) || isset($parents[$parent])) { - // Grab parent, and remove the row from the next iteration. - $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array(); - unset($list[$name]); - - // Determine the region for the row. - $region_name = call_user_func($row['#region_callback'], $row); - - // Add the element in the tree. - $target = &$trees[$region_name]['']; - foreach ($parents[$name] as $key) { - $target = &$target['children'][$key]; - } - $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']); - - // Add tabledrag indentation to the first row cell. - if ($depth = count($parents[$name])) { - $children = Element::children($row); - $cell = current($children); - $indentation = array( - '#theme' => 'indentation', - '#size' => $depth, - ); - $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : ''); - } - - // Add row id and associate JS settings. - $id = Html::getClass($name); - $row['#attributes']['id'] = $id; - if (isset($row['#js_settings'])) { - $row['#js_settings'] += array( - 'rowHandler' => $row['#row_type'], - 'name' => $name, - 'region' => $region_name, - ); - $js_settings[$id] = $row['#js_settings']; - } - } - } - } - // Determine rendering order from the tree structure. - foreach ($regions as $region_name => $region) { - $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder')); - } - - $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings; - - // If the custom #tabledrag is set and there is a HTML ID, add the table's - // HTML ID to the options and attach the behavior. - // @see \Drupal\Core\Render\Element\Table::preRenderTable() - if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) { - foreach ($elements['#tabledrag'] as $options) { - $options['table_id'] = $elements['#attributes']['id']; - drupal_attach_tabledrag($elements, $options); - } - } - - return $elements; - } - - /** - * Determines the rendering order of an array representing a tree. - * - * Callback for array_reduce() within - * \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender(). - */ - public function reduceOrder($array, $a) { - $array = !isset($array) ? array() : $array; - if ($a['name']) { - $array[] = $a['name']; - } - if (!empty($a['children'])) { - uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement')); - $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder'))); - } - return $array; - } - - /** * Returns the extra fields of the entity type and bundle used by this form. * * @return array diff --git a/core/modules/field_ui/templates/field-ui-table.html.twig b/core/modules/field_ui/templates/field-ui-table.html.twig new file mode 100644 index 0000000..30c3e83 --- /dev/null +++ b/core/modules/field_ui/templates/field-ui-table.html.twig @@ -0,0 +1,47 @@ +{# +/** + * @file + * Default theme implementation to display a field UI table. + * + * Available variables: + * - attributes: HTML attributes to apply to the tag. + * - caption: A localized string for the tag. + * Note: Drupal currently supports only one table header row, see + * https://www.drupal.org/node/893530 and + * http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109. + * - header: Table header cells. Each cell contains the following properties: + * - tag: The HTML tag name to use; either TH or TD. + * - attributes: HTML attributes to apply to the tag. + * - content: A localized string for the title of the column. + * - field: Field name (required for column sorting). + * - sort: Default sort order for this column ("asc" or "desc"). + * - sticky: A flag indicating whether to use a "sticky" table header. + * - rows: Table rows. Each row contains the following properties: + * - attributes: HTML attributes to apply to the tag. + * - data: Table cells. + * - no_striping: A flag indicating that the row should receive no + * 'even / odd' styling. Defaults to FALSE. + * - cells: Table cells of the row. Each cell contains the following keys: + * - tag: The HTML tag name to use; either TH or TD. + * - attributes: Any HTML attributes, such as "colspan", to apply to the + * table cell. + * - content: The string to display in the table cell. + * - active_table_sort: A boolean indicating whether the cell is the active + table sort. + * - footer: Table footer rows, in the same format as the rows variable. + * - empty: The message to display in an extra row if table does not have + * any rows. + * - no_striping: A boolean indicating that the row should receive no striping. + * - header_columns: The number of columns in the header. + * + * @see template_preprocess_table() + * + * @ingroup themeable + */ +#} +{# Add Ajax wrapper. #} +
+ {% include 'table.html.twig' %} +
tag. + * - colgroups: Column groups. Each group contains the following properties: + * - attributes: HTML attributes to apply to the