Создаем разные виды Drupal HTML таблиц (часть 1)

Создаем разные виды Drupal HTML таблиц (часть 1)

Сегодня мы поговорим о html таблицах в друпале, а точнее о том, как их создавать. Не найдется, пожалуй, ни одного человека, кто не встречался с таблицами в друпале. Первое место, где их можно увидеть - это, конечно же, админка.
Прежде чем мы перейдем к рассмотрению различных видов таблиц, приведем сразу общий хук меню, чтобы его не повторять для каждого вида.
В файле MYMODULE.module

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Implements hook_menu().
 */
function MYMODULE_menu() {
  $items['test-tables'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array('MYMODULE_tables_page'),
    // Вариант тестовый, поэтому откроем доступ всем, в реальном модуле, урл конечно нужно закрыть пермишенами.
    'access callback' => TRUE,
  );
 
  return $items;
}

Содержание

Простая таблица

Начнем с самой простой таблицы, таблицы без сортировок, иерархии и т.д.
Опишем тему, которая будет непосредственно отвечать за вывод таблицы. hook_theme()[1] также располагается в MYMODULE.module.

1
2
3
4
5
6
7
8
9
10
11
/**
 * Implements hook_theme().
 */
function MYMODULE_theme() {
  return array(
    'simple' => array(
      'render element' => 'form',
      'file' => 'MYMODULE.theme.inc',
    ),
  );
}

Теперь необходимо описать функцию MYMODULE_tables_page(), которая будет отдавать рендерный массив необходимых данных. Для примера выведем все опубликованные заголовки нод и дополнительно укажем тип материала, дату создания, ссылку для редактирования. Намеренно не использовал node_load_multiple(), т.к. абсолютно незачем загружать полноценные объекты нод для получения этих данных из одной таблицы одним запросом.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * Get nodes titles.
 */
function MYMODULE_get_all_nodes() {
  return db_select('node', 'n')
    ->fields('n', array('nid', 'type', 'title', 'created'))
    ->condition('n.status', NODE_PUBLISHED)
    ->range(NULL, 10)
    ->execute()
    ->fetchAllAssoc('nid');
}
 
/**
 * Example tables page.
 */
function MYMODULE_tables_page() {
 $nodes = MYMODULE_get_all_nodes();
 
  $form['simple_table'] = array(
    '#type'  => 'container',
    '#theme' => 'simple',
  );
 
  foreach ($nodes as $nid => $node) {
    $form['simple_table'][$nid]['title'] = array(
      '#markup' => $node->title,
    );
 
    $form['simple_table'][$nid]['type'] = array(
      '#markup' => $node->type,
    );
 
    $form['simple_table'][$nid]['created'] = array(
      '#markup' => date('d-m-Y H:i', $node->created),
    );
 
    $form['simple_table'][$nid]['link'] = array(
      '#type' => 'link',
      '#title' => t('Edit'),
      '#href' => $nid . '/edit',
    );
  }
 
  return $form;
}

Далее очередь функции темы theme_simple(). В файле MYMODULE.theme.inc поместим следующие строки

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * Returns HTML for a simple table.
 */
function theme_simple($vars) {
  $form = $vars['form'];
  $rows = array();
  foreach (element_children($form) as $id) {
    if (isset($form[$id])) {
      $rows[] = array(
        'data' => array(
          drupal_render($form[$id]['title']),
          drupal_render($form[$id]['type']),
          drupal_render($form[$id]['created']),
          drupal_render($form[$id]['link']),
        ),
        'class' => array(),
      );
    }
  }
 
  // Шапка таблицы.
  $header = array(
    t('Title'),
    t('Type'),
    t('Date'),
    t('Action'),
  );
 
  $output = theme('table', array(
    'header' => $header,
    'rows'  => $rows,
    'empty' => t('Table is empty', array()),
  ));
 
  $output .= drupal_render_children($form);
  return $output;
}

Результат приведен на изображении ниже.
простая таблица

Таблица с сортировкой и пейджинатором

Сортировку и постраничную навигацию можно "прицепить" практически к любой таблице. Рассмотрим изменения на примере простой таблицы, описанной выше.
Необходимо внести изменения непосредственно в сам запрос на выборку информации о нодах.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * Get nodes titles.
 */
function MYMODULE_get_all_nodes($header) {
  return db_select('node', 'n')
    // Добавление возможности постраничного вывода.
    ->extend('PagerDefault')
    // Добавление возможности сортировки вместе с orderByHeader().
    ->extend('TableSort')
    ->fields('n', array('nid', 'type', 'title', 'created'))
    ->condition('n.status', NODE_PUBLISHED)
    // Ограничиваем отображение нод тремя строками.
    ->limit(3)
    // Сортировка по шапке таблицы.
    ->orderByHeader($header)
    ->execute()
    ->fetchAllAssoc('nid');
}

Аргумент $header в функции MYMODULE_get_all_nodes() - это шапка будущей таблицы. Внесем следующие изменения в MYMODULE_tables_page().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * Example tables page.
 */
function MYMODULE_tables_page() {
  // Шапка таблицы с указанием филдов, по которым будет производиться сортировка.
  $header = array(
    array('data' => t('Title'), 'field' => 'n.title'),
    array('data' => t('Type'), 'field' => 'n.type'),
    array('data' => t('Date'), 'field' => 'n.created'),
    t('Action'),
  );
 
  $nodes = MYMODULE_get_all_nodes($header);
  $form['simple_table'] = array(
    '#type'  => 'container',
    '#theme' => 'simple',
    // Передаем header в функцию темизации simple.
    '#header' => $header,
  );
 
  foreach ($nodes as $nid => $node) {
    $form['simple_table'][$nid]['title'] = array(
      '#markup' => $node->title,
    );
    $form['simple_table'][$nid]['type'] = array(
      '#markup' => $node->type,
    );
    $form['simple_table'][$nid]['created'] = array(
      '#markup' => date('d-m-Y H:i', $node->created),
    );
    $form['simple_table'][$nid]['link'] = array(
      '#type' => 'link',
      '#title' => t('Edit'),
      '#href' => $nid . '/edit',
    );
  }
  // Подключаем отображение пейджинатора.
  $form['pager']['#markup'] = theme('pager');
 
  return $form;
}

В MYMODULE.theme.inc передаем $header в функцию темизации table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * Returns HTML for a simple table.
 */
function theme_simple($vars) {
  $form = $vars['form'];
  $rows = array();
  foreach (element_children($form) as $id) {
    if (isset($form[$id])) {
      $rows[] = array(
        'data' => array(
          drupal_render($form[$id]['title']),
          drupal_render($form[$id]['type']),
          drupal_render($form[$id]['created']),
          drupal_render($form[$id]['link']),
        ),
        'class' => array(),
      );
    }
  }
 
  $output = theme('table', array(
    'header' => $form['#header'],
    'rows'  => $rows,
    'empty' => t('Table is empty', array()),
  ));
 
  $output .= drupal_render_children($form);
  return $output;
}

В итоге получаем результат
таблица с пейджинатором и сортировкой

Draggable таблица

Draggable таблицы встречаются в друпале повсеместно. Это и таблица форматов текста, таблица изменения порядка фильтров, таблица управления полей для типа материала.
Как и в предыдущих примерах начинаем с объявления нашей темы.
В файле MYMODULE.module

1
2
3
4
5
6
7
8
9
10
11
/**
 * Implements hook_theme().
 */
function MYMODULE_theme() {
  return array(
    'drag_table' => array(
      'render element' => 'form',
      'file' => 'MYMODULE.theme.inc',
    ),
  );
}

Функция выборки данных MYMODULE_get_all_nodes() точно такая же как и в примере с простой таблицей. Функция MYMODULE_tables_page() немного притерпит изменений с ключами элементов, названием темы и дополнительным элементом "вес", а в остальном все та же функция из 1-го примера.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Example tables page.
 */
function MYMODULE_tables_page() {
 
  $nodes = MYMODULE_get_all_nodes();
  $form['drag_table'] = array(
    '#type'   => 'container',
    '#theme'  => 'drag_table',
  );
 
  foreach ($nodes as $nid => $node) {
    $form['drag_table'][$nid]['title'] = array(
      '#markup' => $node->title,
    );
 
    $form['drag_table'][$nid]['type'] = array(
      '#markup' => $node->type,
    );
 
    $form['drag_table'][$nid]['created'] = array(
      '#markup' => date('d-m-Y H:i', $node->created),
    );
 
    $form['drag_table'][$nid]['link'] = array(
      '#type' => 'link',
      '#title' => t('Edit'),
      '#href' => $nid . '/edit',
    );
 
    // Вес.
    $form['drag_table'][$nid]['weight'] = array(
      '#type'  => 'weight',
      '#delta' => 10,
      '#title' => t('Weight'),
      '#title_display' => 'invisible',
      '#default_value' => '',
    );
  }
 
  return $form;
}

Функция темизации в MYMODULE.theme.inc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * Returns HTML for a drag table.
 */
function theme_drag_table($vars) {
  $form = $vars['form'];
  $rows = array();
  foreach (element_children($form) as $id) {
    // Добавляем класс для элемента "вес".
    $form[$id]['weight']['#attributes']['class'] = array('drag-order-weight');
    if (isset($form[$id])) {
      $rows[] = array(
        'data' => array(
          drupal_render($form[$id]['title']),
          drupal_render($form[$id]['type']),
          drupal_render($form[$id]['created']),
          drupal_render($form[$id]['link']),
          drupal_render($form[$id]['weight']),
        ),
        // Добавляем класс draggable для строки.
        'class' => array('draggable'),
      );
    }
  }
 
  // Шапка таблицы.
  $header = array(
    t('Title'),
    t('Type'),
    t('Date'),
    t('Action'),
    t('Weight'),
  );
 
  $output = theme('table', array(
    'header' => $header,
    'rows'  => $rows,
    'empty' => t('Table is empty', array()),
    // Добавляем ID для таблицы (необходимо для добавления tableDrag поведения).
    'attributes' => array('id' => 'drag-table'),
  ));
 
  $output .= drupal_render_children($form);
  // Подключение draggable поведения для таблицы.
  drupal_add_tabledrag('drag-table', 'order', 'sibling', 'drag-order-weight');
  return $output;
}

Подробнее ознакомиться с функцией drupal_add_tabledrag() и с ее аргументами можно по ссылке[2], приведенной в дополнительных материалах в конце статьи.
Итак получаем вот такую таблицу
draggable таблица

Таблица с возможностью выбора строк (tableselect)

Данный вид таблицы можно встретить в друпале, например, на странице контента. Объявлять свою функцию темизации нет необходимости, достаточно внести небольшие изменения в MYMODULE_tables_page().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Example tables page.
 */
function MYMODULE_tables_page() {
  $nodes = MYMODULE_get_all_nodes();
  $options = array();
  foreach ($nodes as $nid => $node) {
    // Обращаю внимание на то, что ключи массива ячеек таблицы должны совпадать с ключами массива шапки таблицы.
    $options[$nid][] = $node->title;
    $options[$nid][] = $node->type;
    $options[$nid][] = date('d-m-Y H:i', $node->created);
    $options[$nid][] = l('Edit', $nid . '/edit');
  }
 
  // Шапка таблицы.
  $header = array(
    t('Title'),
    t('Type'),
    t('Date'),
    t('Action'),
  );
 
  $form['table_select'] = array(
    '#type' => 'tableselect',
    '#header' => $header,
    '#options' => $options,
    '#empty' => t('No content available.'),
  );
 
  return $form;
}

Таблица tableselect
tableselect таблица

Материал оказался довольно объемным, поэтому остальные виды таблиц будут рассмотрены в следующей статье Создаем разные виды Drupal HTML таблиц (часть 2), а именно:

  • Таблица с иерархией строк
  • Draggable таблица с возможностью выбора строк
  • Таблица блоков (как на странице admin/structure/blocks)
  • Таблица с фильтром

Дополнительная информация по статье

  1. https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_theme/7 - описание hook_theme() из документации.
  2. https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_add_tabledrag/7 - подробное описание drupal_add_tabledrag() из документации.
  3. https://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7 - описание theme_table.
  4. https://api.drupal.org/api/drupal/includes!form.inc/function/theme_tableselect/7 - описание theme_tableselect.

6 Comments

Аватар пользователя kalabro

Ух ты! Ждём вторую часть)

Ух ты! Ждём вторую часть)

> Таблица блоков (как на странице admin/structure/blocks)

Совсем недавно мучилась вот с чем. Если верхний уровень делать тоже draggable (например, как будто бы можно сортировать не только блоки внутри регионов, но и сами регионы), то нельзя использовать у этих «регионов» colspan. У tabledrag.js ломается расчёт столбцов. Мелочь, а неприятно.

Аватар пользователя Алексей

Извините, наверно очень

Извините, наверно очень глупый вопрос.
Вот я создал модуль, перенес в его файлы ***.module и ***.theme.inc, изменил названия функций, включил этот модуль, сбросил кэш.
А где я могу посмотреть результат его работы? То есть то, что у вас сразу после строки "Результат приведен на изображении ниже."

Аватар пользователя nightdevel

Добрый вечер! В самом вверху

Добрый вечер! В самом вверху статьи я привел хук меню, в котором указан url адрес, по обращению к которому будет отрабатывать пейдж колбек drupal_get_form с переданным в него в качестве аргумента именем функции MYMODULE_tables_page. В данном случае, для просмотра результата необходимо обратиться по урлу

1
SITEURL/test-tables

где SITEURL - ваш адрес сайта на локальной машине.

Аватар пользователя Алексей

Подскажите пожалуйста.

Подскажите пожалуйста.
Dhn у вас в начале самом

1
2
3
4
5
6
7
8
9
10
function MYMODULE_menu() {
  $items['test-tables'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array('MYMODULE_tables_page'),
    // Вариант тестовый, поэтому откроем доступ всем, в реальном модуле, урл конечно нужно закрыть пермишенами.
    'access callback' => TRUE,
  );
 
  return $items;
}

написано "// Вариант тестовый, поэтому откроем доступ всем, в реальном модуле, урл конечно нужно закрыть пермишенами."
А нельзя ли пример, как это сделать?

Аватар пользователя Семён

Нужно ли в tableselect

Нужно ли в tableselect дополнительно проверять с сабмите выбранные селекты на предмет подсовывания тех, которых нет в форме?
Например форма для удаления node и селектах id.

Аватар пользователя Гость

можно сделать доп. проверку,

можно сделать доп. проверку, но если вы имеете ввиду модификацию формы через редактирование html кода (подсовывание новых id) - то друпал не позволит этого сделать, т.к. есть спец. токен формы, который будет проверен на предмет модификации формы.