Документация для разработчиков
Темная тема

Создание списка элементов

Собранный полный пример административной страницы со списком можно увидеть здесь.

Принципы работы

Можно выделить следующий функционал, как правило, требуемый от административной страницы со списком элементов:

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

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

Разберем создание административной страницы, содержащей список элементов. В качестве примера будем использовать административную страницу модуля "Подписка, рассылки".

Начало

Прежде чем приступить к разбору функционала страницы, проведем "предварительную подготовку". Для начала создадим файл /bitrix/modules/subscribe/rubric_admin.php следующей структуры:

<?php
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;

// подключим все необходимые файлы:
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_before.php"); // первый общий пролог

Loader::includeModule('subscribe'); // инициализация модуля
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/subscribe/prolog.php"); // пролог модуля

// явного подключения языкового файла не требуется

// получим права доступа текущего пользователя на модуль
$POST_RIGHT = $APPLICATION->GetGroupRight("subscribe");
// если нет прав - отправим к форме авторизации с сообщением об ошибке
if ($POST_RIGHT == "D")
	$APPLICATION->AuthForm(Loc::getMessage("ACCESS_DENIED"));

// здесь будет вся серверная обработка и подготовка данных
<?php
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php"); // второй общий пролог // здесь будет вывод страницы <?php require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin.php");

Это и есть "костяк страницы", обозначенный на схеме.

Далее, создадим языковой файл, например, /bitrix/modules/subscribe/lang/ru/rubric_admin.php для русского языка. В файле будем определять все языковые сообщения в виде элементов массива: $MESS['идентификатор_сообщения'] = "текст_сообщения";

Затем, поскольку с изначальными параметрами установки БУС каталог /bitrix/modules/ недоступен посредством HTTP-протокола, создадим файл /bitrix/admin/rubric_admin.php:

<?php 
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/subscribe/admin/rubric_admin.php");
;

Теперь наш файл доступен по ссылке http://mysite.ru/bitrix/admin/rubric_admin.php.

Функционал страницы

Как видно из схемы, весь функционал страницы условно можно разделить на несколько составных частей:

  • Обработка и подготовка данных
    • Создание экземпляров необходимых для работы классов
    • Обработка действий над элементами списка
    • Конфигурация и обработка фильтра списка элементов
    • Выборка из БД (если требуется) и подготовка к выводу элементов списка
    • Задание параметров таблицы элементов (в т.ч., сортировка)
  • Вывод данных
    • Конфигурация и вывод административного меню
    • Вывод фильтра
    • Вывод списка

В процессе работы нам потребуются экземпляры следующих классов:

Класс Описание
CAdminList Основной класс для работы со списками элементов в административной панели.
CAdminResult Расширение класса CDBResult, предназначенное для работы со списками элементов в скриптах административных страниц.
CAdminSorting Работа с сортировками списка.
CAdminFilter Работа с фильтром списка.

Обработка и подготовка данных

Для начала, создадим экземпляры классов CAdminList и CAdminSorting:

$sTableID = "tbl_rubric"; // ID таблицы
$oSort = new CAdminSorting($sTableID, "ID", "desc"); // объект сортировки
$lAdmin = new CAdminList($sTableID, $oSort); // основной объект списка
$by = mb_strtoupper($lAdmin->getField());
$order = mb_strtoupper($lAdmin->getOrder());

Фильтр результатов

В нашем примере фильтр результатов будет состоять из следующих параметров:

  • Поиск по идентификатору рассылки
  • Выборка по активности рассылки
  • Выборка по публикации рассылки
  • Выборка активности рассылки

Предварительная работа с фильтром осуществляется следующим образом:

// проверку значений фильтра для удобства вынесем в отдельную функцию
function CheckFilter()
{
	global $FilterArr, $lAdmin;
	foreach ($FilterArr as $f) global $$f;
	
	/* 
		здесь проверяем значения переменных $find_имя и, в случае возникновения ошибки, 
		вызываем $lAdmin->AddFilterError("текст_ошибки"). 
	*/
	
	return count($lAdmin->arFilterErrors) == 0; // если ошибки есть, вернем false;
}
// опишем элементы фильтра
$FilterArr = Array(
	"find_id",
	"find_lid",
	"find_active",
	"find_visible",
	"find_auto",
	);
// инициализируем фильтр
$lAdmin->InitFilter($FilterArr);
// если все значения фильтра корректны, обработаем его
if (CheckFilter())
{
	// создадим массив фильтрации для выборки CRubric::GetList() на основе значений фильтра
	$arFilter = Array(
		"ID"		=> $find_id,
		"LID"		=> $find_lid,
		"ACTIVE"	=> $find_active,
		"VISIBLE"	=> $find_visible,
		"AUTO"		=> $find_auto,
	);
}

Обработка действий над элементами

Над элементами списка можно совершать два типа действий:

  • Редактирование элементов непосредственно на странице списка
  • Групповые и одиночные действия на элементами

Определить, что произведены действия над элементами списка можно проверив значения CAdminList::EditAction() и CAdminList::GroupAction(). Не забудьте добавить проверку прав в обоих вариантах (в данном случае - проверка наличия прав на запись для модуля подписки - $POST_RIGHT=="W").

Пример:

// сохранение отредактированных элементов
if($lAdmin->EditAction() && $POST_RIGHT=="W")
{
	// пройдем по списку переданных элементов
	foreach($lAdmin->GetEditFields() as $ID=>$arFields)
	{ 
		
		// сохраним изменения каждого элемента
		$DB->StartTransaction();
		$ID = IntVal($ID);
		$cData = new CRubric;
		if(($rsData = $cData->GetByID($ID)) && ($arData = $rsData->Fetch()))
		{
			foreach($arFields as $key=>$value)
				$arData[$key]=$value;
			if(!$cData->Update($ID, $arData))
			{
				$lAdmin->AddGroupError(Loc::getMessage("rub_save_error")." ".$cData->LAST_ERROR, $ID);
				$DB->Rollback();
			}
		}
		else
		{
			$lAdmin->AddGroupError(Loc::getMessage("rub_save_error")." ".Loc::getMessage("rub_no_rubric"), $ID);
			$DB->Rollback();
		}
		$DB->Commit();
	}
}
// обработка одиночных и групповых действий
if(($arID = $lAdmin->GroupAction()) && $POST_RIGHT=="W")
{
// если выбрано "Для всех элементов"
if ($lAdmin->IsGroupActionToAll())
	{
		$cData = new CRubric;
		$rsData = $cData->GetList(array($by=>$order), $arFilter);
		while($arRes = $rsData->Fetch())
			$arID[] = $arRes['ID'];
	}
$action = $lAdmin->GetAction();
// пройдем по списку элементов
	foreach($arID as $ID)
	{
		if(strlen($ID)<=0)
			continue;
		$ID = IntVal($ID);
        
		// для каждого элемента совершим требуемое действие
		switch($action)
		{
		// удаление
		case "delete":
			@set_time_limit(0);
			$DB->StartTransaction();
			if(!CRubric::Delete($ID))
			{
				$DB->Rollback();
				$lAdmin->AddGroupError(Loc::getMessage("rub_del_err"), $ID);
			}
			$DB->Commit();
			break;
		
		// активация/деактивация
		case "activate":
		case "deactivate":
			$cData = new CRubric;
			if(($rsData = $cData->GetByID($ID)) && ($arFields = $rsData->Fetch()))
			{
				$arFields["ACTIVE"]=($_REQUEST['action']=="activate"?"Y":"N");
				if(!$cData->Update($ID, $arFields))
					$lAdmin->AddGroupError(Loc::getMessage("rub_save_error").$cData->LAST_ERROR, $ID);
			}
			else
				$lAdmin->AddGroupError(Loc::getMessage("rub_save_error")." ".Loc::getMessage("rub_no_rubric"), $ID);
			break;
		}
	}
}

Выборка элементов

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

// выберем список рассылок
$cData = new CRubric;
$rsData = $cData->GetList(array($by=>$order), $arFilter);
// преобразуем список в экземпляр класса CAdminResult
$rsData = new CAdminResult($rsData, $sTableID);
// аналогично CDBResult инициализируем постраничную навигацию.
$rsData->NavStart();
// отправим вывод переключателя страниц в основной объект $lAdmin
$lAdmin->NavText($rsData->GetNavPrint(Loc::getMessage("rub_nav")));

Подготовка списка элементов к выводу

Для того, чтобы сформировать таблицу элементов, нужно, во-первых, сформировать массив заголовков таблицы, а, во-вторых, отправить список в основной объект $lAdmin. Первое осуществляется посредством вызова метода СAdminList::AddHeaders(), которому передается массив колонок таблицы. Каждая колонка описывается массивом, содержащим следующие ключи:

Ключ Описание
id Идентификатор колонки.
content Заголовок колонки.
sort Значение параметра GET-запроса для сортировки.
default Параметр, показывающий, будет ли колонка по умолчанию отображаться в списке (true|false)

Пример:

$lAdmin->AddHeaders(array(
	array(  "id"    =>"ID",
		"content"  =>"ID",
		"sort"     =>"id",
		"default"  =>true,
	),
	array(  "id"    =>"NAME",
		"content"  =>Loc::getMessage("rub_name"),
		"sort"     =>"name",
		"default"  =>true,
	),
	array(  "id"    =>"LID",
		"content"  =>Loc::getMessage("rub_site"),
		"sort"     =>"lid",
		"default"  =>true,
	),
	array(  "id"    =>"SORT",
		"content"  =>Loc::getMessage("rub_sort"),
		"sort"     =>"sort",
		"align"    =>"right",
		"default"  =>true,
	),
	array(  "id"    =>"ACTIVE",
		"content"  =>Loc::getMessage("rub_act"),
		"sort"     =>"act",
		"default"  =>true,
	),
	array(  "id"    =>"VISIBLE",
		"content"  =>Loc::getMessage("rub_visible"),
		"sort"     =>"visible",
		"default"  =>true,
	),
	array(  "id"    =>"AUTO",
		"content"  =>Loc::getMessage("rub_auto"),
		"sort"     =>"auto",
		"default"  =>true,
	),
	array(  "id"    =>"LAST_EXECUTED",
		"content"  =>Loc::getMessage("rub_last_exec"),
		"sort"     =>"last_executed",
		"default"  =>true,
	),
));

Передача списка элементов в основной объект осуществляется следующим образом:

  1. Вызываем CAdminList::AddRow(). Результат метода - ссылка на пустой экземпляр класса CAdminListRow
  2. Формируем поля строки, используя следующие методы класса CAdminListRow:
    • AddField - значение ячейки будет отображаться в заданном виде при просмотре и при редактировании списка
    • AddViewField - при просмотре списка значение ячейки будет отображаться в заданном виде
    • AddEditField - при редактировании списка значение ячейки будет отображаться в заданном виде
    • AddCheckField - значение ячейки будет редактироваться в виде чекбокса
    • AddSelectField - значение ячейки будет редактироваться в виде выпадающего списка
    • AddInputField - значение ячейки будет редактироваться в виде текстового поля с заданным набором атрибутов
    • AddCalendarField - значение ячейки будет редактироваться в виде поля для ввода даты
  3. Формируем контекстное меню для строки (CAdminListRow::AddActions())

При формировании полей строки можно комбинировать различные методы для одного и того же поля.

Контекстное меню элемента задается массивом, элементы которого представлюят собой ассоциативные массивы со следующим набором ключей:

Ключ Описание
ICON Имя CSS-класса с иконкой действия.
DISABLED Флаг "пункт меню заблокирован" (true|false).
DEFAULT Флаг "пункт меню является действием по умолчанию" (true|false). При двойном клике по строке сработает действие по умолчанию.
TEXT Название пункта меню.
TITLE Текст всплывающей подсказки пункта меню.
ACTION Действие, производимое по выбору пункта меню (Javascript).
SEPARATOR Вставка разделителя {true|false}. При значении, равном true, остальные ключи пункта меню будут проигнорированы.

Пример:

while($arRes = $rsData->NavNext(true, "f_")):
  
	// создаем строку. результат - экземпляр класса CAdminListRow
	$row =& $lAdmin->AddRow($f_ID, $arRes); 
  
	// далее настроим отображение значений при просмотре и редактировании списка
  
	// параметр NAME будет редактироваться как текст, а отображаться ссылкой
	$row->AddInputField("NAME", array("size"=>20));
	$row->AddViewField("NAME", ''.$f_NAME.'');
  
	// параметр LID будет редактироваться в виде выпадающего списка языков
	$row->AddEditField("LID", CLang::SelectBox("LID", $f_LID)); 
  
	// параметр SORT будет редактироваться текстом
	$row->AddInputField("SORT", array("size"=>20)); 
  
	// флаги ACTIVE и VISIBLE будут редактироваться чекбоксами
	$row->AddCheckField("ACTIVE"); 
	$row->AddCheckField("VISIBLE");
  
	// параметр AUTO будет отображаться в виде "Да" или "Нет", полужирным при редактировании
	$row->AddViewField("AUTO", $f_AUTO=="Y"?Loc::getMessage("POST_U_YES"):Loc::getMessage("POST_U_NO")); 
	$row->AddEditField("AUTO", "".($f_AUTO=="Y"?Loc::getMessage("POST_U_YES"):Loc::getMessage("POST_U_NO"))."");
	// сформируем контекстное меню
	$arActions = Array();
	// редактирование элемента
	$arActions[] = array(
		"ICON"=>"edit",
		"DEFAULT"=>true,
		"TEXT"=>Loc::getMessage("rub_edit"),
		"ACTION"=>$lAdmin->ActionRedirect("rubric_edit.php?ID=".$f_ID)
	);
  
	// удаление элемента
	if ($POST_RIGHT>="W")
		$arActions[] = array(
			"ICON"=>"delete",
			"TEXT"=>Loc::getMessage("rub_del"),
			"ACTION"=>"if(confirm('".Loc::getMessage('rub_del_conf')."')) ".$lAdmin->ActionDoGroup($f_ID, "delete")
		);
	// вставим разделитель
	$arActions[] = array("SEPARATOR"=>true);
	// проверка шаблона для автогенерируемых рассылок
	if (strlen($f_TEMPLATE)>0 && $f_AUTO=="Y")
		$arActions[] = array(
			"ICON"=>"",
			"TEXT"=>Loc::getMessage("rub_check"),
			"ACTION"=>$lAdmin->ActionRedirect("template_test.php?ID=".$f_ID)
		);
	// если последний элемент - разделитель, почистим мусор.
	if(is_set($arActions[count($arActions)-1], "SEPARATOR"))
		unset($arActions[count($arActions)-1]);
  
	// применим контекстное меню к строке
	$row->AddActions($arActions);
endwhile;

Для завершения подготовки таблицы остается добавить к ней "резюме" и настроить групповые операции над элементами.

Резюме таблицы формируется в виде массива, элементами которого являются ассоциативные массивы с ключами "title" - название параметра - и "value" - значение параметра. Кроме того, ассоциативный массив может содержать элемент с ключом "counter" и значением true. В этом случае, элемент резюме будет счетчиком отмеченных элементов таблицы и значение будет динамически изменяться. Прикрепляется резюме вызовом метода CAdminList::AddFooter().

Список возможных групповых операций задается в виде массива элементов вида "идентификатор_операции"=>"название_операции" и прикрепляется к таблице вызовом CAdminList::AddGroupActionTable(). Про обработку групповых действий см. выше.

Пример:

// резюме таблицы
$lAdmin->AddFooter(
	array(
		array("title"=>Loc::getMessage("MAIN_ADMIN_LIST_SELECTED"), "value"=>$rsData->SelectedRowsCount()), // кол-во элементов
		array("counter"=>true, "title"=>Loc::getMessage("MAIN_ADMIN_LIST_CHECKED"), "value"=>"0"), // счетчик выбранных элементов
	)
);
// групповые действия
$lAdmin->AddGroupActionTable(Array(
	"delete"=>Loc::getMessage("MAIN_ADMIN_LIST_DELETE"), // удалить выбранные элементы
	"activate"=>Loc::getMessage("MAIN_ADMIN_LIST_ACTIVATE"), // активировать выбранные элементы
	"deactivate"=>Loc::getMessage("MAIN_ADMIN_LIST_DEACTIVATE"), // деактивировать выбранные элементы
));

Задание параметров административного меню

Также можно задать административное меню, которое обычно отображается над таблицей со списком (только если у текущего пользователя есть права на редактирование). Административное формируется в виде массива, элементами которого являются ассоциативные массивы с ключами:

Ключ Описание
TEXT Текст пункта меню.
TITLE Текст всплывающей подсказки пункта меню.
LINK Ссылка на кнопке.
LINK_PARAM Дополнительные параметры ссылки (напрямую подставляются в тэг <A>).
ICON CSS-класс иконки действия.
HTML Задание пункта меню напрямую HTML-кодом.
SEPARATOR Разделитель между пунктами меню (true|false).
NEWBAR Новый блок элементов меню (true|false).
MENU Создание выпадающего подменю. Значение задается аналогично контекстному меню строки таблицы.

Прикрепляется контекстное меню вызовом CAdminList::AddAdminContextMenu().

Пример:

// сформируем меню из одного пункта - добавление рассылки
$aContext = array(
	array(
		"TEXT"=>Loc::getMessage("POST_ADD"),
		"LINK"=>"rubric_edit.php?lang=".LANG,
		"TITLE"=>Loc::getMessage("POST_ADD_TITLE"),
		"ICON"=>"btn_new",
	),
);
// и прикрепим его к списку
$lAdmin->AddAdminContextMenu($aContext);

Завершение подготовки

На этом подготовка списка завершена. Для того, чтобы обработать альтернтативные методы вывода списка (обновление посредством AJAX, вывод в формате Excel) вызовем метод CAdminList::CheckListMode():

// альтернативный вывод
$lAdmin->CheckListMode();

И в завершение непосредственно перед стандартным выводом, установим заголовок страницы:

// установим заголовок страницы
$APPLICATION->SetTitle(Loc::getMessage("rub_title"));

Вывод данных

Как видно на схеме, подготовку страницы и основной вывод необходимо разделить подключением административного файла prolog_admin_after.php:

// не забудем разделить подготовку данных и вывод
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php");

Выше было сказано, что вывод данных можно разбить на 2 части - вывод фильтра и вывод списка.

Вывод фильтра

Для вывода фильтра требуется сперва создать экземпляр класса CAdminFilter, передав ему список полей, а затем сформировать форму фильтра "вручную".

Пример:

// создадим объект фильтра
$oFilter = new CAdminFilter(
$sTableID."_filter",
	array(
		'find_id' => 'ID',
		'find_lid' => Loc::getMessage("rub_f_site"),
		'find_active' => Loc::getMessage("rub_f_active"),
		'find_visible' => Loc::getMessage("rub_f_public"),
		'find_auto' => Loc::getMessage("rub_f_auto"),
	)
);
<form name="find_form" method="get" action="<?echo $APPLICATION->GetCurPage();?>">
<?php
$oFilter->Begin();;
<tr>
	<td><?="ID":</td>
	<td>
		<input type="text" name="find_id" size="47" value="<?echo htmlspecialchars($find_id)?>">
	</td>
</tr>
<tr>
	<td><?=Loc::getMessage("rub_f_site").":"</td>
	<td><input type="text" name="find_lid" size="47" value="<?echo htmlspecialchars($find_lid)?>"></td>
</tr>
<tr>
	<td><?=Loc::getMessage("rub_f_active"):</td>
	<td>
		<?php
		$arr = array(
			"reference" => array(
				Loc::getMessage("POST_YES"),
				Loc::getMessage("POST_NO"),
			),
			"reference_id" => array(
				"Y",
				"N",
			)
		);
		echo SelectBoxFromArray("find_active", $arr, $find_active, Loc::getMessage("POST_ALL"), "");
	</td>
</tr>
<tr>
	<td><?=Loc::getMessage("rub_f_public"):</td>
	<td><?php
echo SelectBoxFromArray("find_visible", $arr, $find_visible, Loc::getMessage("POST_ALL"), "");</td>
</tr>
<tr>
	<td><?=Loc::getMessage("rub_f_auto"):</td>
	<td><?php
echo SelectBoxFromArray("find_auto", $arr, $find_auto, Loc::getMessage("POST_ALL"), "");</td>
</tr>
<?php
$oFilter->Buttons(array("table_id"=>$sTableID,"url"=>$APPLICATION->GetCurPage(),"form"=>"find_form"));
$oFilter->End();
</form>

Вывод таблицы

Ну и вполне логично, что после всех вышеприведенных подготовительных операций вывод списка осуществляется очень просто:

// выведем таблицу списка элементов
$lAdmin->DisplayList();

Собранный пример административной страницы со списком можно увидеть здесь.



Пользовательские комментарии

Мы будем рады, если разработчики добавят свои комментарии по практическому использованию методов системы.

Для этого нужно всего лишь авторизоваться на сайте

Но помните, что Пользовательские комментарии, несмотря на модерацию, не являются официальной документацией. Ответственность за их использование несет сам пользователь.

Также Пользовательские комментарии не являются местом для обсуждения функционала. По подобным вопросам обращайтесь на форумы.
0
Вячеслав Докукин
Сообщение не промодерировано, возможны ошибки и неточности.
Долго мучился с тем что форма фильтра не отправляла поля которые в ней были, оказалось причиной было то что в месте с блоком формирования полей фильтра
Код
/**
 * Create filter
 */
$oFilter = new CAdminFilter(
    $sTableID . "_filter",
    array(
        'find_date_from' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_DATE"),
        'find_id_from' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_ID"),
        'find_status' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_STATUS"),
        'find_entity' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_ENTITY"),
        'find_ext_code' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_EXT_CODE"),
        'find_name' => Loc::getMessage("T_ITSERW_CNMARKET_ADMIN_FILTER_FIELD_TITLE_NAME"),
    )
);


я вынес за пределы тега <form>
блок, из-за чего все поля внутри формы стали "мимо кассы" и не отправлялись ajax по клику кнопки "Найти"
Код
<? $oFilter->Begin(); ?>
6
Александр Кудин
Долго не мог решить проблему почему при перелистывании списка, или других действиях слетало верхнее контекстное меню

оказалось необходимо метод AddAdminContextMenu обязательно вызывать до вызова CheckListMode

$lAdmin->AddAdminContextMenu($aContext);
$lAdmin->CheckListMode();
© «Битрикс», 2001-2024, «1С-Битрикс», 2024
Наверх