Класс ReversePageNavigation

Класс \Bitrix\Main\UI\ReversePageNavigation считает границы выборки от конца списка, а не от начала. Он наследует класс PageNavigation, переопределяет конструктор и четыре метода расчета. Остальные методы работают так же, как в базовом классе.

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

Чем обратная навигация отличается от прямой

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

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

Характеристика

PageNavigation

ReversePageNavigation

Число записей

Задают методом setRecordCount()

Передают в конструктор, второй аргумент обязателен

Страница по умолчанию

Первая

С наибольшим номером

Неполная страница

Последняя по номеру

Отдельной неполной страницы нет, остаток добавляется к странице с наибольшим номером

Нулевое смещение

На первой странице

На странице с наибольшим номером

Если отсчет страниц не привязан к концу списка, подойдет базовый класс PageNavigation: он не требует числа записей при создании и не дает отрицательных смещений.

Создать объект обратной навигации

Конструктор принимает два аргумента: идентификатор навигации и общее число записей.

use Bitrix\Main\UI\ReversePageNavigation;
use Bitrix\Main\UserTable;

$totalCount = UserTable::getCount();

$navigation = new ReversePageNavigation('users', $totalCount);
$navigation
    ->setPageSizes([10, 20, 50])
    ->setPageSize(20)
    ->initFromUri()
;

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

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

У обязательного аргумента есть вторая роль — он защищает от отрицательного смещения.

Номер страницы приходит из адреса, и он может оказаться больше, чем число страниц. Метод initFromUri() обрезает такой номер до максимального, но только если число записей уже задано. Для обратной навигации это критично: смещение считается от конца выборки, поэтому лишний номер уводит его в минус.

При 47 записях по 20 на странице получаются две страницы. Запрос третьей даст смещение -13, и выполнить его не удастся.

Размер страницы по этой же причине задавайте до initFromUri(). Вызов setPageSize() после него пересчитает число страниц, а номер текущей страницы останется прежним — и снова уйдет за границу.

Не задавайте номер страницы методом setCurrentPage() без собственной проверки. Метод не сверяет значение с числом страниц, поэтому номер больше getPageCount() даст отрицательное смещение.

Переопределенные методы

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

Метод

PageNavigation

ReversePageNavigation

__construct()

Принимает идентификатор навигации

Принимает идентификатор навигации и общее число записей

getPageCount()

Делит число записей на размер страницы и округляет вверх. Неполная страница считается отдельной

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

getCurrentPage()

Возвращает 1, если номер страницы не задан

Возвращает наибольший номер страницы, если номер не задан

getOffset()

Отсчитывает смещение от начала выборки

Отсчитывает смещение от конца выборки. На странице с наибольшим номером возвращает 0

getLimit()

Возвращает размер страницы

На странице с наибольшим номером возвращает размер страницы плюс остаток от деления, на остальных страницах — размер страницы

Остальные методы — initFromUri(), addParams(), clearParams(), сеттеры и геттеры настроек — наследуются без изменений. Их контракты описаны в статье Класс PageNavigation.

Режим показа всех записей работает и здесь. Переопределенные методы учитывают его так же, как базовый класс: getPageCount() возвращает 1, getOffset()0, а getLimit() — общее число записей.

Как распределяются записи по страницам

При 47 записях и размере страницы 20 метод getPageCount() отбрасывает остаток и возвращает две страницы, а не три. Семь лишних записей не образуют отдельную страницу: объект добавляет их к странице с наибольшим номером.

Номера в объекте и в интерфейсе при этом не совпадают. Шаблоны компонента выводят номер по формуле «число страниц минус номер страницы плюс один», поэтому страницу с номером 2 компонент показывает посетителю как первую.

Номер в объекте и адресе

Номер в интерфейсе

getOffset()

getLimit()

Позиции записей в выборке

2 — открывается по умолчанию

1

0

27

записи 1–27

1

2

27

20

записи 28–47

У расчета две особенности.

  • Страница 2 начинается с первой записи выборки и содержит 27 записей — на семь больше заданного размера страницы.

  • Страница 1 заканчивается последней записью выборки.

Вторая особенность сохраняется при любом числе записей. Если записей станет 48, смещение страницы 1 вырастет до 28, и страница снова закончится последней записью выборки.

Из отброшенного остатка следует общее правило: объект не гарантирует ровно заданный размер на каждой странице. Число страниц растет только при переходе через значение, кратное размеру страницы, а весь остаток попадает на страницу с наибольшим номером. При размере 20 она содержит от 20 до 39 записей. Ровно 20 на каждой странице получится, только если число записей кратно размеру: при 40 записях остатка нет.

Когда записей меньше размера страницы, все они попадают на единственную страницу. Метод getLimit() при этом возвращает сумму размера и остатка: при семи записях и размере 20 это 27. Ограничение оказывается больше числа записей, но на результат запроса это не влияет — выборка вернет семь записей.

Пример. Выборка страницы обратной навигации.

use Bitrix\Main\UI\ReversePageNavigation;
use Bitrix\Main\UserTable;

$totalCount = UserTable::getCount();

$navigation = new ReversePageNavigation('users', $totalCount);
$navigation
    ->setPageSize(20)
    ->initFromUri()
;

$users = UserTable::query()
    ->setSelect(['ID', 'LOGIN'])
    ->setOrder(['ID' => 'ASC'])
    ->setOffset($navigation->getOffset())
    ->setLimit($navigation->getLimit())
    ->fetchCollection()
;

Порядок сортировки задает разработчик. Класс считает только смещение и ограничение, а какие записи окажутся в начале выборки, определяет метод setOrder().

Вывести обратную навигацию

Компонент bitrix:main.pagenavigation выводит обратную навигацию так же, как прямую. Отдельного параметра для этого нет: компонент проверяет тип переданного объекта и сам разворачивает порядок номеров страниц, а также расчет диапазона записей.

$APPLICATION->IncludeComponent('bitrix:main.pagenavigation', '', [
    'NAV_OBJECT' => $navigation,
    'SEF_MODE' => 'Y',
    'SHOW_COUNT' => 'Y',
]);

Параметры компонента и его шаблоны описаны в статье Постраничная навигация.

Если выборка пуста, навигацию выводить нечего: при нулевом числе записей getPageCount() и getCurrentPage() возвращают 0. Базовый класс в такой ситуации вернул бы номер страницы 1, поэтому проверка вида getCurrentPage() == 1 в собственном шаблоне для обратной навигации не сработает.