Views: 4595
Last Modified: 30.03.2022

How to implement page navigation in core D7 for ORM selection (Available from version 16.0.0).

Basics

Page navigation is an auxiliary object for UI. For legacy kernel/core, page navigation was implemented in the query result object. Handling of limits required calling a special function CDBResult::NavQuery() to pass the query text. However, page navigation in D7 is just an auxiliary object for UI. Query is deemed as primary and page navigation just helps to substitute correct limit and offset.

Limit requirement in queries. It's incorrect to relegate php with all potential fetching without limitations. In case the retrieval is executed without limits, you have an option to use new navigation. This means that developer does this at his/her own risk, without vendor recommendation. And you'll need to solve issues arising from using different databases and other tasks.


For page navigation, you have to know the number of records. Just as in the old kernel/core, you need to execute a separate query with COUNT. It's more effective than retrieving a large chunk of data records. For small-scale fetching this issue is not as prevalent. However, if you are getting a voluminous batch of data indeed, current core page navigation will be able to handle it even without COUNT.

Page navigation tools

  • \Bitrix\Main\UI\PageNavigation class for standard navigation;
  • \Bitrix\Main\UI\ReversePageNavigation class for reverse navigation;
  • \Bitrix\Main\UI\AdminPageNavigation class for navigation in administration section;
  • main.pagenavigation components with .default templates (based at the old component's round), admin (section), modern (for grids).

Page navigation supports both parameters in GET and SEF. Now you need to directly indicate a navigation identifier instead of global variable $NavNum. The following URL are supported:

/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

where nav-cars and nav-books - identifiers for two different page navigations at a single page.

Page navigation in the administrative section

$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 - new parameter that forces ORM to execute an individual query COUNT, resulting in retrieved object and can be received via $cultureList->getCount(). Without this parameter there's no point to execute getCount() and event counterproductive (you'll get a thrown exception).

$nav->getOffset() returns first record position for the current navigation page.
$nav->getLimit() returns number of records at the page or 0, if "all records" are selected.
$nav->setRecordCount() sets number of records for navigation.

For convenience, administration section now has an added function $adminList->setNavigation() which just connects the component main.pagenavigation with an admin template.

Direct page navigation in Public section

<?
$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
);
?>

Now navigation initialization is added:

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

Public section now has more settings, such as "allow all records", and it's all needed before initializing the object (the admin section was already re-defined by descendant class). Additionally, getting current page from URL is optional, because it can be sourced and set from anywhere.

The component has one important parameter: "SEF_MODE" => "Y" used to generate SEF for page navigation.

Reverse page navigation in Public section

<?
$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
);
?>

The descendant class ReversePageNavigation is constructed with mandatory received number of records already in the constructor. This is done because the reverse navigation cannot mathematically operate without the number of records. The method getCount() was updated in ORM for this purpose: now it receives filter parameter on input.

Page navigation in UI grid

<?
$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"=>"Name", "default"=>true),
      ),
      "ROWS"=>$rows,
      "FOOTER"=>array(array("title"=>"Total", "value"=>$newsList->getCount())),
      "NAV_OBJECT"=>$nav,
      "NAV_PARAMS"=>array(
         "SEF_MODE" => "Y"
      ),
   )
);
?>

"NAV_PARAMS"=>array( "SEF_MODE" => "Y" ) - new parameter that passes new parameter values to navigation component.

Page navigation without COUNT

Only direct navigation is possible. General principle: developer manually pinpoints a little more records back and forth to determine, if you can show "more" records at the page. This helps developer to define the limit shown at the page.

<?
$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
);
?>

Attention: "already known" number of records is inserted: $nav->setRecordCount($offset + $n);

This is done for navigation component to understand that pages are still available. However, for templates that show number of records, you need to pass "SHOW_COUNT" => "N" to prevent attempts to display an incorrect number (this number will be correct only at the last page).

Attention! Navigation without COUNT indicates a very significant amount of records. That's why it's not recommended to allow all records via the method $nav->allowAllRecords(true).

Please see the file with examples.



Courses developed by Bitrix24