Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Render / Element / Tableselect.php
1 <?php
2
3 namespace Drupal\Core\Render\Element;
4
5 use Drupal\Core\Form\FormStateInterface;
6 use Drupal\Core\Render\Element;
7 use Drupal\Component\Utility\Html as HtmlUtility;
8 use Drupal\Core\StringTranslation\TranslatableMarkup;
9
10 /**
11  * Provides a form element for a table with radios or checkboxes in left column.
12  *
13  * Properties:
14  * - #header: An array of table header labels.
15  * - #options: An associative array where each key is the value returned when
16  *   a user selects the radio button or checkbox, and each value is the row of
17  *   table data.
18  * - #empty: The message to display if table does not have any options.
19  * - #multiple: Set to FALSE to render the table with radios instead checkboxes.
20  * - #js_select: Set to FALSE if you don't want the select all checkbox added to
21  *   the header.
22  *
23  * Other properties of the \Drupal\Core\Render\Element\Table element are also
24  * available.
25  *
26  * Usage example:
27  * @code
28  * $header = [
29  *   'first_name' => $this->t('First Name'),
30  *   'last_name' => $this->t('Last Name'),
31  * ];
32  *
33  * $options = [
34  *   1 => ['first_name' => 'Indy', 'last_name' => 'Jones'],
35  *   2 => ['first_name' => 'Darth', 'last_name' => 'Vader'],
36  *   3 => ['first_name' => 'Super', 'last_name' => 'Man'],
37  * ];
38  *
39  * $form['table'] = array(
40  *   '#type' => 'tableselect',
41  *   '#header' => $header,
42  *   '#options' => $options,
43  *   '#empty' => $this->t('No users found'),
44  * );
45  * @endcode
46  *
47  * See https://www.drupal.org/node/945102 for a full explanation.
48  *
49  * @see \Drupal\Core\Render\Element\Table
50  *
51  * @FormElement("tableselect")
52  */
53 class Tableselect extends Table {
54
55   /**
56    * {@inheritdoc}
57    */
58   public function getInfo() {
59     $class = get_class($this);
60     return [
61       '#input' => TRUE,
62       '#js_select' => TRUE,
63       '#multiple' => TRUE,
64       '#responsive' => TRUE,
65       '#sticky' => FALSE,
66       '#pre_render' => [
67         [$class, 'preRenderTable'],
68         [$class, 'preRenderTableselect'],
69       ],
70       '#process' => [
71         [$class, 'processTableselect'],
72       ],
73       '#options' => [],
74       '#empty' => '',
75       '#theme' => 'table__tableselect',
76     ];
77   }
78
79   /**
80    * {@inheritdoc}
81    */
82   public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
83     // If $element['#multiple'] == FALSE, then radio buttons are displayed and
84     // the default value handling is used.
85     if (isset($element['#multiple']) && $element['#multiple']) {
86       // Checkboxes are being displayed with the default value coming from the
87       // keys of the #default_value property. This differs from the checkboxes
88       // element which uses the array values.
89       if ($input === FALSE) {
90         $value = [];
91         $element += ['#default_value' => []];
92         foreach ($element['#default_value'] as $key => $flag) {
93           if ($flag) {
94             $value[$key] = $key;
95           }
96         }
97         return $value;
98       }
99       else {
100         return is_array($input) ? array_combine($input, $input) : [];
101       }
102     }
103   }
104
105   /**
106    * Prepares a 'tableselect' #type element for rendering.
107    *
108    * Adds a column of radio buttons or checkboxes for each row of a table.
109    *
110    * @param array $element
111    *   An associative array containing the properties and children of
112    *   the tableselect element. Properties used: #header, #options, #empty,
113    *   and #js_select. The #options property is an array of selection options;
114    *   each array element of #options is an array of properties. These
115    *   properties can include #attributes, which is added to the
116    *   table row's HTML attributes; see table.html.twig. An example of per-row
117    *   options:
118    *   @code
119    *     $options = array(
120    *       array(
121    *         'title' => $this->t('How to Learn Drupal'),
122    *         'content_type' => $this->t('Article'),
123    *         'status' => 'published',
124    *         '#attributes' => array('class' => array('article-row')),
125    *       ),
126    *       array(
127    *         'title' => $this->t('Privacy Policy'),
128    *         'content_type' => $this->t('Page'),
129    *         'status' => 'published',
130    *         '#attributes' => array('class' => array('page-row')),
131    *       ),
132    *     );
133    *     $header = array(
134    *       'title' => $this->t('Title'),
135    *       'content_type' => $this->t('Content type'),
136    *       'status' => $this->t('Status'),
137    *     );
138    *     $form['table'] = array(
139    *       '#type' => 'tableselect',
140    *       '#header' => $header,
141    *       '#options' => $options,
142    *       '#empty' => $this->t('No content available.'),
143    *     );
144    *   @endcode
145    *
146    * @return array
147    *   The processed element.
148    */
149   public static function preRenderTableselect($element) {
150     $rows = [];
151     $header = $element['#header'];
152     if (!empty($element['#options'])) {
153       // Generate a table row for each selectable item in #options.
154       foreach (Element::children($element) as $key) {
155         $row = [];
156
157         $row['data'] = [];
158         if (isset($element['#options'][$key]['#attributes'])) {
159           $row += $element['#options'][$key]['#attributes'];
160         }
161         // Render the checkbox / radio element.
162         $row['data'][] = drupal_render($element[$key]);
163
164         // As table.html.twig only maps header and row columns by order, create
165         // the correct order by iterating over the header fields.
166         foreach ($element['#header'] as $fieldname => $title) {
167           // A row cell can span over multiple headers, which means less row
168           // cells than headers could be present.
169           if (isset($element['#options'][$key][$fieldname])) {
170             // A header can span over multiple cells and in this case the cells
171             // are passed in an array. The order of this array determines the
172             // order in which they are added.
173             if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) {
174               foreach ($element['#options'][$key][$fieldname] as $cell) {
175                 $row['data'][] = $cell;
176               }
177             }
178             else {
179               $row['data'][] = $element['#options'][$key][$fieldname];
180             }
181           }
182         }
183         $rows[] = $row;
184       }
185       // Add an empty header or a "Select all" checkbox to provide room for the
186       // checkboxes/radios in the first table column.
187       if ($element['#js_select']) {
188         // Add a "Select all" checkbox.
189         $element['#attached']['library'][] = 'core/drupal.tableselect';
190         array_unshift($header, ['class' => ['select-all']]);
191       }
192       else {
193         // Add an empty header when radio buttons are displayed or a "Select all"
194         // checkbox is not desired.
195         array_unshift($header, '');
196       }
197     }
198
199     $element['#header'] = $header;
200     $element['#rows'] = $rows;
201
202     return $element;
203   }
204
205   /**
206    * Creates checkbox or radio elements to populate a tableselect table.
207    *
208    * @param array $element
209    *   An associative array containing the properties and children of the
210    *   tableselect element.
211    * @param \Drupal\Core\Form\FormStateInterface $form_state
212    *   The current state of the form.
213    * @param array $complete_form
214    *   The complete form structure.
215    *
216    * @return array
217    *   The processed element.
218    */
219   public static function processTableselect(&$element, FormStateInterface $form_state, &$complete_form) {
220     if ($element['#multiple']) {
221       $value = is_array($element['#value']) ? $element['#value'] : [];
222     }
223     else {
224       // Advanced selection behavior makes no sense for radios.
225       $element['#js_select'] = FALSE;
226     }
227
228     $element['#tree'] = TRUE;
229
230     if (count($element['#options']) > 0) {
231       if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
232         $element['#default_value'] = [];
233       }
234
235       // Create a checkbox or radio for each item in #options in such a way that
236       // the value of the tableselect element behaves as if it had been of type
237       // checkboxes or radios.
238       foreach ($element['#options'] as $key => $choice) {
239         // Do not overwrite manually created children.
240         if (!isset($element[$key])) {
241           if ($element['#multiple']) {
242             $title = '';
243             if (isset($element['#options'][$key]['title']) && is_array($element['#options'][$key]['title'])) {
244               if (!empty($element['#options'][$key]['title']['data']['#title'])) {
245                 $title = new TranslatableMarkup('Update @title', [
246                   '@title' => $element['#options'][$key]['title']['data']['#title'],
247                 ]);
248               }
249             }
250             $element[$key] = [
251               '#type' => 'checkbox',
252               '#title' => $title,
253               '#title_display' => 'invisible',
254               '#return_value' => $key,
255               '#default_value' => isset($value[$key]) ? $key : NULL,
256               '#attributes' => $element['#attributes'],
257               '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
258             ];
259           }
260           else {
261             // Generate the parents as the autogenerator does, so we will have a
262             // unique id for each radio button.
263             $parents_for_id = array_merge($element['#parents'], [$key]);
264             $element[$key] = [
265               '#type' => 'radio',
266               '#title' => '',
267               '#return_value' => $key,
268               '#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
269               '#attributes' => $element['#attributes'],
270               '#parents' => $element['#parents'],
271               '#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)),
272               '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
273             ];
274           }
275           if (isset($element['#options'][$key]['#weight'])) {
276             $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
277           }
278         }
279       }
280     }
281     else {
282       $element['#value'] = [];
283     }
284     return $element;
285   }
286
287 }