Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll
20/04/2025, by Ivan

Кратко (TL;DR): Используйте ссылки пагинации, предоставляемые JSON:API. Никогда не создавайте URL пагинации вручную.


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

Как?

Каждый ответ JSON:API с пагинацией содержит встроенные ссылки пагинации. Пример:

{
  "data": [
    {"type": "sample--type", "id": "abcd-uuid-here"},
    {"type": "sample--type", "id": "efgh-uuid-here"}
  ],
  "links": {
    "self": "?page[offset]=3&page[limit]=3",
    "next": "?page[offset]=6&page[limit]=3",
    "prev": "?page[offset]=0&page[limit]=3"
  }
}
  • self: текущая страница.
  • next: следующая страница.
  • prev: предыдущая страница.

Наличие или отсутствие этих ссылок имеет значение:

  1. Если есть next — есть следующая страница.
  2. Если next нет — вы на последней странице.
  3. Если есть prev — вы не на первой странице.
  4. Если нет next и prev — страница одна.

Даже если задан page[limit]=3, может вернуться только 2 ресурса — например, если один из них удалён из-за отсутствия доступа. Это нормально и ожидаемо.

Пример плохой реализации (наивный подход)

const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = 'page[limit]=5';
const filter = `filter[field_premium][value]=${user.isSubscriber()}`;

fetch(`${baseUrl}${path}?${pager}&${filter}`)
  .then(resp => resp.ok ? resp.json() : Promise.reject(resp.statusText))
  .then(document => listComponent.setContent(document.data))
  .catch(console.log);

Проблемы:

  • Фильтр избыточен — сервер сам отфильтрует запрещённый контент.
  • Предполагается, что придёт 5 элементов, но может прийти меньше.

Улучшенная реализация

const listQuota = 5;
const content = [];
const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = `page[limit]=${listQuota}`;

const getAndSetContent = (link) => {
  fetch(link)
    .then(resp => resp.ok ? resp.json() : Promise.reject(resp.statusText))
    .then(document => {
      content.push(...document.data);
      listContent.setContent(content.slice(0, listQuota));

      const hasNextPage = document.links.hasOwnProperty("next");
      if (content.length = listQuota && hasNextPage) {
        getAndSetContent(document.links.next);
      }

      const nextPageLink = hasNextPage ? document.links.next : null;
      if (content.length > listQuota || hasNextPage) {
        listComponent.showNextPageLink(nextPageLink);
      }
    })
    .catch(console.log);
}

getAndSetContent(`${baseUrl}${path}?${pager}`);

Преимущества:

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

Важно: ссылки "следующая страница" в HTML не должны совпадать с API-страницами. Лучше использовать ссылки из API как есть.

Часто задаваемые вопросы

Почему нельзя задать лимит больше 50?

JSON:API должен проверять доступ к каждому элементу ответа. При лимите в 200 000 это может привести к перегрузке и ошибкам. Лимит 50 был установлен как безопасный компромисс. Если нужно больше — используйте модуль JSON:API Page Limit.

Почему количество ресурсов в ответе меньше, чем лимит?

page[limit] — это максимум, а не гарантия. Если некоторые ресурсы недоступны пользователю (например, неопубликованные узлы), они будут удалены из ответа. Это поведение по умолчанию для повышения безопасности.

Статья с сайта Drupal Documentation.