From 927e675cb8359ad22860b68fc2b429d3520f77d5 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Nov 2011 23:10:39 -0600 Subject: [PATCH] refactor the paginator to reincorporate the google style slider. --- laravel/paginator.php | 334 ++++++++++++++++++++++++------------------ 1 file changed, 194 insertions(+), 140 deletions(-) diff --git a/laravel/paginator.php b/laravel/paginator.php index 990e1cd2..53c258f4 100644 --- a/laravel/paginator.php +++ b/laravel/paginator.php @@ -54,11 +54,11 @@ class Paginator { protected $appendage; /** - * The pagination elements that will be generated. + * The "dots" element used in the pagination slider. * - * @var array + * @var string */ - protected $elements = array('first', 'previous', 'status', 'next', 'last'); + protected $dots = '...'; /** * Create a new Paginator instance. @@ -91,9 +91,9 @@ class Paginator { { $page = static::page($total, $per_page); - $last_page = ceil($total / $per_page); + $last = ceil($total / $per_page); - return new static($results, $page, $total, $per_page, $last_page); + return new static($results, $page, $total, $per_page, $last); } /** @@ -116,198 +116,266 @@ class Paginator { return ($last > 0) ? $last : 1; } - return ($page < 1 or filter_var($page, FILTER_VALIDATE_INT) === false) ? 1 : $page; + return (static::valid($page)) ? $page : 1; + } + + /** + * Determine if a given page number is a valid page. + * + * A valid page must be greater than or equal to one and a valid integer. + * + * @param int $page + * @return bool + */ + protected static function valid($page) + { + return $page >= 1 and filter_var($page, FILTER_VALIDATE_INT) !== false; } /** * Create the HTML pagination links. * + * Typically, an intelligent, "sliding" window of links will be rendered based + * on the total number of pages, the current page, and the number of adjacent + * pages that should rendered. This creates a beautiful paginator similar to + * that of Google's. + * + * Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52 + * + * If you wish to render only certain elements of the pagination control, + * explore some of the other public methods available on the instance. + * + * + * // Render the pagination links + * echo $paginator->links(); + * + * // Render the pagination links using a given window size + * echo $paginator->links(5); + * + * + * @param int $adjacent * @return string */ - public function links() + public function links($adjacent = 3) { if ($this->last <= 1) return ''; - // Each pagination element is created by an element method. This allows - // us to keep this class clean and simple, because pagination code can - // become a mess. We would rather keep it simple and beautiful. + // The hard-coded seven is to account for all of the constant elements in a + // sliding range, such as the current page, the two ellipses, and the two + // beginning and ending pages. // - // If the page is greater the one, we will render the first and previous - // links, otherwise we skip them since we are already on the first page. - if ($this->page > 1) + // If there are not enough pages to make the creation of a slider possible + // based on the adjacent pages, we will simply display all of the pages. + // Otherwise, we will create a "truncating" slider which displays a nice + // window of pages based on the current page. + if ($this->last < 7 + ($adjacent * 2)) { - $elements[] = $this->first(); - - $elements[] = $this->previous(); + $numbers = $this->range(1, $this->last); + } + else + { + $numbers = $this->slider($adjacent); } - // The status is always rendered regardless of the current page. So we - // can simply add it to the array of pagination elements. - $elements[] = $this->status(); + $content = $this->previous().' '.$numbers.' '.$this->next(); - // If the current page is not the last page, we will render the next - // and last links. Otherwise we will skip them since we are already - // on the last page and can't go any further. - if ($this->page < $this->last) + return ''; + } + + /** + * Build sliding list of HTML numeric page links. + * + * This method is very similar to the "links" method, only it does not + * render the "first" and "last" pagination links, but only the pages. + * + * + * // Render the pagination slider + * echo $paginator->slider(); + * + * // Render the pagination slider using a given window size + * echo $paginator->slider(5); + * + * + * @param int $adjacent + * @return string + */ + public function slider($adjacent = 3) + { + $window = $adjacent * 2; + + // Example: 1 [2] 3 4 5 6 ... 23 24 + if ($this->page <= $window) { - $elements[] = $this->next(); - - $elements[] = $this->last(); + return $this->range(1, $window + 2).' '.$this->ending(); } - return ''.PHP_EOL; + // Example: 1 2 ... 32 33 34 35 [36] 37 + elseif ($this->page >= $this->last - $window) + { + return $this->beginning().' '.$this->range($this->last - $window - 2, $this->last); + } + + // Example: 1 2 ... 23 24 25 [26] 27 28 29 ... 51 52 + $content = $this->range($this->page - $adjacent, $this->page + $adjacent); + + return $this->beginning().' '.$content.' '.$this->ending(); } /** - * Get the "status" pagination element. + * Generate the "previous" HTML link. * - * @param string $text - * @return string - */ - public function status($text = null) - { - if (is_null($text)) $text = Lang::line('pagination.status')->get(); - - return str_replace(array(':current', ':last'), array($this->page, $this->last), $text); - } - - /** - * Create the "first" pagination element. + * + * // Create the "previous" pagination element + * echo $paginator->previous(); * - * @param string $text - * @return string - */ - public function first($text = null) - { - return $this->backwards(__FUNCTION__, $text, 1); - } - - /** - * Create the "previous" pagination element. + * // Create the "previous" pagination element with custom text + * echo $paginator->previous('Go Back'); + * * - * @param string $text * @return string */ public function previous($text = null) { - return $this->backwards(__FUNCTION__, $text, $this->page - 1); + $disabled = function($page) { return $page <= 1; }; + + return $this->element(__FUNCTION__, $this->page - 1, $text, $disabled); } /** - * Create the "next" pagination element. + * Generate the "next" HTML link. + * + * + * // Create the "next" pagination element + * echo $paginator->next(); + * + * // Create the "next" pagination element with custom text + * echo $paginator->next('Skip Forwards'); + * * - * @param string $text * @return string */ public function next($text = null) { - return $this->forwards(__FUNCTION__, $text, $this->page + 1); + $disabled = function($page, $last) { return $page >= $last; }; + + return $this->element(__FUNCTION__, $this->page + 1, $text, $disabled); } /** - * Create the "last" pagination element. - * - * @param string $text - * @return string - */ - public function last($text = null) - { - return $this->forwards(__FUNCTION__, $text, $this->last); - } - - /** - * Create a "backwards" paginatino element. - * - * This function handles the creation of the first and previous elements. - * - * @param string $element - * @param string $text - * @param int $last - * @return string - */ - protected function backwards($element, $text, $last) - { - $disabler = function($page) { return $page <= 1; }; - - return $this->element($element, $text, $last, $disabler); - } - - /** - * Create a "forwards" paginatino element. - * - * This function handles the creation of the next and last elements. - * - * @param string $element - * @param string $text - * @param int $last - * @return string - */ - protected function forwards($element, $text, $last) - { - $disabler = function($page, $last) { return $page >= $last; }; - - return $this->element($element, $text, $last, $disabler); - } - - /** - * Create a chronological pagination element. + * Create a chronological pagination element, such as a "previous" or "next" link. * * @param string $element - * @param string $text * @param int $page - * @param Closure $disabler + * @param string $text + * @param Closure $disabled * @return string */ - protected function element($element, $text, $page, $disabler) + protected function element($element, $page, $text, $disabled) { $class = "{$element}_page"; if (is_null($text)) $text = Lang::line("pagination.{$element}")->get(); - if ($disabler($this->page, $this->last)) + // Each consumer of this method provides a "disabled" Closure which can + // be used to determine if the element should be a span element or an + // actual link. For example, if the current page is the first page, + // the "first" element should be a span instead of a link. + if ($disabled($this->page, $this->last)) { - return HTML::span($text, array('class' => "disabled {$class}")); + return HTML::span($text, array('class' => "{$class} disabled")); } else { - // We will assume the page links should use HTTPS if the current request - // is also using HTTPS. Since pagination links automatically point to - // the current URI, this makes pretty good sense. - list($uri, $secure) = array(Request::uri(), Request::secure()); - - $appendage = '?page='.$page.$this->appendage($element, $page); - - return HTML::link($uri.$appendage, $text, array('class' => $class), $secure); + return $this->link($page, $text, $class); } } /** - * Create the pagination link "appendage" for an element. + * Build the first two page links for a sliding page range. * - * @param string $element - * @param int $page * @return string */ - protected function appendage($element, $page) + protected function beginning() { - if ( ! is_null($this->appendage)) return $this->appendage; + return $this->range(1, 2).' '.$this->dots; + } - $appendage = ''; + /** + * Build the last two page links for a sliding page range. + * + * @return string + */ + protected function ending() + { + return $this->dots.' '.$this->range($this->last - 1, $this->last); + } - if (count($this->appends) > 0) + /** + * Build a range of numeric pagination links. + * + * For the current page, an HTML span element will be generated instead of a link. + * + * @param int $start + * @param int $end + * @return string + */ + protected function range($start, $end) + { + $pages = array(); + + for ($page = $start; $page <= $end; $page++) { - $appendage .= '&'.http_build_query($this->appends); + if ($this->page == $page) + { + $pages[] = HTML::span($page, array('class' => 'current')); + } + else + { + $pages[] = $this->link($page, $page, null); + } } - return $this->appendage = $appendage; + return implode(' ', $pages); + } + + /** + * Create a HTML page link. + * + * @param int $page + * @param string $text + * @param string $attributes + * @return string + */ + protected function link($page, $text, $class) + { + $url = URI::current().'?page='.$page.$this->appendage($this->appends); + + return HTML::link($url, $text, compact('class'), Request::secure()); + } + + /** + * Create the "appendage" that should be attached to every pagination link. + * + * The developer may assign an array of values that will be converted to a + * query string and attached to every pagination link. This allows simple + * implementation of sorting or other things the developer may need. + * + * @param array $appends + * @return string + */ + protected function appendage($appends) + { + if ( ! is_null($this->appendage)) + { + return $this->appendage; + } + + return $this->appendage = (count($appends) > 0) ? '&'.http_build_query($appends) : ''; } /** * Set the items that should be appended to the link query strings. * - * This provides a convenient method of maintaining sort or passing other - * information to the route handling pagination. - * * @param array $values * @return Paginator */ @@ -317,18 +385,4 @@ class Paginator { return $this; } - /** - * Set the elements that should be included when creating the pagination links. - * - * The available elements are "first", "previous", "status", "next", and "last". - * - * @param array $elements - * @return string - */ - public function elements($elements) - { - $this->elements = $elements; - return $this; - } - } \ No newline at end of file