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

More paginator on same page #5

Open
Grazulex opened this issue Dec 16, 2015 · 5 comments
Open

More paginator on same page #5

Grazulex opened this issue Dec 16, 2015 · 5 comments

Comments

@Grazulex
Copy link

Hello,
do you have a solution to put more paginator on the same page ?

thanks

JMS

@l3pp4rd
Copy link
Member

l3pp4rd commented Dec 16, 2015

just copy the class and make different parameter names

@ValdasK
Copy link
Contributor

ValdasK commented Dec 17, 2015

I guess this won't work so well, as pager widgets are global. Going to other pages on single paginator will make all pagers to display other pages

@l3pp4rd
Copy link
Member

l3pp4rd commented Dec 17, 2015

we did not have such use case so far, but maybe adding public properties for parameter names would solve this in clean way. templates could use these parameters

@Grazulex
Copy link
Author

If you want, I have update the Pagintion class :

class Pagination extends \ArrayIterator
{
    /**
     * a default filter value for ANY filter option
     * does not modify a query with this filter value.
     * Use this value in filter select option
     *
     * @var string
     */
    public static $filterAny = 'any';

    /**
     * this is an upper bound for max items per page
     * if an user modifiers request uri limit to a crazy number
     * that may impact the server performance. This number
     * will ensure the limit
     *
     * @var integer
     */
    public static $maxPerPage = 500;

    /**
     * Default pager options
     *
     * @var array
     */
    public static $defaults = [
        /**
         * This callable will be called for every filter option
         * found in the url: function (QueryBuilder $qb, $filterKey, $filterValue)
         *
         * The Pager checks whether the DQL has changed after calling
         * handler. And if it did, it skips the default handling.
         *
         * You may throw the exception if this filter is not allowed.
         *
         * @var callable
         */
        'applyFilter' => null,

        /**
         * This callable will be called for every sorter option
         * found in the url: function (QueryBuilder $qb, $sorterKey, $direction)
         *
         * The Pager checks whether the DQL has changed after calling
         * handler. And if it did, it skips the default handling.
         *
         * You may throw the exception if this sorter is not allowed.
         *
         * @var callable
         */
        'applySorter' => null,

        /**
         * Default filters to apply ['key' => 'value'] array
         *
         * @var array
         */
        'filters' => [], // default filters to apply

        /**
         * Default sorters to apply ['key' => 'direction'] array
         *
         * @var array
         */
        'sorters' => [], // default sorters to apply

        /**
         * Page range for pagination
         *
         * @var integer
         */
        'range' => 10,

        /**
         * Number of items per page
         *
         * @var integer
         */
        'limit' => 10,

        'alias' => '',
    ];

    /**
     * Pagination data values
     *
     * @var array
     */
    protected $pagination;

    /**
     * Request query parameters
     *
     * @var array
     */
    protected $query;

    /**
     * Currently used route name
     *
     * @var string
     */
    protected $route;

    /**
     * Total item count
     *
     * @var integer
     */
    protected $count;

    /**
     * items per page
     *
     * @var integer
     */
    protected $limit;

    /**
     * Current page
     *
     * @var integer
     */
    protected $page;

    protected $alias;

    /**
     * Paginate given $qb based on $request
     * accepts $options for customization for filters and sorters
     *
     * @param QueryBuilder $qb
     * @param Request $request
     * @param array $options
     */
    public function __construct(QueryBuilder $qb, Request $request, array $options = [])
    {
        extract(array_merge(self::$defaults, $options));

        $params = array_merge($request->query->all(), $request->attributes->all());
        foreach ($params as $key => $param) {
            if (substr($key, 0, 1) == '_') {
                unset($params[$key]);
            }
        }       
        // only one sorter may be used, from params or default
        $params['sorters'] = isset($params['sorters']) ? $params['sorters'] : $sorters;
        // merge default filters
        $params['filters'] = array_merge($filters, isset($params['filters']) ? $params['filters'] : []);

        $params['alias'] = isset($params['alias']) ? $params['alias'] : $alias;

        $paginator = clone $qb;
        if ($alias == $params['alias'])
        {
            $this->applyFilters($paginator, $params['filters'], $applyFilter);
            $this->applySorters($paginator, $params['sorters'], $applySorter);
        }

        $counter = clone $paginator;
        $counter->resetDQLPart('orderBy');
        $counter->select($qb->expr()->countDistinct($qb->getRootAlias()));

        $this->page = max(abs(intval((isset($params['page']) ? $params['page'] : 1))), 1);
        $this->limit = abs(intval((isset($params['limit']) ? $params['limit'] : $limit)));
        // ensure upper bound
        $this->limit = min($this->limit, self::$maxPerPage);

        $this->count = intval($counter->getQuery()->getSingleScalarResult());

        // Set page to last one if query is more than total
        $this->page = max(min(intval(ceil($this->count / $this->limit)), $this->page), 1);

        $paginator->setFirstResult(($this->page - 1) * $this->limit);
        $paginator->setMaxResults($this->limit);

        $this->route = $request->attributes->get('_route');
        $this->query = $params;

        $this->pagination = $this->buildPagination($this->page, $range);

        parent::__construct($paginator->getQuery()->getResult());
    }

    /**
     * @return int
     */
    public function currentPage()
    {
        return $this->page;
    }

    /**
     * @return int
     */
    public function itemsPerPage()
    {
        return $this->limit;
    }

    public function query()
    {
        return $this->query;
    }

    public function route()
    {
        return $this->route;
    }

    public function total()
    {
        return $this->count;
    }

    public function pagination()
    {
        return $this->pagination;
    }

    protected function applySorters(QueryBuilder $qb, array $sorters, callable $handler = null)
    {
        foreach ($sorters as $key => $direction) {
            // custom handling
            if (null !== $handler) {
                $dql = $qb->getDQL(); // will check for difference
                call_user_func_array($handler, [$qb, $key, $direction]);
                if ($qb->getDQL() !== $dql) {
                    continue; // custom sorter handler has handled the parameter
                }
            }
            $qb->addOrderBy($key, in_array(strtoupper($direction), ['ASC', 'DESC']) ? $direction : 'ASC');
        }
    }

    protected function applyFilters(QueryBuilder $qb, array $filters, callable $handler = null)
    {
        foreach ($filters as $key => $val) {
            if ($val === self::$filterAny) {
                continue;
            }

            if (null !== $handler) {
                call_user_func_array($handler, [$qb, $key, $val]);
                continue;
            }

            $name = preg_replace('/[^A-z]/', '_', $key);
            $qb->andWhere($qb->expr()->{is_array($val) ? 'in' : 'eq'}($key, ':'.$name));
            $qb->setParameter($name, $val);
        }
    }

    protected function buildPagination($page, $range)
    {
        $pageCount = intval(ceil($this->total() / $this->limit));
        $current = $page;
        if ($range > $pageCount) {
            $range = $pageCount;
        }
        $delta = ceil($range / 2);
        if ($current - $delta > $pageCount - $range) {
            $pages = range($pageCount - $range + 1, max($pageCount, 1));
        } else {
            if ($current - $delta < 0) {
                $delta = $current;
            }
            $offset = $current - $delta;
            $pages = range($offset + 1, $offset + $range);
        }

        $proximity = floor($range / 2);
        $startPage = $current - $proximity;
        $endPage = $current + $proximity;
        if ($startPage < 1) {
            $endPage = min($endPage + (1 - $startPage), $pageCount);
            $startPage = 1;
        }
        if ($endPage > $pageCount) {
            $startPage = max($startPage - ($endPage - $pageCount), 1);
            $endPage = $pageCount;
        }

        $viewData = [
            'last' => $pageCount,
            'current' => $current,
            'numItemsPerPage' => $this->limit,
            'first' => 1,
            'pageCount' => $pageCount,
            'totalCount' => $this->total(),
            'pageRange' => $range,
            'startPage' => $startPage,
            'endPage' => $endPage,
        ];

        if ($current - 1 > 0) {
            $viewData['previous'] = $current - 1;
        }
        if ($current + 1 <= $pageCount) {
            $viewData['next'] = $current + 1;
        }

        $viewData['pagesInRange'] = $pages;
        $viewData['firstPageInRange'] = min($pages);
        $viewData['lastPageInRange']  = max($pages);
        $viewData['currentItemCount'] = $this->count();
        $viewData['firstItemNumber'] = (($current - 1) * $this->limit) + 1;
        $viewData['lastItemNumber'] = $viewData['firstItemNumber'] + $viewData['currentItemCount'] - 1;
        return $viewData;
    }

With this new "alias" option, you can used different pagination on the same page

@l3pp4rd
Copy link
Member

l3pp4rd commented Apr 19, 2016

basically this bundle is so small that you can just copy the source code and adapt the way you like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants