7

I have a list of items which is fetched from an REST API and displayed in a page 10 at a time. These items are not stored in Drupal database in any way. I managed to have a basic pager which is just a Previous & Next. But as per design the list should have a full pager.

So, how can I add a Full Pager in such page?

2 Answers 2

15

There are some examples on drupal.org of adding a pager to the page, if you want to use the one that core uses:

https://api.drupal.org/api/drupal/core%21includes%21pager.inc/function/pager_default_initialize/8.2.x

The second example should work for you:

  $page = pager_find_page();
  $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
  $offset = $num_per_page * $page;
  $result = mymodule_remote_search($keywords, $offset, $num_per_page);

  // Now that we have the total number of results, initialize the pager.
  pager_default_initialize($result->total, $num_per_page);

  // Create a render array with the search results.
  $render = [];
  $render[] = [
    '#theme' => 'search_results',
    '#results' => $result->data,
    '#type' => 'remote',
  ];

  // Finally, add the pager to the render array, and return.
  $render[] = ['#type' => 'pager'];
  return $render;

I have done this before in my own wired up search pages, it should solve your issue.

You can replace $num_per_page above with 10, if you do not have a custom module to store the setting.

You could go further, and in your code create a service that handles just attaching the pager. I was doing some sandboxing exploring the search API, and came up with this. First, you need to define your service and inject it to your controller (I refer to mine as pager_service).

  /**
   * @var \Drupal\velirsearch\Service\PagerService
   */
  private $pager_service;

  /**
   * SearchResults constructor.
   * @param \Drupal\velirsearch\Service\PagerService $pager_service
   */
  public function __construct(PagerService $pager_service) {
    $this->pager_service = $pager_service;
  }

  /**
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   * @return static
   */
  public static function create(ContainerInterface $container) {
    $pager_service = $container->get('velirsearch.pager_service');
    return new static($pager_service);
  }

Then, a method that attaches a pager:

     /**
       * Convenient method to obtain a pager to attach from the pager service.
       * @param $totalRows
       * @param $limit
       * @return array
       */
      protected function attachPager($totalRows, $limit) {
        return $this->pager_service->getPager($totalRows, $limit);
      }

Then, the service:

namespace Drupal\velirsearch\Service;

use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class PagerService implements ContainerAwareInterface {

  use ContainerAwareTrait;

  /**
   * @var \Symfony\Component\HttpFoundation\Request
   */
  private $requestStack;

  /**
   * PagerService constructor.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   */
  public function __construct(RequestStack $requestStack) {
    $this->requestStack = $requestStack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $requestStack = $container->get('request_stack');
    return new static($requestStack);
  }

  /**
   * @param $totalRows
   * @param $limit
   * @return array
   */
  public function getPager($totalRows, $limit = 10) {
    pager_default_initialize($totalRows, $limit);
    return ['#type' => 'pager'];
  }
}

Then, when you need it...

  $render[] = $this->attachPager($results->getResultCount(), 10);

  return $render;

You don't necessarily have to go that far, I was just looking for ways to reduce the code needed to attach a pager to different instances of search result controllers. YMMV.

Note that I did not use the third argument to pager_default_initialize, $element, which you will need if your page will have multiple pagers on it.

getResultCount(), by the way, is a method provided from Search API ResultSet class.

edit: now looking at my answer, your pager service does not need to inject the RequestStack at all. I was probably thinking of another idea and never finished it or figured I did not need it.

4
  • That's a pretty damn good answer, Kevin! I would suggest an improvement though. And that's in the way you inject the request_stack in your service. You may lose the create() method there and just inject it when you are declaring the service in your .services.yml like this: services: yourmodule.pager_service: class: Drupal\yourmodule\PagerService arguments: ['@request_stack'] Feb 2, 2018 at 16:18
  • 1
    Important, but easy to miss detail: when adding $render[] = ['#theme' => 'my_pretty_results'... make sure it's $render[] =... and not $render =.... The code sample I was working with had it done without the [], which still produced the output results, but failed to add the pager. The system's flexibility hid the problem.
    – coladict
    Jan 25, 2019 at 9:24
  • Sometime pager comes before the main content. To bring pager down we can set weight to pager so that it comes below the content. who is using above service can return return ['#type' => 'pager', '#weight' => 10]' from getPager method. Dec 4, 2019 at 10:08
  • 1
    good answer! pager_default_initialize() is deprecated (and removed in D9) . You'll need to use the new PagerManagerInterface. An easy drop in replacement.
    – diesonne
    May 5, 2021 at 7:02
2

As per change record pager_default_initialize() has been deprecated and new pager.manager service is created.

$current_page = \Drupal::service('pager.manager')->createPager($total_count, $results_in_page)->getCurrentPage();

Sample Code using new pager service.

<?php

namespace Drupal\controller_examples\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Component\Serialization\Json;

/**
 * Class CustomPagerController.
 *
 * @package Drupal\controller_examples\Controller
 */
class CustomPagerController extends ControllerBase {

  /**
   * The Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The pager manager.
   *
   * @var \Drupal\Core\Pager\PagerManagerInterface
   */
  protected $pagerManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->messenger = $container->get('messenger');
    $instance->logger = $container->get('logger.factory')->get('controller_examples');
    $instance->moduleHandler = $container->get('module_handler');
    $instance->pagerManager = $container->get('pager.manager');
    return $instance;
  }

  /**
   * Returns a render-able array for a location import.
   */
  public function import() {

    $header = $decoded = [];

    $header = ['State Id', 'State Name', 'Country Id'];

    $moduleExist = $this->moduleHandler->moduleExists('controller_examples');

    if ($moduleExist) {

      $modulePath = $this->moduleHandler->getModule('controller_examples')->getPath();
      $file = $modulePath . '/data/states.json';
      $locationData = file_get_contents($file);
      $decoded = Json::decode($locationData);
      $this->logger->notice($this->t('States count is %count', ['%count' => count($decoded['states'])]));
    } else {
      $this->logger->error('Controller Examples Module Not Exist');
      $this->messenger->addError("Controller Examples Module Not Exist");
    }

    $rows = $decoded['states'];

    $rowPiece = $this->pagerArray($rows, 50);

    $build['table'] = [
      '#type' => 'table',
      '#header' => $header,
      '#rows' => $rowPiece,
      '#empty' => $this->t('No content has been found.'),
    ];
    $build['pager'] = [
      '#type' => 'pager',
    ];
    return $build;
  }

  /**
   * Returns pager array.
   */
  public function pagerArray($items, $itemsPerPage) {
    // Get total items count.
    $total = count($items);
    // Get the number of the current page.
    $currentPage = $this->pagerManager->createPager($total, $itemsPerPage)->getCurrentPage();
    // Split an array into chunks.
    $chunks = array_chunk($items, $itemsPerPage);
    // Return current group item.
    $currentPageItems = $chunks[$currentPage];
    return $currentPageItems;
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.