Всем привет! Меня зовут Илья, я разработчик в Битрикс24. В последнее время наша команда стремится быть прозрачнее и делиться изменениями в продукте. Мы хотим, чтобы разработчики, использующие Битрикс24, быстрее узнавали об обновлениях и имели на руках актуальную документацию. Это поможет меньше велосипедить и искать решения на стороне.

Знаю, раньше и Битрикс, и его блоги собирали много хейта. Да что говорить, я сам одно время работал на партнеров и не всем был доволен. Даже писал критические посты. Однако всё это время продукт не стоял на месте, Битрикс24 времен 2014 и сейчас — две разных вселенных. До сих пор не всё идеально, но постоянно происходят улучшения. 

Об одном из них, простом и полезном, расскажу сегодня. Ранее у нас не было хорошо задокументированного коробочного решения по гридам. Если стояла задача вывести в публичной части информацию в виде таблиц, мы вручную пилили шаблоны для элементов и искали костыли для сортировки данных. Проблема возникала часто: например, если нужно было вывести список товаров, сделок или клиентов, а еще лучше — интерактивные списки.

Впереди мало слов и много кода. Если останутся вопросы или замечания, жду вас в комментах.

Как выглядят гриды в Битрикс24

Гриды — элементы интерфейса в Битрикс24, отображаемые как списки. Это может быть список складских документов, перечень сделок, список кандидатов для отбора персонала и многое другое.

Мы уже используем встроенные гриды во внутренней разработке: это проще и эстетичнее, чем сторонние решения. Вот как выглядит стандартный грид:

Список компаний из CRM
Список компаний из CRM

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

Пошаговый гайд по гридам: от простого к сложному

Покажу на примере, как шаг за шагом создавать и совершенствовать грид, накидывая функционал. Возьмем в качестве иллюстрации список сотрудников.

Шаг 1. Создаем класс грида

На этом шаге выполняем минимальные действия по подключению к API Битрикс24 и выводу грида. Для пользователей существует таблет UserTable, грид будем делать на его основе. Первым делом создаем класс самого грида:

<?php

use Bitrix\Main\Grid\TabletGrid;
use Bitrix\Main\UserTable;

final class UserGrid extends TabletGrid
{
	protected function getTabletClass(): string
	{
    	return UserTable::class;
	}
}

Шаг 2. Выводим грид

Просто так вывалить код в статический файлик из архитектурных соображений нельзя. Добавим кастомный компонент user.grid со следующим содержимым:

<?php

use Bitrix\Main\Grid\Component\GridComponent;
use Bitrix\Main\Grid\Grid;
use Bitrix\Main\Grid\Settings;
use Bitrix\Main\UserTable;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
	die();
}

final class UserGridComponent extends TabletGridComponent
{
	protected function createGrid(): Grid
	{
    	$settings = new Settings([
        		'ID' => 'user-grid',
    	]);

    	$grid = new UserGrid($settings);

    	return $grid;
	}

    protected function getTablet(): string
	{
    		return UserTable::class;
	}
}

Сам шаблон компонента (templates/.default/template.php) будет выглядеть так:

<?php

use Bitrix\Main\Grid\Component\ComponentParams;

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
	die();
}

/**
 * @var array $arParams
 * @var array $arResult
 * @var CMain $APPLICATION
 * @var CBitrixComponent $component
 * @var CBitrixComponentTemplate $this
 */

$APPLICATION->IncludeComponent(
	'bitrix:main.ui.grid',
	'',
	ComponentParams::get(
    	$arResult['GRID']
	)
);

Размещаем компонент на нужной странице и любуемся результатом:

Шаг 3. Работаем со списком отображаемых столбцов

По умолчанию выводятся все не приватные и скалярные поля таблета. За это отвечает провайдер TabletColumnsProvider, создающий список столбцов. Если нужно его изменить, вы можете ограничить список отображаемых столбцов. Создаем класс UserColumns, который будет отвечать за формирование столбцов:

<?php

use Bitrix\Main\Grid\Column\Columns;
use Bitrix\Main\Grid\Column\DataProvider\TabletColumnsProvider;
use Bitrix\Main\Grid\Column\DataProvider\UfColumnsProvider;
use Bitrix\Main\ORM\Entity;

final class UserColumns extends Columns
{
	public function __construct(Entity $entity)
	{
    	parent::__construct(
        	new TabletColumnsProvider(
            	$entity,
            	[
                	'ID',
                	'ACTIVE',
                	'LOGIN',
                	'EMAIL',
                	'NAME',
                	'SECOND_NAME',
                	'LAST_NAME',
                	'DATE_REGISTER',
                	'LAST_LOGIN',
            	],
        	),
    	);
	}
}

Давайте в классе грида переопределим метод createColumns, отвечающий за создание столбцов:

<?php

use Bitrix\Main\Grid\TabletGrid;

final class UserGrid extends TabletGrid
{
	// ...

	protected function createColumns(): Columns
	{
    	return new UserColumns(
        	$this->getEntity()
    	);
	}
}

В данном случае используется базовый класс Columns. В качестве аргументов конструктор этого класса принимает список провайдеров, формирующих список столбцов. По умолчанию провайдер TabletColumnsProvider выводит все поля таблета, чтобы это изменить, достаточно передать вторым аргументом массив со списком полей.

Шаг 4. Управляем отображением грида

Виджет грида позволяет управлять отображением гридов по умолчанию. Если, например, мы не хотим по умолчанию показывать столбцы с датой последнего входа, ID и отчеством, нужно перечислить остальные поля третьим параметром у провайдера TabletColumnsProvider:

<?php

use Bitrix\Main\Grid\Column\Columns;
use Bitrix\Main\Grid\Column\DataProvider\TabletColumnsProvider;
use Bitrix\Main\Grid\Column\DataProvider\UfColumnsProvider;
use Bitrix\Main\ORM\Entity;

final class UserColumns extends Columns
{
	public function __construct(Entity $entity)
	{
    	parent::__construct(
        	new TabletColumnsProvider(
            	$entity,
            	selectFields: [
                	'ID',
                	'ACTIVE',
                	'LOGIN',
                	'EMAIL',
                	'NAME',
                	'SECOND_NAME',
                	'LAST_NAME',
                	'DATE_REGISTER',
                	'LAST_LOGIN',
            	],
            	defaultFields: [
                	'ACTIVE',
                	'LOGIN',
                	'EMAIL',
                	'NAME',
                	'LAST_NAME',
                	'DATE_REGISTER',
            	],
        	),
    	);
	}
}

При отображении грида будут показаны только столбцы из аргумента defaultFields, но через шестеренку мы сможем при желании также выбрать и настроить другие столбцы:

Если нужно скрыть все столбцы таблета, можно использовать третий параметр isDefaultShow:

<?php

use Bitrix\Main\Grid\Column\Columns;
use Bitrix\Main\Grid\Column\DataProvider\TabletColumnsProvider;
use Bitrix\Main\ORM\Entity;

final class UserColumns extends Columns
{
	public function __construct(Entity $entity)
	{
    	parent::__construct(
        	new TabletColumnsProvider(
            	$entity,
            	selectFields: [
                	'ID',
                	'ACTIVE',
                	'LOGIN',
                	'EMAIL',
                	'NAME',
                	'SECOND_NAME',
                	'LAST_NAME',
                	'DATE_REGISTER',
                	'LAST_LOGIN',
            	],
            	isDefaultShow: false,
        	),
    	);
	}
}

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

Шаг 5. Привязываем пользовательские поля и работаем с ассемблерами

Помимо обычных полей, к таблетам (и множеству других сущностей) можно привязывать «пользовательские поля». Дополним грид такими полями, добавив к коллекции столбцов провайдер UfColumnsProvider:

<?php

use Bitrix\Main\Grid\Column\Columns;
use Bitrix\Main\Grid\Column\DataProvider\TabletColumnsProvider;
use Bitrix\Main\Grid\Column\DataProvider\UfColumnsProvider;
use Bitrix\Main\ORM\Entity;

final class UserColumns extends Columns
{
	public function __construct(Entity $entity)
	{
    	parent::__construct(
        	new TabletColumnsProvider(
            	$this->getEntity()
        	),
        	new UfColumnsProvider(
            	$this->getEntity()->getUfId(),
        	),
    	);
	}
}

Так в гриде появятся все пользовательские поля, настроенные для отображения в списке. Обратите внимание, что у них в настройках не должно стоять галочки «Не показывать в списке»:

С точки зрения настроек провайдер UfColumnsProvider поддерживает те же аргументы, что и TabletColumnsProvider: selectFields, defaultFields и isDefaultShow.

Коллекция столбцов Columns отвечает только за формирование списка столбцов. За отображение отвечают классы-сборщики RowAssembler и FieldAssembler. Задача этих классов заключается в сборке сырых данных и трансформации в пригодный для вывода вид.

ВАЖНО: сборщики — это необязательный элемент грида, если стандартные типы позволяют выводить информацию корректно.

Шаг 6. Добавляем UF

В примере с гридом сотрудников можно заметить, что поле «Подразделения» выводит не список подразделений, а строку Array. Чтобы UF поля выводились правильно, нужно добавить сборщик UfFieldAssembler. По аналогии со столбцами добавим класс UserRows и укажем нужный сборщик:

<?php

use Bitrix\Main\Grid\Row\Assembler\OnlyFieldsRowAssembler;
use Bitrix\Main\Grid\Row\Assembler\Field\UfFieldAssembler;
use Bitrix\Main\Grid\Row\Rows;
use Bitrix\Main\ORM\Entity;

final class UserRows extends Rows
{
	public function __construct(array $visibleColumnIds, Entity $entity)
	{
		$rowAssembler = new OnlyFieldsRowAssembler(
			$visibleColumnIds,
			new UfFieldAssembler(
				$entity->getUfId(),
			),
		);

		parent::__construct($rowAssembler);
	}
}

Затем используем созданный класс в гриде:

<?php

use Bitrix\Main\Grid\Row\Rows;
use Bitrix\Main\Grid\TabletGrid;

final class UserGrid extends TabletGrid
{
	// ...

	protected function createRows(): Rows
	{
		return new UserRows(
			$this->getVisibleColumnsIds(),
			$this->getEntity()
		);
	}
}

Важно обратить внимание на использование метода getVisibleColumnsIds. Для оптимальной работы сборщиков необходимо передавать только отображаемые столбцы и не подготавливать вывод для тех столбцов, которые выводить необязательно. Так мы получим корректное отображение всех используемых UF полей:

Из коробки у нас также имеются следующие сборщики:

  1. HtmlFieldAssembler.

  2. UserFieldAssembler.

  3. StringFieldAssembler.

  4. NumberFieldAssembler.

  5. ListFieldAssembler.

  6. UfFieldAssembler.

Шаг 7. Идем к контекстным действиям

Ок, у нас есть строчка по сотруднику, а если мы хотим добавить к ней конкретные типовые действия внутри? Допустим, уволить сотрудника или принять обратно. Добавим два асимметричных действия, чтобы по клику выводилась либо скрывалась нужная информация.

Поговорим про контекстные действия — они отображаются возле строки в «гамбургере». За их работу отвечает интерфейс и класс, который содержит в себе типовой код формирования кнопки в меню. Для сотрудников мы можем реализовать два противоположных действия: «уволить» и «принять обратно». Реализуем оба действия (пока без логики обработки):

<?php

use Bitrix\Main\Grid\Row\Action\BaseAction;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;

final class HireAction extends BaseAction
{
	public static function getId(): ?string
	{
		return 'hire';
	}

	protected function getText(): string
	{
		return 'принять обратно';
	}

	public function processRequest(HttpRequest $request): ?Result
	{
		return null;
	}
}

final class FireAction extends BaseAction
{
	public static function getId(): ?string
	{
		return 'fire';
	}

	protected function getText(): string
	{
		return 'уволить';
	}

	public function processRequest(HttpRequest $request): ?Result
	{
		return null;
	}
}

Далее добавим провайдер наших действий:

<?php

use Bitrix\Main\Grid\Row\Action\DataProvider;

final class UserContextActionDataProvider extends DataProvider
{
	public function prepareActions(): array
	{
		return [
			new FireAction(),
			new HireAction(),
		];
	}
}

Используем провайдер в уже созданном классе строк UserRows:

<?php

use Bitrix\Main\Grid\Row\Assembler\DynamicRowAssembler;
use Bitrix\Main\Grid\Row\Assembler\Field\UfFieldAssembler;
use Bitrix\Main\Grid\Row\Rows;
use Bitrix\Main\ORM\Entity;

final class UserRows extends Rows
{
	public function __construct(array $visibleColumnIds, Entity $entity)
	{
		$rowAssembler = new DynamicRowAssembler(
			$visibleColumnIds,
			new UfFieldAssembler(
				$entity->getUfId(),
			),
		);

		parent::__construct(
			$rowAssembler,
			new UserContextActionDataProvider(),
		);
	}
}

Мы видим, что возле строк появился виджет «гамбургера», нажав на который можно увидеть оба действия:

Логично предположить, что отображать обе кнопки одновременно — бессмысленно. Давайте доработаем код так, чтобы отображалась одна из кнопок, в зависимости от текущего статуса сотрудника. Для этого переопределим метод getControl у обоих действий, который отвечает за вывод контрола. Если данный метод вернёт null, в меню ничего не будет отображаться:

<?php

final class HireAction extends BaseAction
{
	// ...

	public function getControl(array $rawFields): ?array
	{
		if ($rawFields['ACTIVE'] === 'Y')
		{
			return null;
		}

		return parent::getControl($rawFields);
	}
}

Наблюдательный читатель наверняка уже заметил проблему в коде: если столбец ACTIVE не будет отображаться (скрыт настройками), то также он не будет содержаться в аргументе $rawFields. Чтобы этого не происходило, нужно отметить столбец ACTIVE обязательным. Тогда он будет выбираться из базы данных всегда, независимо от того, отображается он сейчас или нет. Добьемся этого, добавив метод UserColumns::prepareColumns с таким содержимым:

<?php

final class UserColumns extends Columns
{
	// ...

	protected function prepareColumns(array $columns): array
	{
		foreach ($columns as $column)
		{
			if ($column->getId() === 'ACTIVE')
			{
				$column->setNecessary(true);
			}
		}

		return $columns;
	}
}

Наконец, реализуем логику для обоих действий в два этапа:

  1. создаём JS-обработчик нажатия на кнопку для фронтенда;

  2. создаём PHP-обработчик для бэкенде.

В подавляющем большинстве случаев для реализации JS-обработчика достаточно использовать класс SendRowActionOnclick. Он первым аргументом принимает само действие, а вторым — полезную нагрузку, которая отправится на бэкенд. Остальные случаи — это сложная логика, которая обрабатывается на фронтенде. Ей, возможно, и бэкенд-обработчик не нужен. За логику отвечает свойство onclick:

<?php

use Bitrix\Main\Grid\Row\Action\BaseAction;
use Bitrix\Main\Grid\Row\Action\Control\SendRowActionOnclick;

final class HireAction extends BaseAction
{
	// ...

	public function getControl(array $rawFields): ?array
	{
		if ($rawFields['ACTIVE'] === 'Y')
		{
			return null;
		}

		$this->onclick = new SendRowActionOnclick($this, [
			'id' => $rawFields['ID'],
		]);

		return parent::getControl($rawFields);
	}
}

Обработчик на бэкенде должен принять эти данные и обработать по своей логике. За это отвечает метод processRequest:

<?php

use Bitrix\Main\Error;
use Bitrix\Main\Grid\Row\Action\BaseAction;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;

final class HireAction extends BaseAction
{
	// ...

	public function processRequest(HttpRequest $request): ?Result
	{
		$result = null;

		$userId = (int)$request->get('id');
		if ($userId > 0)
		{
			$user = new CUser();
			$user->Update($userId, [
				'ACTIVE' => 'Y',
			]);
			if ($user->LAST_ERROR)
			{
				$result = new Result();
				$result->addError(
					new Error($user->LAST_ERROR)
				);
			}
		}

		return $result;
	}
}

Опять же, обращаем внимание на использование $rawFields['ID'] и не забываем сделать его обязательным:

<?php

final class UserColumns extends Columns
{
	// ...

	protected function prepareColumns(array $columns): array
	{
		$necessaryColumns = [
			'ID',
			'ACTIVE',
		];

		foreach ($columns as $column)
		{
			if (in_array($column->getId(), $necessaryColumns))
			{
				$column->setNecessary(true);
			}
		}

		return $columns;
	}
}

В случае, если обработчик возвращает объект результата, то на фронтенд будет отправлены данные из него (актуально для кастомных обработчиков), либо отобразится поп-ап с ошибками (актуально для базовых).

Шаг 8. Прикручиваем групповые действия с несколькими элементами

Это актуально, когда мы хотим выделить несколько элементов и применить  общее действие (в нашем случае это — массовый найм сотрудников или увольнение). Тут тоже есть тонкости. 

Помимо действий в конкретной строке можно также добавить и действия в нижнюю панель грида, так называемые групповые действия. За работу с ними в панели отвечает класс Bitrix\Main\Grid\Panel\Panel. Механика аналогична действиям в строке, сначала необходимо добавить класс-действие. Но есть нюансы, о которых сейчас расскажу :)

Редактирование

Начнем с типового, но необычного действия "Редактирование". Необычное оно потому, что это встроенное действие грида, и нам практически не нужно писать код.

Создадим класс-действие (как обычно, без обработчика):

<?php

use Bitrix\Main\Filter\Filter;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;

final class EditAction extends \Bitrix\Main\Grid\Panel\Action\EditAction
{
	public function processRequest(HttpRequest $request, bool $isSelectedAllRows, ?Filter $filter): ?Result
	{
		return null;
	}
}

Создаем провайдер:

<?php

use Bitrix\Main\Grid\Panel\Action\DataProvider;

final class UserPanelProvider extends DataProvider
{
	public function prepareActions(): array
	{
		return [
			new EditAction(),
		];
	}
}

И, наконец, сделаем сам объект панели грида:

<?php

use Bitrix\Main\Grid\Panel\Panel;

final class UserPanel extends Panel
{
	public function __construct()
	{
		parent::__construct(
			new UserPanelProvider(),
		);
	}
}

Затем добавляем панель в грид:

<?php

use Bitrix\Main\Grid\Panel\Panel;
use Bitrix\Main\Grid\TabletGrid;

final class UserGrid extends TabletGrid
{
	// ...

	protected function createPanel(): ?Panel
	{
		return new UserPanel();
	}
}

Можно заметить, что возле каждой строки появились галочки, а внизу грида панель:

Можно выделить несколько строк, нажать кнопку «редактировать» и увидеть, что грид перестроится под редактирование:

В этом особенность такого действия. Оно встроенное, так что не нужно озадачиваться его отображением или JS логикой, нужно лишь реализовать его обработку на бэкенде:

<?php

use Bitrix\Main\Error;
use Bitrix\Main\Filter\Filter;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;

final class EditAction extends \Bitrix\Main\Grid\Panel\Action\EditAction
{
	public function processRequest(HttpRequest $request, bool $isSelectedAllRows, ?Filter $filter): ?Result
	{
		$result = new Result();

		$rows = $this->getRequestRows($request);
		foreach ($rows as $row)
		{
			$id = $row['ID'];
			unset($row['ID']);

			$user = new CUser();
			$user->Update($id, $row);
			if ($user->LAST_ERROR)
			{
				$result->addError(
					new Error($user->LAST_ERROR)
				);
			}
		}

		return $result;
	}
}

Можно заметить, что не используются аргументы isSelectedAllRows и filter, но это особенность данного действия. Нельзя абстрактно отредактировать все записи. Далее мы рассмотрим примеры работы с этими аргументами.

Аналогичное действие, доступное из коробки, — это удаление. Для него также существует базовый класс Bitrix\Main\Grid\Panel\Action\RemoveAction, а остальное происходит идентично.

Увольнение/прием на работу

Теперь добавим кастомное действие, которого нет в коробке грида. Это снова будут увольнение и приём на работу. Мы не будет делать отдельную кнопку, а используем селектор для выбора действия. Нужно добавить сразу два. Но прежде начнем с добавления класс-наследника Bitrix\Main\Grid\Panel\Action\Group\GroupChildAction. Он будет выступать элементом групповых действий:

<?php

use Bitrix\Main\Filter\Filter;
use Bitrix\Main\Grid\Panel\Action\Group\GroupChildAction;
use Bitrix\Main\Grid\Panel\Snippet\Onchange;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;

final class FireAction extends GroupChildAction
{
	public static function getId(): string
	{
		return 'fire';
	}

	public function getName(): string
	{
		return 'уволить';
	}

	public function processRequest(HttpRequest $request, bool $isSelectedAllRows, ?Filter $filter): ?Result
	{
		return null;
	}

	protected function getOnchange(): Onchange
	{
		return new Onchange();
	}
}

Замечу, что мы пока что добавили класс без реализации логики обработки запроса, а также с заглушкой метода getOnchange. Об этом поговорим далее. Давайте добавим сам элемент, отвечающий за групповое действие. В данном случае просто наследуемся от базового класса \Bitrix\Main\Grid\Panel\Action\GroupAction и перечисляем доступные действия:

<?php

final class GroupAction extends \Bitrix\Main\Grid\Panel\Action\GroupAction
{
	protected function prepareChildItems(): array
	{
		return [
			new HireAction(),
			new FireAction(),
		];
	}
}

Добавляем полученный класс в провайдер панели:

<?php

use Bitrix\Main\Grid\Panel\Action\DataProvider;

final class UserPanelProvider extends DataProvider
{
	public function prepareActions(): array
	{
		return [
			new EditAction(),
			new GroupAction(),
		];
	}
}

Вжух, в нижней панели грида у нас появился селектор с действиями:

Теперь поговорим о логике работы селектора в момент, когда мы выбираем элемент списка. При его выборе срабатывает событие onchange. Оно выполняет действия, указанные в методе getOnchange для класса нашего действия:

<?php

use Bitrix\Main\Grid\Panel\Action\Group\GroupChildAction;
use Bitrix\Main\Grid\Panel\Actions;
use Bitrix\Main\Grid\Panel\Snippet;
use Bitrix\Main\Grid\Panel\Snippet\Onchange;

class FireAction extends GroupChildAction
{
	// ...

	final protected function getOnchange(): Onchange
	{
    	return new Onchange([
        	// сбрасываем панель от данных других кнопок
        	[
            	'ACTION' => Actions::RESET_CONTROLS,
        	],
        	// создаем кнопку
        	[
            	'ACTION' => Actions::CREATE,
            	'DATA' => [
                	// добавляем кнопку, которая выполняет JS код, который отправляет выбранные строки грида
                	(new Snippet)->getApplyButton([
                    	'ONCHANGE' => [
                        	[
                            	'ACTION' => Actions::CALLBACK,
                            	'DATA' => [
                                	[
                                    	'JS' => 'CurrentGrid.sendSelected()',
                                	]
                            	],
                        	],
                    	],
                	]),
            	],
        	],
    	]);
	}
}

Далее добавляем обработчик запроса в действии:

<?php

use Bitrix\Main\Error;
use Bitrix\Main\Filter\Filter;
use Bitrix\Main\Grid\Panel\Action\Group\GroupChildAction;
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Result;
use Bitrix\Main\UserTable;
use CUser;

final class FireAction extends GroupChildAction
{
	// ...

	public function processRequest(HttpRequest $request, bool $isSelectedAllRows, ?Filter $filter): ?Result
	{
		$result = new Result();

		if ($isSelectedAllRows)
		{
			$filter = $filter?->getValue() ?? [];
		}
		else
		{
			$filter = [
				'@ID' => $this->getRequestRows($request),
			];
		}

		$rows = UserTable::getList([
			'select' => [
				'ID',
			],
			'filter' => $filter,
		]);
		foreach ($rows as $row)
		{
			$user = new CUser();
			$user->Update($row['ID'], [
				'ACTIVE' => 'N',
			]);
			if ($user->LAST_ERROR)
			{
				$result->addError(
					new Error($user->LAST_ERROR)
				);
			}
		}

		return $result;
	}
}

При передаче аргумента $isSelectedAllRows важно обязательно использовать фильтр, если он передан. Если такой галочки не стоит, то можно воспользоваться методом базового класса Bitrix\Main\Grid\Panel\Action\Group\GroupChildAction::getRequestRows. Он вытащит из запроса выбранные строки.

Работа с результатом аналогична всем другим обработчикам: если вы передали ошибки, они будут отражены в интерфейсе.

Шаг 9. Добавляем фильтр

Грид можно использовать с фильтрами и без. В данном случае это отдельная сущность. Когда мы разобрались с отображением самого грида, можем приступать к отображению фильтра. Механика практически та же самая.  

Пока добавим класс для фильтра с провайдером полей на базе таблета:

<?php

use Bitrix\Main\Filter\Filter;
use Bitrix\Main\Filter\TabletDataProvider;
use Bitrix\Main\ORM\Entity;

final class UserFilter extends Filter
{
	public function __construct(string $gridId, Entity $entity)
	{
		parent::__construct(
			$gridId,
			new TabletDataProvider(
				new Settings([
					'ID' => $gridId,
				]),
				$entity,
				selectFields: [
					'ID',
					'ACTIVE',
					'LOGIN',
					'EMAIL',
					'NAME',
					'SECOND_NAME',
					'LAST_NAME',
					'DATE_REGISTER',
					'LAST_LOGIN',
				],
				defaultFields: [
					'ID',
					'ACTIVE',
					'LOGIN',
					'EMAIL',
					'NAME',
					'LAST_NAME',
					'DATE_REGISTER',
				],
			)
		);
	}
}

Обратите внимание, что TabletDataProvider идентичен по аргументам с TabletColumnProvider, и можно сразу указать поля, которые участвуют в фильтре. Первым параметром обязательно передаются настройки фильтра. Создадим фильтр в классе грида:

<?php

use Bitrix\Main\Filter\Filter;
use Bitrix\Main\Grid\TabletGrid;

final class UserGrid extends TabletGrid
{
	// ...

	protected function createFilter(): Filter
	{
		return new UserFilter(
			$this->getId(),
			$this->getEntity(),
		);
	}
}

Фильтр — это отдельный виджет, и он связывается с гридом исключительно по ID. Мы передадим ID грида в конструктор и добавим отображение фильтра в шаблоне компонента. Код шаблона компонента будет выглядеть так:

<?php

if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
	die();
}

/**
 * @var array $arParams
 * @var array $arResult
 * @var CMain $APPLICATION
 * @var CBitrixComponent $component
 * @var CBitrixComponentTemplate $this
 */

if (isset($arResult['~FILTER']))
{
	$componentParams = \Bitrix\Main\Filter\Component\ComponentParams::get(
		$arResult['~FILTER']
	);
	if (\Bitrix\Main\Loader::includeModule('intranet'))
	{
		// Если мы делаем разработку для корпоративных порталов, то добавить фильтр можно через специальный фасад
		\Bitrix\UI\Toolbar\Facade\Toolbar::addFilter($componentParams);
	}
	else
	{
		$APPLICATION->IncludeComponent(
			'bitrix:main.ui.filter',
			'',
			$componentParams,
		);
	}
}

$APPLICATION->IncludeComponent(
	'bitrix:main.ui.grid',
	'',
	\Bitrix\Main\Grid\Component\ComponentParams::get(
		$arResult['~GRID']
	)
);

Смотрим на страницу и видим, что вверху над гридом появился фильтр:

Итак, у нас получился простой работающий грид, и его можно встроить в интерфейс и использовать. В следующий раз я подробно расскажу о том, как сделать полную кастомизацию грида в Битрикс24. 

Работали с гридами ранее? Как сейчас их реализуете? 

Если вы регулярно работаете с Битрикс24, как разработчик, для нашей команды будет ценной обратная связь. Поделитесь, подходит ли формат подобных статей, что хочется в него добавить, или каких обновлений в продукте вам не хватает.

Описанный функционал выйдет совсем скоро в свежем апдейте модуля main, не забудьте обновиться ;-)

Комментарии (0)