84cafb08936857d472125358391cac123352bc88
[yaffs-website] / web / core / modules / field_ui / src / Element / FieldUiTable.php
1 <?php
2
3 namespace Drupal\field_ui\Element;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Render\Element;
7 use Drupal\Core\Render\Element\Table;
8
9 /**
10  * Provides a field_ui table element.
11  *
12  * @RenderElement("field_ui_table")
13  */
14 class FieldUiTable extends Table {
15
16   /**
17    * {@inheritdoc}
18    */
19   public function getInfo() {
20     $info = parent::getInfo();
21     $info['#regions'] = ['' => []];
22     $info['#theme'] = 'field_ui_table';
23     // Prepend FieldUiTable's prerender callbacks.
24     array_unshift($info['#pre_render'], [$this, 'tablePreRender'], [$this, 'preRenderRegionRows']);
25     return $info;
26   }
27
28   /**
29    * Performs pre-render tasks on field_ui_table elements.
30    *
31    * @param array $elements
32    *   A structured array containing two sub-levels of elements. Properties
33    *   used:
34    *   - #tabledrag: The value is a list of $options arrays that are passed to
35    *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
36    *     $options array.
37    *
38    * @return array
39    *   The $element with prepared variables ready for field-ui-table.html.twig.
40    *
41    * @see drupal_render()
42    * @see \Drupal\Core\Render\Element\Table::preRenderTable()
43    */
44   public static function tablePreRender($elements) {
45     $js_settings = [];
46
47     // For each region, build the tree structure from the weight and parenting
48     // data contained in the flat form structure, to determine row order and
49     // indentation.
50     $regions = $elements['#regions'];
51     $tree = ['' => ['name' => '', 'children' => []]];
52     $trees = array_fill_keys(array_keys($regions), $tree);
53
54     $parents = [];
55     $children = Element::children($elements);
56     $list = array_combine($children, $children);
57
58     // Iterate on rows until we can build a known tree path for all of them.
59     while ($list) {
60       foreach ($list as $name) {
61         $row = &$elements[$name];
62         $parent = $row['parent_wrapper']['parent']['#value'];
63         // Proceed if parent is known.
64         if (empty($parent) || isset($parents[$parent])) {
65           // Grab parent, and remove the row from the next iteration.
66           $parents[$name] = $parent ? array_merge($parents[$parent], [$parent]) : [];
67           unset($list[$name]);
68
69           // Determine the region for the row.
70           $region_name = call_user_func_array($row['#region_callback'], [&$row]);
71
72           // Add the element in the tree.
73           $target = &$trees[$region_name][''];
74           foreach ($parents[$name] as $key) {
75             $target = &$target['children'][$key];
76           }
77           $target['children'][$name] = ['name' => $name, 'weight' => $row['weight']['#value']];
78
79           // Add tabledrag indentation to the first row cell.
80           if ($depth = count($parents[$name])) {
81             $children = Element::children($row);
82             $cell = current($children);
83             $indentation = [
84               '#theme' => 'indentation',
85               '#size' => $depth,
86               '#suffix' => isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '',
87             ];
88             $row[$cell]['#prefix'] = \Drupal::service('renderer')->render($indentation);
89           }
90
91           // Add row id and associate JS settings.
92           $id = Html::getClass($name);
93           $row['#attributes']['id'] = $id;
94           if (isset($row['#js_settings'])) {
95             $row['#js_settings'] += [
96               'rowHandler' => $row['#row_type'],
97               'name' => $name,
98               'region' => $region_name,
99             ];
100             $js_settings[$id] = $row['#js_settings'];
101           }
102         }
103       }
104     }
105
106     // Determine rendering order from the tree structure.
107     foreach ($regions as $region_name => $region) {
108       $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], [static::class, 'reduceOrder']);
109     }
110
111     $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;
112
113     // If the custom #tabledrag is set and there is a HTML ID, add the table's
114     // HTML ID to the options and attach the behavior.
115     // @see \Drupal\Core\Render\Element\Table::preRenderTable()
116     if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
117       foreach ($elements['#tabledrag'] as $options) {
118         $options['table_id'] = $elements['#attributes']['id'];
119         drupal_attach_tabledrag($elements, $options);
120       }
121     }
122
123     return $elements;
124   }
125
126   /**
127    * Performs pre-render to move #regions to rows.
128    *
129    * @param array $elements
130    *   A structured array containing two sub-levels of elements. Properties
131    *   used:
132    *   - #tabledrag: The value is a list of $options arrays that are passed to
133    *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
134    *     $options array.
135    *
136    * @return array
137    *   The $element with prepared variables ready for field-ui-table.html.twig.
138    */
139   public static function preRenderRegionRows($elements) {
140     // Determine the colspan to use for region rows, by checking the number of
141     // columns in the headers.
142     $columns_count = 0;
143     foreach ($elements['#header'] as $header) {
144       $columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1);
145     }
146
147     $rows = [];
148     foreach (Element::children($elements) as $key) {
149       $rows[$key] = $elements[$key];
150       unset($elements[$key]);
151     }
152
153     // Render rows, region by region.
154     foreach ($elements['#regions'] as $region_name => $region) {
155       $region_name_class = Html::getClass($region_name);
156
157       // Add region rows.
158       if (isset($region['title']) && empty($region['invisible'])) {
159         $elements['#rows'][] = [
160           'class' => [
161             'region-title',
162             'region-' . $region_name_class . '-title'
163           ],
164           'no_striping' => TRUE,
165           'data' => [
166             ['data' => $region['title'], 'colspan' => $columns_count],
167           ],
168         ];
169       }
170       if (isset($region['message'])) {
171         $class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated');
172         $elements['#rows'][] = [
173           'class' => [
174             'region-message',
175             'region-' . $region_name_class . '-message', $class,
176           ],
177           'no_striping' => TRUE,
178           'data' => [
179             ['data' => $region['message'], 'colspan' => $columns_count],
180           ],
181         ];
182       }
183
184       // Add form rows, in the order determined at pre-render time.
185       foreach ($region['rows_order'] as $name) {
186         $element = $rows[$name];
187
188         $row = ['data' => []];
189         if (isset($element['#attributes'])) {
190           $row += $element['#attributes'];
191         }
192
193         // Render children as table cells.
194         foreach (Element::children($element) as $cell_key) {
195           $child = $element[$cell_key];
196           // Do not render a cell for children of #type 'value'.
197           if (!(isset($child['#type']) && $child['#type'] == 'value')) {
198             $cell = ['data' => $child];
199             if (isset($child['#cell_attributes'])) {
200               $cell += $child['#cell_attributes'];
201             }
202             $row['data'][] = $cell;
203           }
204         }
205         $elements['#rows'][] = $row;
206       }
207     }
208
209     return $elements;
210   }
211
212   /**
213    * Determines the rendering order of an array representing a tree.
214    *
215    * Callback for array_reduce() within ::tablePreRender().
216    *
217    * @param mixed $array
218    *   Holds the return value of the previous iteration; in the case of the
219    *   first iteration it instead holds the value of the initial array.
220    * @param mixed $a
221    *   Holds the value of the current iteration.
222    *
223    * @return array
224    *   Array where rendering order has been determined.
225    */
226   public static function reduceOrder($array, $a) {
227     $array = !$array ? [] : $array;
228     if ($a['name']) {
229       $array[] = $a['name'];
230     }
231     if (!empty($a['children'])) {
232       uasort($a['children'], ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
233       $array = array_merge($array, array_reduce($a['children'], [static::class, 'reduceOrder']));
234     }
235
236     return $array;
237   }
238
239 }