Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Make Paginate Class more extensible #4917

Open
davist11 opened this issue Sep 11, 2019 · 2 comments

Comments

@davist11
Copy link
Contributor

commented Sep 11, 2019

We are working on a project that integrates with a paginated API and wanted to be able to share front-end pagination between API and Craft data.

I ended up accomplishing this by coping the public methods from the Paginate variable class, and then setting the appropriate properties in my class:

  • currentPage
  • totalPages
  • total
  • first
  • last

It would be awesome if those public methods were pulled out into a separate class to make it more extensible (felt a little weird to extend a Twig variable class here).

@brandonkelly

This comment has been minimized.

Copy link
Member

commented Sep 11, 2019

Most of the heavy lifting happens in the Paginator class now, have you had a look at that?

@davist11

This comment has been minimized.

Copy link
Contributor Author

commented Sep 11, 2019

I did dig into that, my problem was that I was just getting an array back from the API (vs a QueryInterface like that class was expecting)

Here's the class that I ended up creating for reference

/**
 * Structures an object to match what the Craft {% paginate %} tag returns
 */
class PaginationModel extends BaseObject
{
    public $currentPage;
    public $totalPages = 1;
    public $total = 0;
    public $first = 1;
    public $last;
    public $pageSize;
    public $perPage;
    private $_basePath;

    /**
     * Constructor to setup all of the properties for pagination that are needed
     *
     * @param mixed $response
     * @param integer $perPage
     * @param array $config
     */
    public function __construct($response, int $perPage, array $config = [])
    {
        $this->currentPage = $response->_pageNumber;
        $this->totalPages = $response->_totalPages;
        $this->total = $response->_totalResults;
        $this->perPage = $perPage;
        $this->pageSize = $response->_pageSize;
        $this->first = $this->getPageOffset() + 1;
        $this->last = $this->getPageOffset() + $this->getPageResults();

        parent::__construct($config);
    }

    /**
     * Returns the offset of the first result
     *
     * @return integer
     */
    public function getPageOffset(): int
    {
        return $this->perPage * ($this->currentPage - 1);
    }

    /**
     * Return the number of items on the current page
     *
     * @return integer
     */
    public function getPageResults() : int
    {
        return ($this->pageSize < $this->perPage) ? $this->pageSize : $this->perPage;
    }

    // Below is pulled directly from craft\web\twig\variables\Paginate
    // to replicate functionality from the Craft paginate tag
    // =========================================================================

    /**
     * Returns the base path that should be used for pagination URLs.
     *
     * @return string
     */
    public function getBasePath(): string
    {
        if ($this->_basePath !== null) {
            return $this->_basePath;
        }
        return $this->_basePath = Craft::$app->getRequest()->getPathInfo();
    }

    /**
     * Sets the base path that should be used for pagination URLs.
     *
     * @param string $basePath
     */
    public function setBasePath(string $basePath)
    {
        $this->_basePath = $basePath;
    }

    /**
     * Returns the URL to a specific page
     *
     * @param int $page
     * @return string|null
     */
    public function getPageUrl(int $page)
    {
        if ($page < 1 || $page > $this->totalPages) {
            return null;
        }

        $pageTrigger = Craft::$app->getConfig()->getGeneral()->getPageTrigger();
        $useQueryParam = strpos($pageTrigger, '?') === 0;

        $path = $this->getBasePath();

        // If not using a query param, append the page to the path
        if (!$useQueryParam && $page != 1) {
            if ($path) {
                $path .= '/';
            }

            $path .= $pageTrigger . $page;
        }

        // Build the URL with the same query string as the current request
        $url = UrlHelper::url($path, Craft::$app->getRequest()->getQueryStringWithoutPath());

        // If using a query param, append or remove it
        if ($useQueryParam) {
            $param = trim($pageTrigger, '?=');
            if ($page != 1) {
                $url = UrlHelper::urlWithParams($url, [$param => $page]);
            } else {
                $url = UrlHelper::removeParam($url, $param);
            }
        }

        return $url;
    }

    /**
     * Returns the URL to the first page.
     *
     * @return string|null
     */
    public function getFirstUrl()
    {
        return $this->getPageUrl(1);
    }

    /**
     * Returns the URL to the next page.
     *
     * @return string|null
     */
    public function getLastUrl()
    {
        return $this->getPageUrl($this->totalPages);
    }

    /**
     * Returns the URL to the previous page.
     *
     * @return string|null
     */
    public function getPrevUrl()
    {
        return $this->getPageUrl($this->currentPage - 1);
    }

    /**
     * Returns the URL to the next page.
     *
     * @return string|null
     */
    public function getNextUrl()
    {
        return $this->getPageUrl($this->currentPage + 1);
    }

    /**
     * Returns previous page URLs up to a certain distance from the current page.
     *
     * @param int|null $dist
     * @return array
     */
    public function getPrevUrls(int $dist = null): array
    {
        if ($dist !== null) {
            $start = $this->currentPage - $dist;
        } else {
            $start = 1;
        }

        return $this->getRangeUrls($start, $this->currentPage - 1);
    }

    /**
     * Returns next page URLs up to a certain distance from the current page.
     *
     * @param int|null $dist
     * @return array
     */
    public function getNextUrls(int $dist = null): array
    {
        if ($dist !== null) {
            $end = $this->currentPage + $dist;
        } else {
            $end = $this->totalPages;
        }

        return $this->getRangeUrls($this->currentPage + 1, $end);
    }

    /**
     * Returns a range of page URLs.
     *
     * @param int $start
     * @param int $end
     * @return array
     */
    public function getRangeUrls(int $start, int $end): array
    {
        if ($start < 1) {
            $start = 1;
        }

        if ($end > $this->totalPages) {
            $end = $this->totalPages;
        }

        $urls = [];

        for ($page = $start; $page <= $end; $page++) {
            $urls[$page] = $this->getPageUrl($page);
        }

        return $urls;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.