44  /  330

Постраничная навигация

Просмотров: 7281 (Статистика ведётся с 06.02.2017)
Дата последнего изменения: 14.04.2016

Как реализовать постраничную навигацию в ядре D7 для выборок из ORM. (Доступно с версии 16.0.0)

Принципы

Постраничная навигация - вспомогательный объект пользовательского интерфейса. В старом ядре постраничка была в объекте результата запроса, а чтобы выбрать с лимитами, приходилось вызывать специальную функцию CDBResult::NavQuery(), в которую передавался текст запроса. В D7 постраничная навигация - это просто вспомогательный объект пользовательского интерфейса. Первичным считается запрос, а постраничка просто помогает подставить правильные limit и offset.

Обязательность использования limit в запросах. Некорректно оставлять на долю php всю потенциально большую выборку без ограничений. Если выборка все-таки без лимитов, то возможно использование новой навигации, но разработчик это делает на свой страх и риск, без рекомендаций вендора. При этом необходимо решать вопросы кроссбазности и другие задачи.


Для постраничной навигации нужно знать количество записей. Как и в старом ядре для этого необходимо сделать отдельный запрос с COUNT. На большом количестве записей это эффективнее полной выборки, а на маленьком - не имеет значения. Но если имеется не просто большая, а гигантская выборка, то постраничка на новом ядре вполне справится и без COUNT.

Инструменты для постраничной навигации

  • класс \Bitrix\Main\UI\PageNavigation для простой навигации;
  • класс \Bitrix\Main\UI\ReversePageNavigation для обратной навигации;
  • класс \Bitrix\Main\UI\AdminPageNavigation для навигации в админке;
  • компонент main.pagenavigation с шаблонами .default (основан на round старого компонента), admin (для админки), modern (для гридов).

Постраничка поддерживает как параметры в GET, так и ЧПУ. Вместо глобальной переменной $NavNum теперь нужно явно указывать идентификатор навигации. Поддерживаются URL следующего вида:

/page.php?nav-cars=page-5&nav-books=page-2&other=params
/page.php?nav-cars=page-5-size-20&nav-books=page-2
/page.php?nav-cars=page-all&nav-books=page-2
/dir/nav-cars/page-2/size-20/
/dir/nav-cars/page-all/?other=params
/dir/nav-cars/page-5/nav-books/page-2/size-10

где nav-cars и nav-books - идентификаторы двух разных постраничек на одной странице.

Постраничная навигация в административном разделе

$nav = new \Bitrix\Main\UI\AdminPageNavigation("nav-culture");

$cultureList = CultureTable::getList(array(
   'order' => array(strtoupper($by) => $order),
   'count_total' => true,
   'offset' => $nav->getOffset(),
   'limit' => $nav->getLimit(),
));

$nav->setRecordCount($cultureList->getCount());

$adminList->setNavigation($nav, Loc::getMessage("PAGES"));

while($culture = $cultureList->fetch())
{
}

'count_total' => true - новый параметр, который заставляет ORM выполнить отдельный запрос COUNT, результат которого оказывается в объекте результата выборки и может быть получен через $cultureList->getCount(). Без этого параметра выполнять getCount() бесполезно и даже вредно (получите исключение).

$nav->getOffset() возвращает позицию первой записи для текущей страницы навигации.
$nav->getLimit() возвращает количество записей на странице или 0, если выбрано "все записи".
$nav->setRecordCount() устанавливает количество записей для навигации.

Для удобства в административном отделе добавлена функция $adminList->setNavigation(), которая просто подключает компонент main.pagenavigation с шаблоном admin.

Прямая постраничная навигация в Публичном разделе

<?
$filter = array("=IBLOCK_ID"=>6);

$nav = new \Bitrix\Main\UI\PageNavigation("nav-more-news");
$nav->allowAllRecords(true)
   ->setPageSize(5)
   ->initFromUri();

$newsList = \Bitrix\Iblock\ElementTable::getList(
   array(
      "filter" => $filter,
      "count_total" => true,
      "offset" => $nav->getOffset(),
      "limit" => $nav->getLimit(),
   )
);

$nav->setRecordCount($newsList->getCount());

while($news = $newsList->fetch())
{
}
?>

<?
$APPLICATION->IncludeComponent(
   "bitrix:main.pagenavigation",
   "",
   array(
      "NAV_OBJECT" => $nav,
      "SEF_MODE" => "Y",
   ),
   false
);
?>

Добавилась инициализация навигации:

$nav->allowAllRecords(true) ->setPageSize(5) ->initFromUri();

В публичном разделе больше настроек типа "разрешить все записи", а это все нужно знать до того, как будет проинициализирован объект. (В административном разделе это уже переопределено классом-наследником.) Кроме того, необязательно получать текущую страницу из URL так как её можно взять где угодно и установить.

У компонента есть важный параметр "SEF_MODE" => "Y", по которому он генерирует ЧПУ постраничной навигации.

Обратная постраничная навигация в Публичном разделе

<?
$filter = array("=IBLOCK_ID"=>6);

$cnt = \Bitrix\Iblock\ElementTable::getCount($filter);

$nav = new \Bitrix\Main\UI\ReversePageNavigation("nav-news", $cnt);
$nav->allowAllRecords(true)
   ->setPageSize(5)
   ->initFromUri();

$newsList = \Bitrix\Iblock\ElementTable::getList(
   array(
      "filter" => $filter,
      "offset" => $nav->getOffset(),
      "limit" => $nav->getLimit(),
   )
);

while($news = $newsList->fetch())
{
}
?>

<?
$APPLICATION->IncludeComponent(
   "bitrix:main.pagenavigation",
   "",
   array(
      "NAV_OBJECT" => $nav,
      "SEF_MODE" => "Y",
   ),
   false
);
?>

Здесь конструируется класс-наследник ReversePageNavigation и обязательно передаётся ему количество записей уже в конструкторе, т.к. обратная навигация математически не может работать без количества записей. Для этого в ORM обновился метод getCount(), теперь он принимает на вход параметр фильтра.

Постраничная навигация в гриде

<?
$filter = array("=IBLOCK_ID"=>6);

$nav = new \Bitrix\Main\UI\PageNavigation("nav-grid-news");
$nav->allowAllRecords(true)
   ->setPageSize(5)
   ->initFromUri();

$newsList = \Bitrix\Iblock\ElementTable::getList(
   array(
      "filter" => $filter,
      "count_total" => true,
      "offset" => $nav->getOffset(),
      "limit" => $nav->getLimit(),
   )
);

$rows = array();
while($news = $newsList->fetch())
{
   $cols = array(
      "ID" => $news["ID"],
      "NAME" => $news["NAME"],
   );

   $rows[] = array(
      "columns"=>$cols,
   );
}

$nav->setRecordCount($newsList->getCount());
?>

<?
$APPLICATION->IncludeComponent(
   "bitrix:main.interface.grid",
   "",
   array(
      "GRID_ID"=>"news_grid",
      "HEADERS"=>array(
         array("id"=>"ID", "name"=>"ID", "default"=>true),
         array("id"=>"NAME", "name"=>"Название", "default"=>true),
      ),
      "ROWS"=>$rows,
      "FOOTER"=>array(array("title"=>"Всего", "value"=>$newsList->getCount())),
      "NAV_OBJECT"=>$nav,
      "NAV_PARAMS"=>array(
         "SEF_MODE" => "Y"
      ),
   )
);
?>

"NAV_PARAMS"=>array( "SEF_MODE" => "Y" ) - новый параметр, который по сути передаёт параметры в компонент навигации.

Постраничная навигация без COUNT

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

<?
$filter = array("=IBLOCK_ID"=>6);

$nav = new \Bitrix\Main\UI\PageNavigation("nav-less-news");
$nav->allowAllRecords(true)
   ->setPageSize(5)
   ->initFromUri();

$newsList = \Bitrix\Iblock\ElementTable::getList(
   array(
      "filter" => $filter,
      "offset" => ($offset = $nav->getOffset()),
      "limit" => (($limit = $nav->getLimit()) > 0? $limit + 1 : 0),
   )
);

$n = 0;
while($news = $newsList->fetch())
{
   $n++;
   if($limit > 0 && $n > $limit)
   {
      break;
   }
}

$nav->setRecordCount($offset + $n);
?>

<?
$APPLICATION->IncludeComponent(
   "bitrix:main.pagenavigation",
   "",
   array(
      "NAV_OBJECT" => $nav,
      "SEF_MODE" => "Y",
      "SHOW_COUNT" => "N",
   ),
   false
);
?>

Обратите внимание: выставляется "уже известное" количество записей: $nav->setRecordCount($offset + $n);

Это делается для того, чтобы компонент навигации понял, что есть еще страницы. Но для шаблонов, которые выводят количество записей, мы передаем "SHOW_COUNT" => "N", чтобы они не пытались выводить неверную цифру (она будет верна только на последней странице).

Внимание! Если осуществляется навигация без COUNT, то имеется в виду, что записей очень много. Поэтому не стоит разрешать все записи методом $nav->allowAllRecords(true).

Файл с примерами.

23
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии