Rubrique: AstuceDifficultée: Intermédiaire Sommaire Ajout de la fonctionnalité Création du tableau Comme chacun le sait, l'élément table a été ajouté au Form API de Drupal 8, ce qui signifie que nous pouvons désormais programmer l'affichage d'un tableau dans un formulaire via l'élément : '#type' => 'table' Dans cette astuce, nous allons construire une en-tête (un header) ridiculement complexe pour notre tableau, à savoir un header multi-niveaux pour l'élément HTML table, comme celui-ci : Le header de ce tableau a plusieurs niveaux, avec des cellules groupées entre-elles. Le rendu de tableaux multi-niveaux est une nouvelle fonctionnalité qui n'existe pas à l'heure actuelle dans Drupal 8. J'en ai fait la proposition ici : https://www.drupal.org/node/893530#comment-11497445 Lorsque cette nouvelle fonctionnalité sera prête et ajoutée à Drupal 8 - c'est à dire que le lien ci-dessus sera indiqué 'fixed' ou '(closed) fixed' - alors vous pourrez sauter le premier paragraphe et utiliser directement la Form API comme dans le dernier paragraphe : créer le tableau. Ajout de la fonctionnalité Dans ce paragraphe, nous allons ajouter à Form API la possibilité d'effectuer un rendu aussi complexe pour notre tableau. Cette fonctionnalité a été proposée à l'inclusion pour Drupal 8, n'hésitez pas à commenter ici pour accélérer le processus. En attendant d'être dans le core de Drupal, nous allons devoir développer un peu pour l'ajouter nous-même. Pour cela, rien de plus simple, voici deux méthodes à ajouter dans un module custom ou dans votre thème ! Méthodes à ajouter dans un module custom ou votre thème /** * @see template_preprocess_table() * * Prepare table with multi row headers. * @todo: remove this when [#893530] is ready. */function THEME_preprocess_table(&$variables) { // Format the table columns: if (!empty($variables['colgroups'])) { foreach ($variables['colgroups'] as &$colgroup) { // Check if we're dealing with a simple or complex column if (isset($colgroup['data'])) { $cols = $colgroup['data']; unset($colgroup['data']); $colgroup_attributes = $colgroup; } else { $cols = $colgroup; $colgroup_attributes = array(); } $colgroup = array(); $colgroup['attributes'] = new Attribute($colgroup_attributes); $colgroup['cols'] = array(); // Build columns. if (is_array($cols) && !empty($cols)) { foreach ($cols as $col_key => $col) { $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); } } } } // Build an associative array of responsive classes keyed by column. $responsive_classes = array(); // Format the table header: $ts = array(); $header_columns = 0; if (!empty($variables['header'])) { $ts = tablesort_init($variables['header']); // Build multirows header from simple row. if (empty($variables['header_multilevel'])) { $variables['header'] = [ 'single_row' => $variables['header'], ]; } foreach ($variables['header'] as $row_key => $header_row) { $header_columns_in_row = 0; // Use a separate index with responsive classes as headers // may be associative. $responsive_index = -1; foreach ($header_row as $col_key => $cell) { // Increase the responsive index. $responsive_index++; if (!is_array($cell)) { $header_columns_in_row++; $cell_content = $cell; $cell_attributes = new Attribute(); $is_header = TRUE; } else { if (isset($cell['colspan'])) { $header_columns_in_row += $cell['colspan']; } else { $header_columns_in_row++; } $cell_content = ''; if (isset($cell['data'])) { $cell_content = $cell['data']; unset($cell['data']); } // Flag the cell as a header or not and remove the flag. $is_header = isset($cell['header']) ? $cell['header'] : TRUE; unset($cell['header']); // Track responsive classes for each column as needed. Only the header // cells for a column are marked up with the responsive classes by a // module developer or themer. The responsive classes on the header cells // must be transferred to the content cells. if (!empty($cell['class']) && is_array($cell['class'])) { if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; } elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; } } tablesort_header($cell_content, $cell, $variables['header'], $ts); // tablesort_header() removes the 'sort' and 'field' keys. $cell_attributes = new Attribute($cell); } $variables['header'][$row_key][$col_key] = array(); $variables['header'][$row_key][$col_key]['tag'] = $is_header ? 'th' : 'td'; $variables['header'][$row_key][$col_key]['attributes'] = $cell_attributes; $variables['header'][$row_key][$col_key]['content'] = $cell_content; } $header_columns = max($header_columns_in_row, $header_columns); } } $variables['header_columns'] = $header_columns; // Rows and footer have the same structure. $sections = array('rows' , 'footer'); foreach ($sections as $section) { if (!empty($variables[$section])) { foreach ($variables[$section] as $row_key => $row) { $cells = $row; $row_attributes = array(); // Check if we're dealing with a simple or complex row if (isset($row['data'])) { $cells = $row['data']; $variables['no_striping'] = isset($row['no_striping']) ? $row['no_striping'] : FALSE; // Set the attributes array and exclude 'data' and 'no_striping'. $row_attributes = $row; unset($row_attributes['data']); unset($row_attributes['no_striping']); } // Build row. $variables[$section][$row_key] = array(); $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); $variables[$section][$row_key]['cells'] = array(); if (!empty($cells)) { // Reset the responsive index. $responsive_index = -1; foreach ($cells as $col_key => $cell) { // Increase the responsive index. $responsive_index++; if (!is_array($cell)) { $cell_content = $cell; $cell_attributes = array(); $is_header = FALSE; } else { $cell_content = ''; if (isset($cell['data'])) { $cell_content = $cell['data']; unset($cell['data']); } // Flag the cell as a header or not and remove the flag. $is_header = !empty($cell['header']); unset($cell['header']); $cell_attributes = $cell; } // Active table sort information. if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; } // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM // class from header to cell as needed. if (isset($responsive_classes[$responsive_index])) { $cell_attributes['class'][] = $responsive_classes[$responsive_index]; } $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; } } } } } if (empty($variables['no_striping'])) { $variables['attributes']['data-striping'] = 1; }} /** * hook_theme_registry_alter(). */function THEME_theme_registry_alter(&$theme_registry) { // Add new key variable to table element for preprocess method. $theme_registry['table']['variables']['header_multilevel'] = FALSE; // Remove template_preprocess_table method from preprocess functions (will be // replaced by our THEME_preprocess_table(). $theme_registry['table']['preprocess functions'] = array_diff($theme_registry['table']['preprocess functions'], ['template_preprocess_table']);} La première méthode est simplement une réécriture de la méthode template_preprocess_table() du fichier theme.inc de Drupal. La seconde méthode permet d'altérer la structure du rendering de l'élément table en ajoutant une nouvelle clef d'une part, et en supprimant l'usage de la méthode template_preprocess_table() devenue obsolète. Désormais, il s'agit d'insérer un nouveau template dans votre thème : table.html.twig. Ce template devra être placé dans un dossier templates de votre thème et viendra remplacer l'usage de celui provenant de Drupal. Ce fichier doit contenir le code suivant : Code du template table.html.twig {# /** * @file * Theme override to display a table. * * Available variables: * - attributes: HTML attributes to apply to the <table> tag. * - caption: A localized string for the <caption> tag. * - colgroups: Column groups. Each group contains the following properties: * - attributes: HTML attributes to apply to the <col> tag. * - header: Table header rows. Each row contains and array of cell with 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 <tr> 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() */ #} <table{{ attributes }}> {% if caption %} <caption>{{ caption }}</caption> {% endif %} {% for colgroup in colgroups %} {% if colgroup.cols %} <colgroup{{ colgroup.attributes }}> {% for col in colgroup.cols %} <col{{ col.attributes }} /> {% endfor %} </colgroup> {% else %} <colgroup{{ colgroup.attributes }} /> {% endif %} {% endfor %} {% if header %} <thead> {% for row in header %} <tr> {% for cell in row %} {% set cell_classes = [ cell.active_table_sort ? 'is-active', ] %} <{{ cell.tag }}{{ cell.attributes.addClass(cell_classes) }}> {{- cell.content -}} </{{ cell.tag }}> {% endfor %} </tr> {% endfor %} </thead> {% endif %} {% if rows %} <tbody> {% for row in rows %} {% set row_classes = [ not no_striping ? cycle(['odd', 'even'], loop.index0), ] %} <tr{{ row.attributes.addClass(row_classes) }}> {% for cell in row.cells %} <{{ cell.tag }}{{ cell.attributes }}> {{- cell.content -}} </{{ cell.tag }}> {% endfor %} </tr> {% endfor %} </tbody> {% elseif empty %} <tbody> <tr class="odd"> <td colspan="{{ header_columns }}" class="empty message">{{ empty }}</td> </tr> </tbody> {% endif %} {% if footer %} <tfoot> {% for row in footer %} <tr{{ row.attributes }}> {% for cell in row.cells %} <{{ cell.tag }}{{ cell.attributes }}> {{- cell.content -}} </{{ cell.tag }}> {% endfor %} </tr> {% endfor %} </tfoot> {% endif %} </table> Création du tableau La partie du dessus ayant assuré la création de la nouvelle fonctionnalité, nous disposons désormais d'une nouvelle clef et d'une possibilité nouvelle pour l'élément table permettant la création d'un tableau avec une en-tête complexe. Voyons comment : $form['table'] = [ '#type' => 'table', '#header' => [ 'row1' => [ 'bigcell' => [ 'data' => 'Big header', 'rowspan' => 2 ], 'longcell' => [ 'data' => 'Long header', 'colspan' => 2 ], 'Small header' ], 'row2' => ['cell1', 'cell2', 'cell3'] ], '#header_multilevel' => TRUE, '#empty' => $this->t('There are no lines here.'),]; Dans le code ci-dessus, nous remarquons immédiatement la nouvelle clef : #header_multilevel passée à TRUE. Cette clef indique que nous désirons un header sur plusieurs lignes, et donc, que l'on donnera un tableau à l'élément #header plus complexe qu'habituellement. Cet élément #header justement est désormais un tableau dont chaque élément représente une ligne du header. Nous retrouvons donc deux lignes : 'row1' et 'row2' (la valeur des clefs n'a pas d'importance). Chacune de ces lignes contient quatre colonnes. Dans la première, la première cellule groupe les deux lignes ensembles, puis la seconde cellule groupe deux colonnes ensembles, enfin la dernière est une cellule simple. La seconde ligne ne contient que trois cellules, puisque la première cellule est une cellule groupée provenant de la première colonne. Le résultat étant le tableau donné en début d'article ! Remarquez que vous pouvez toujours utiliser les trois syntaxes pour une cellule : un simple texte pour une cellule simple un tableau de texte pour définir plusieurs cellules simples un tableau contenant la clef 'data', définissant une cellule complexe Ceci ne change donc pas de l'API classique pour un tableau. Connectez-vous ou inscrivez-vous pour publier un commentaire