3 namespace Drupal\Core\Render\Element;
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;
11 * Provides a form element for a table with radios or checkboxes in left column.
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
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
23 * Other properties of the \Drupal\Core\Render\Element\Table element are also
29 * 'first_name' => $this->t('First Name'),
30 * 'last_name' => $this->t('Last Name'),
34 * 1 => ['first_name' => 'Indy', 'last_name' => 'Jones'],
35 * 2 => ['first_name' => 'Darth', 'last_name' => 'Vader'],
36 * 3 => ['first_name' => 'Super', 'last_name' => 'Man'],
39 * $form['table'] = array(
40 * '#type' => 'tableselect',
41 * '#header' => $header,
42 * '#options' => $options,
43 * '#empty' => $this->t('No users found'),
47 * See https://www.drupal.org/node/945102 for a full explanation.
49 * @see \Drupal\Core\Render\Element\Table
51 * @FormElement("tableselect")
53 class Tableselect extends Table {
58 public function getInfo() {
59 $class = get_class($this);
64 '#responsive' => TRUE,
67 [$class, 'preRenderTable'],
68 [$class, 'preRenderTableselect'],
71 [$class, 'processTableselect'],
75 '#theme' => 'table__tableselect',
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) {
91 $element += ['#default_value' => []];
92 foreach ($element['#default_value'] as $key => $flag) {
100 return is_array($input) ? array_combine($input, $input) : [];
106 * Prepares a 'tableselect' #type element for rendering.
108 * Adds a column of radio buttons or checkboxes for each row of a table.
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
121 * 'title' => $this->t('How to Learn Drupal'),
122 * 'content_type' => $this->t('Article'),
123 * 'status' => 'published',
124 * '#attributes' => array('class' => array('article-row')),
127 * 'title' => $this->t('Privacy Policy'),
128 * 'content_type' => $this->t('Page'),
129 * 'status' => 'published',
130 * '#attributes' => array('class' => array('page-row')),
134 * 'title' => $this->t('Title'),
135 * 'content_type' => $this->t('Content type'),
136 * 'status' => $this->t('Status'),
138 * $form['table'] = array(
139 * '#type' => 'tableselect',
140 * '#header' => $header,
141 * '#options' => $options,
142 * '#empty' => $this->t('No content available.'),
147 * The processed element.
149 public static function preRenderTableselect($element) {
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) {
158 if (isset($element['#options'][$key]['#attributes'])) {
159 $row += $element['#options'][$key]['#attributes'];
161 // Render the checkbox / radio element.
162 $row['data'][] = drupal_render($element[$key]);
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;
179 $row['data'][] = $element['#options'][$key][$fieldname];
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']]);
193 // Add an empty header when radio buttons are displayed or a "Select all"
194 // checkbox is not desired.
195 array_unshift($header, '');
199 $element['#header'] = $header;
200 $element['#rows'] = $rows;
206 * Creates checkbox or radio elements to populate a tableselect table.
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.
217 * The processed element.
219 public static function processTableselect(&$element, FormStateInterface $form_state, &$complete_form) {
220 if ($element['#multiple']) {
221 $value = is_array($element['#value']) ? $element['#value'] : [];
224 // Advanced selection behavior makes no sense for radios.
225 $element['#js_select'] = FALSE;
228 $element['#tree'] = TRUE;
230 if (count($element['#options']) > 0) {
231 if (!isset($element['#default_value']) || $element['#default_value'] === 0) {
232 $element['#default_value'] = [];
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']) {
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'],
251 '#type' => 'checkbox',
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,
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]);
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,
275 if (isset($element['#options'][$key]['#weight'])) {
276 $element[$key]['#weight'] = $element['#options'][$key]['#weight'];
282 $element['#value'] = [];