Содержание
Таблицы в админке
InstantCMS 2 позволяет автоматизировать вывод табличных записей (списков) в админке компонента.
Работу автоматических таблиц проще всего рассматривать на примере.
Пусть это будет иллюстративный компонент «Гостевая книга» (guestbook), который позволяет оставлять сообщения на сайте. У каждого сообщения есть три поля: имя автора (author), дата публикации (date_pub) и текст сообщения (content). В админке компонента мы хотим выводить все сообщения в одной таблице.
Предположим, что у нас уже создан контроллер админки (backend.php) и в нем определен action index:
class backendGuestbook extends cmsBackend { public function actionIndex(){ } }
Сообщения хранятся в таблице guestbook_messages в базе данных. Таблица имеет поля: id, author, date_pub, content.
Модель компонента имеет метод getMessages(), возвращающий список сообщений и метод getMessagesCount(), возвращающий количество сообщений:
class modelGuestbook extends cmsModel { public function getMessages(){ return $this->get('guestbook_messages'); } public function getMessagesCount(){ return $this->getCount('guestbook_messages'); } }
Теперь мы хотим чтобы в админке компонента экшен index получал из базы все сообщения и выводил их в виде таблицы.
Создание описания таблицы
Для генерации таблицы системе необходимо знать какие колонки она будет содержать. Для этого создается файл описания. Такие файлы лежат в папке /system/controllers/{компонент}/backend/grids. Каждый файл имеет название grid_{название_таблицы}.php. Внутри файла определяется функция grid_{название_таблицы}(), возвращающая массив с тремя полями:
options | Опции таблицы |
columns | Описание столбцов (полей) таблицы |
actions | Действия с каждой строкой (редактировать, удалить и т.п.) |
В нашем примере таблица может называться messages и располагаться в файле /system/controllers/guestbook/backend/grids/grid_messages.php. Внутри объявлена функция:
function grid_messages($controller){ $options = array(); $columns = array(); $actions = array(); return array( 'options' => $options, 'columns' => $columns, 'actions' => $actions ); }
Входящий параметр $controller содержит ссылку на объект контроллера (backendGuestbook).
Опции таблицы
Массив $options возвращаемый выше должен содержать список опций таблицы. Доступные опции:
Название | Описание | Возможные значения |
---|---|---|
is_sortable | Таблицу можно сортировать по столбцам | true или false |
is_filter | Таблицу можно фильтровать по некоторым столбцам | true или false |
is_pagination | Таблица выводится постранично | true или false |
is_draggable | Строки таблицы можно перетаскивать мышкой вверх и вниз | true или false |
order_by | Поле, по которому сортируются строки таблицы по-умолчанию | название поля |
order_to | Порядок сортировки по-умолчанию | asc или desc |
show_id | Показывать колонку с полем id в таблице | true или false |
В нашем примере мы хотим выводить сортируемую таблицу с фильтрами по полям и постраничной разбивкой. Сообщения по-умолчанию сортируются по дате, по убыванию. Заполним массив $options:
$options = array( 'is_sortable' => true, // сортировать по столбцам 'is_filter' => true, // фильтровать по некоторым столбцам 'is_pagination' => true, // выводить постранично 'is_draggable' => false, // нельзя перетаскивать мышкой 'order_by' => 'date_pub',// сортировать по дате публикации 'order_to' => 'desc', // по убыванию (сначала новые) 'show_id' => true // показывать столбец id );
Поля таблицы
Массив $columns в описании таблицы будет содержать описание для каждого столбца (поля). Каждый элемент в $columns соответствует одному полю, а ключ элемента – названию поля во входящих данных (которые мы будем брать из модели).
Например, добавим столбец с полем id:
$columns = array( 'id' => array( 'title' => 'id', // Заголовок столбца 'width' => 30, // Ширина столбца, в пикселях 'filter' => 'exact' // Фильтр по столбцу - искать точные совпадения ) );
Каждое поле может иметь несколько опций:
Название | Описание |
---|---|
title | Заголовок столбца в таблице |
width | Ширина столбца, в пикселях или процентах (в этом случае указывается как строка, например «40%») |
href | Ссылка поля. Позволяет делать поля ссылками. Например, если нужно чтобы значение поля ссылалось на форму редактирования соответствующей записи. Подробнее ниже |
href_handler | Для InstantCMS 2.7.0 и выше. Анонимная функция function($row){} . Получает всю запись (строку) в виде массива. Должна вернуть true или false. Если возвращает true – поле будет ссылкой (если опция href задана). Если возвращает false, то даже при заданной опции href поле не будет ссылкой. |
flag | Если указано true, то позволяет выводить значения поля в виде галочки. Если поле содержит единицу или не пустое – будет выведена зеленая галочка, если 0 или пустое – серая галочка |
flag_on | Позволяет указать точное значение, при котором поле считается заполненным. В этом случае, зеленая галочка будет выводиться только при совпадении значения поля с указанным |
flag_toggle | Позволяет указать URL, на который будет посылаться уведомление о кликах по галочке в поле. Можно использовать, например, для быстрого переключения значения поля одним кликом. Обработчик по указанному URL должен вернуть JSON-объект с двумя полями: error – Наличие ошибки (true или false), is_on – Новое состояние флага (true или false). Можно использовать универсальный URL |
filter | Включает фильтр для столбца. В качестве значения передается тип фильтра – exact (точное совпадение) или like (частичное совпадение). Если указан тип фильтра, столбец будет содержать текстовое поле для ввода условия фильтра |
handler | Анонимная функция с двумя входящими параметрами: function($field, $row){ } , где $field – значение текущего поля, $row – вся строка целиком. Функция должна вернуть обработанное значение $field. Применяется для форматирования выводимых в таблице значений (например, чтобы добавить валюту к ценам в числовых полях) |
editable | Включает инлайн редактирование поля. Опция содержит массив, который может содержать две ячейки: table (обязательная) и save_action (необязательная). В ячейке table указывается название таблицы, в которую изменения этого поля будут вноситься, в ячейке save_action указывается экшен, который будет принимать данные для сохранения. Если ячейка save_action не указана, то используется системный экшен, в большинстве случаев его будет достаточно. Обратите внимание, в таблице table должно быть инкрементное поле id с уникальным идентификатором записи. |
Для нашего примера с гостевой книгой массив столбцов будет выглядеть так:
$columns = array( 'id' => array( 'title' => 'id', 'width' => 30, 'filter' => 'exact' ), 'author' => array( 'title' => LANG_AUTHOR, 'filter' => 'like' ), 'date_pub' => array( 'title' => LANG_DATE, 'filter' => 'like', 'handler' => function ($field, $row){ return date('d.m.Y', strtotime($field)); } ), 'content' => array( 'title' => LANG_MESSAGE, 'filter' => 'like', 'handler' => function ($field, $row){ return mb_strlen($field) > 100 ? mb_substr($field, 0, 100) : $field; } ) );
Поля date_pub и content мы дополнительно обрабатываем с помощью опции handler. В первом случае мы приводим дату в нужный формат, во-втором – обрезаем сообщения длиннее 100 символов. Для поля id включаем строгий фильтр, для остальных полей – фильтры по частичному совпадению.
Ссылки из полей таблицы
При выводе полей в таблице любое из них можно сделать активной ссылкой. Для этого используется опция href в описании поля (столбца). Значением этой опции может быть URL ссылки. Внутри URL можно подставлять значения любых полей текущей записи (строки) таблицы. Это делается с помощью выражения {имя_поля}.
Например, каждое сообщение в гостевой книге доступно по адресу http://site/guestbook/message/123, где 123 - ID сообщения в базе. Мы хотим чтобы текст каждого сообщения в нашей таблице сообщений в админке был ссылкой на страницу сообщения. Тогда мы можем добавить опцию href для столбца content:
'content' => array( 'title' => LANG_MESSAGE, 'filter' => 'like', 'handler' => function ($field, $row){ return mb_strlen($field) > 100 ? mb_substr($field, 0, 100) : $field; }, 'href' => href_to('guestbook', 'message', '{id}') )
Для формирования URL мы используем функцию href_to(), которая возвращает URL требуемого экшена с параметрами. В качестве параметра мы указываем {id}, то есть значение соответствующего поля для данной строки.
Действия таблицы
В последнем столбце таблицы обычно располагаются иконки действий, которые можно совершить с каждой строкой (такие как редактирование, удаление и т.п.):
Массив $actions в описании таблицы содержит список доступных действий для строк. Каждое действие представлено вложенным массивом, который может иметь следующие поля:
title | Заголовок действия, отображается при наведении мыши на иконку |
class | CSS-класс, в котором описана иконка действия. Подробнее см. ниже |
href | Ссылка (URL) на экшен, выполняющий данное действие |
confirm | Текст подтверждения, который будет показан при попытке совершить действие. Например, «Вы уверены?». В тексте можно использовать подстановку полей, с помощью выражения {имя_поля} |
handler | Анонимная функция function($row){}. Получает всю запись (строку) в виде массива. Должна вернуть true или false. Если возвращает false – данное действие не будет отображаться для данной строки. Используется, например, для запрета удаления определенных строк |
В нашем примере с гостевой книгой каждое сообщение может иметь 3 действия – «посмотреть на сайте», «редактировать», «удалить»:
$actions = array( array( 'title' => LANG_VIEW, 'class' => 'view', 'href' => href_to('guestbook', 'message', '{id}') ), array( 'title' => LANG_EDIT, 'class' => 'edit', 'href' => href_to($controller->root_url, 'edit', '{id}') ), array( 'title' => LANG_DELETE, 'class' => 'delete', 'href' => href_to($controller->root_url, 'delete', '{id}'), 'confirm' => LANG_GUESTBOOK_MESSAGE_DELETE_CONFIRM, ), );
Первое действие ссылается на экшен фронтенда – /guestbook/message, а следующие два – на экшены админки. В этом случае вместо названия контроллера в начало URL подставляется свойство $controller→root_url, содержащее корневой путь для backend-контроллера компонента в админке.
Для действия «Удалить» используется подтверждение, заданное константой в языковом файле компонента:
define('LANG_GUESTBOOK_MESSAGE_DELETE_CONFIRM', 'Удалить сообщение №{id}?');
В сообщении выражение {id} будет заменено на ID удаляемой строки.
Иконки действий
Для вывода иконки действия используется CSS-класс. Стандартные классы:
Класс → | Иконка | Класс → | Иконка |
---|---|---|---|
view | accept | ||
edit | play | ||
config | rss | ||
labels | user | ||
fields | unbind | ||
filter | delete | ||
permissions | relations |
Вы можете определить собственный класс my_class в файле /templates/default/controllers/{компонент}/backend/styles.css:
.datagrid .actions a.my_class { background-image: url("../../images/icons/my_icon.png"); }
В этом примере будет использоваться иконка /templates/default/images/icons/my_icon.png.
Вывод таблицы
После того, как готов файл с описанием структуры таблицы, необходимо реализовать ее отображение и заполнение данными. Этот процесс состоит из нескольких этапов:
- Тело таблицы выводится в шаблоне нужного экшена;
- Отправляется AJAX-запрос для получения данных;
- Полученные данные добавляются в таблицу;
- При изменении страницы, сортировки или фильтров таблицы - возврат к п.2.
Вывод таблицы в шаблоне
Чтобы вывести таблицу в шаблоне сначала необходимо загрузить ее описание. Это делается в коде экшена при помощи собственного метода loadDataGrid($grid_name):
$grid = $this->loadDataGrid('my_grid');
Для сохранения сортировки в вашей таблице (если сортировка предполагается), используйте следующий код для загрузки описания таблицы при её выводе:
$grid = $this->loadDataGrid('my_grid', false, 'my_component.my_key');
где my_component - имя вашего компонента (например guestbook), my_key - некий ключ, однозначно идентифицирующий данную таблицу.
Затем полученный массив $grid передается в шаблон, где выводится с помощью метода renderGrid($data_url, $grid):
<?php $this->renderGrid($this->href_to('action'), $grid); ?>
Первый параметр данного метода содержит URL экшена, который будет возвращать данные для вывода в таблице (строки). Эти данные возвращаются в формате JSON по AJAX-запросу. Вторым параметром передается объект описания таблицы, полученный ранее в экшене.
В результате на странице формируется тело таблицы, т.е. пустая таблица, имеющая только заголовки столбцов. В таком виде страница отправляется пользователю. Данные подгружаются уже после вывода страницы пользователю, асинхронно.
Вернемся к нашему примеру с гостевой книгой. Добавим загрузку и вывод таблицы в контроллере админке и шаблоне.
Контроллер /system/controllers/guestbook/backend.php:
class backendGuestbook extends cmsBackend { public function actionIndex(){ // загружаем описание таблицы $grid = $this->loadDataGrid('messages'); // подключаем шаблон и передаем описание в него return cmsTemplate::getInstance()->render('backend/index', array( 'grid' => $grid )); } }
Шаблон /templates/default/controllers/guestbook/backend/index.tpl.php:
<h3>Список сообщений</h3> <?php $this->renderGrid($this->href_to('messages_ajax'), $grid); ?>
Загрузка данных в таблицу
Для загрузки данных обычно используется отдельный экшен, который ничего не выводит в браузер, а только возвращает JSON-массив со строками таблицы.
Внутри экшена происходит следующее:
- Загрузка описания таблицы;
- Подключение модели компонента;
- Получение данных о фильтрах из запроса;
- Если есть сохраненный ранее фильтр - получение его, либо сохранение полученного выше;
- Применение фильтров к модели;
- Получение отобранных данных из модели;
- Формирование и вывод результата в виде JSON.
В примере выше мы указали в шаблоне экшен messages_ajax как источник данных при построении таблицы.
Добавим этот экшен в контроллер:
public function actionMessagesAjax(){ // проверяем что это AJAX-запрос if (!$this->request->isAjax()) { cmsCore::error404(); } // загружаем описание таблицы $grid = $this->loadDataGrid('messages'); // получаем собственную модель $model = cmsCore::getModel($this->name); // устанавливаем кол-во строк на одной странице таблицы по-умолчанию $model->setPerPage(admin::perpage); // подготавливаем массив, в котором будут содержаться // условия фильтрации (в т.ч. номер страницы и сортировка) $filter = array(); // получаем описание фильтров из запроса $filter_str = $this->request->get('filter', ''); // если в $filter_str ничего нет, то загрузим ранее сохранённый фильтр, если он есть, // либо, если в $filter_str есть данные - обновим фильтр $filter_str = cmsUser::getUPSActual('my_component.my_key', $filter_str); if ($filter_str){ // если есть фильтры, // парсим полученную строку с условиями и заполняем массив $filter parse_str($filter_str, $filter); // применяем фильтры таблицы к модели $model->applyGridFilter($grid, $filter); } // получаем общее кол-во строк, с учетом всех фильтров $total = $model->getMessagesCount(); // получаем – сколько строк на странице, либо значение по-умолчанию $perpage = isset($filter['perpage']) ? $filter['perpage'] : admin::perpage; // вычисляем количество страниц $pages = ceil($total / $perpage); // получаем из модели записи (строки), удовлетворяющие условиям фильтрации $messages = $model->getMessages(); // подключаем объект шаблона $template = cmsTemplate::getInstance(); // просим шаблон отрендерить JSON-массив со строками таблицы $template->renderGridRowsJSON($grid, $messages, $total, $pages); $this->halt(); // завершаем работу }
Обратите внимание, что делать разбор фильтров и применять их к модели имеет смысл только если ваша таблица имеет постраничную разбивку, сортировку или собственно фильтры (т.е. включена любая из опций – is_sortable, is_filter или is_pagination).
Здесь был рассмотрен простейший случай, когда вся информация хранилась в одной таблице БД.
Разумеется, в реальной практике может понадобиться выводить данные из нескольких связанных таблиц. Что и нужно сделать обычным путём, описанном, прежде всего, в главе «Модель». Более того, вы можете сделать таблицу, которая выводит данные не из Базы Данных, или из БД с другого сайта и т.п. Важно лишь, чтобы в строке:
$template->renderGridRowsJSON($grid, $data, $total, $pages);
структура таблицы в параметре $grid
соответствовала структуре массива $data
. Тогда всё будет корректно.
Вернуться к оглавлению раздела "Контроллеры"