Skip to content

Digg Style Pagination

Derek Jones edited this page Jul 4, 2012 · 10 revisions

Category:Libraries

Digg Style Pagination Library

The existing pagination class is sufficient, however, I wanted to produce digg-style pagination.

See here for an example of what I mean - [url]http://www.strangerstudios.com/sandbox/pagination/diggstyle.php[/url]. A lot of the updated code has been taken from the script provided there. However, some other improvements/alterations have also been made. Basically this is a mish-mash between the CI_Pagination library and the code found at the above link.

Benefits

  • ability to let the user control how many records per page they would like to see. The url's for this are in the form of /controller/function/perpage/page. E.g. /controller/function/10/2 means show 10 records per page, and show the second page. Therefore you would be getting records 11- 20. Make sense?
  • pages are in terms of page numbers (e.g. /view/10/2 means page 2 rather than offset of 2 which is more logical to me)
  • if there are 100 pages (for example) rather than showing only a few pages either side of this in the pagination, digg-style will show a few pages either side as well as the first couple of pages and the last couple of pages. I think this increases usability.
  • i think it looks better :)

[b]I have only overwritten the existing CI_Pagination class. This is a bit lazy but if you want to do the right thing you should probably rename it and call it as a custom-made library. This is my first library and I am not a guru PHP coder so there may well be something which can be optimised further or some minor bugs. Test it thoroughly before using in anything critical.[/b]

Ok first of all, the updated /system/libraries/Pagination.php file.

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Code Igniter
*
* An open source application development framework for PHP 4.3.2 or newer
*
* @package        CodeIgniter
* @author        Rick Ellis
* @copyright    Copyright (c) 2006, pMachine, Inc.
* @license        http://www.codeignitor.com/user_guide/license.html
* @link        http://www.codeigniter.com
* @since        Version 1.0
* @filesource
*/

// ------------------------------------------------------------------------

/**
* Pagination Class
*
* @package        CodeIgniter
* @subpackage    Libraries
* @category    Pagination
* @author        Rick Ellis
* @link        http://www.codeigniter.com/user_guide/libraries/pagination.html
*/
class CI_Pagination {

    var $base_url           = ''; // The page we are linking to
    var $total_rows         = ''; // Total number of items (database results)
    var $per_page           = 10; // Max number of items you want shown per page
    var $num_links          =  2; // Number of "digit" links to show before/after the currently viewed page
    var $cur_page           =  0; // The current page being viewed
    var $first_link           = '‹ First';
    var $next_link            = '>';
    var $prev_link            = '<';
    var $last_link            = 'Last ›';
    var $uri_segment        = 3;
    var $full_tag_open        = '';
    var $full_tag_close        = '';
    var $first_tag_open        = '';
    var $first_tag_close    = ' ';
    var $last_tag_open        = ' ';
    var $last_tag_close        = '';
    var $cur_tag_open        = '&nbsp;<b>';
    var $cur_tag_close        = '</b>';
    var $next_tag_open        = '&nbsp;';
    var $next_tag_close        = '&nbsp;';
    var $prev_tag_open        = '&nbsp;';
    var $prev_tag_close        = '';
    var $num_tag_open        = '&nbsp;';
    var $num_tag_close        = '';

    /**
     * Constructor
     *
     * @access    public
     * @param    array    initialization parameters
     */
    function CI_Pagination($params = array())
    {
        if (count($params) > 0)
        {
            $this->initialize($params);        
        }
        
        log_message('debug', "Pagination Class Initialized");
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Initialize Preferences
     *
     * @access    public
     * @param    array    initialization parameters
     * @return    void
     */
    function initialize($params = array())
    {
        if (count($params) > 0)
        {
            foreach ($params as $key => $val)
            {
                if (isset($this->$key))
                {
                    $this->$key = $val;
                }
            }        
        }
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Generate the pagination links
     *
     * @access    public
     * @return    string
     */    
    function create_links()
    {
        // If our item count or per-page total is zero there is no need to continue.
        if ($this->total_rows == 0 OR $this->per_page == 0)
        {
           return '';
        }

        // Calculate the total number of pages
        $num_pages = ceil($this->total_rows / $this->per_page);

        // Is there only one page? Hm... nothing more to do here then.
        if ($num_pages == 1)
        {
            return '';
        }

        // Determine the current page number.        
        $CI =& get_instance();    
        if ($CI->uri->segment($this->uri_segment) != 0)
        {
            $this->cur_page = $CI->uri->segment($this->uri_segment);
            
            // Prep the current page - no funny business!
            $this->cur_page = preg_replace("/[a-z\-]/", "", $this->cur_page);
        }
        else
        {
            $this->cur_page = 1;
        }
        
                
        if ( ! is_numeric($this->cur_page))
        {
            $this->cur_page = 0;
        }
        
        // Is the page number beyond the result range?
        // If so we show the last page
        if ($this->cur_page > $num_pages)
        {
            $this->cur_page = $num_pages;
        }
        
        $uri_page_number = $this->cur_page;
        //$this->cur_page = floor(($this->cur_page/$this->per_page) + 1);


        // Add a trailing slash to the base URL if needed
        $this->base_url = preg_replace("/(.+?)\/*$/", "\\1/",  $this->base_url);
        
          // And here we go...
        $pagination = '';
        
        // Render the "First" link
        if ($this->cur_page > 1)
        {
            $pagination .= $this->first_tag_open.'<a >base_url.'">'.$this->first_link.'</a>'.$this->first_tag_close;
        }
        else
            $pagination.= "<span class=\"disabled\">".$this->first_link."</span>";
        
        // Render the "previous" link
        if ($this->cur_page > 1)
        {
            $prev = $this->cur_page - 1;
            $pagination .= $this->prev_tag_open.'<a >base_url.$prev.'">'.$this->prev_link.'</a>'.$this->prev_tag_close;
        }
        else
            $pagination.= "<span class=\"disabled\">&lt;</span>";
        
        
        if ($num_pages < 7 + ($this->num_links * 2))    //not enough pages to bother breaking it up
        {
            for ($counter = 1; $counter <= $num_pages; $counter++)
            {
                if ($counter == $this->cur_page)
                {
                    $pagination .= "<span class=\"current\">$counter</span>"; // Current page
                }
                else
                {
                    $pagination .= $this->num_tag_open.'<a >base_url.$counter.'">'.$counter.'</a>'.$this->num_tag_close;
                }
            }
        }
        elseif($num_pages > 5 + ($this->num_links * 2))    //enough pages to hide some
        {
            //close to beginning; only hide later pages
            if($this->cur_page < 1 + ($this->num_links * 2))
            {
                for ($counter = 1; $counter < 4 + ($this->num_links * 2); $counter++)
                {
                    if ($counter == $this->cur_page)
                    {
                        $pagination .= "<span class=\"current\">$counter</span>"; // Current page
                    }
                    else
                    {
                        $pagination .= $this->num_tag_open.'<a >base_url.$counter.'">'.$counter.'</a>'.$this->num_tag_close;
                    }
                }
                
                $pagination.= "...";
                
                $num_pages_minus = $num_pages-1;
                $pagination .= $this->num_tag_open.'<a >base_url.$num_pages_minus.'">'.$num_pages_minus.'</a>'.$this->num_tag_close;
                $pagination .= $this->num_tag_open.'<a >base_url.$num_pages.'">'.$num_pages.'</a>'.$this->num_tag_close;    
            }
            //in middle; hide some front and some back
            elseif($num_pages - ($this->num_links * 2) > $this->cur_page && $this->cur_page > ($this->num_links * 2))
            {
                $one=1;
                $two=2;
        
                $pagination .= $this->num_tag_open.'<a >base_url.$one.'">'.$one.'</a>'.$this->num_tag_close;
                $pagination .= $this->num_tag_open.'<a >base_url.$two.'">'.$two.'</a>'.$this->num_tag_close;
                
                $pagination.= "...";
                
                for ($counter = $this->cur_page - $this->num_links; $counter <= $this->cur_page + $this->num_links; $counter++)
                {
                    if ($counter == $this->cur_page)
                    {
                        $pagination.= "<span class=\"current\">$counter</span>";
                    }
                    else
                    {
                        $pagination .= $this->num_tag_open.'<a >base_url.$counter.'">'.$counter.'</a>'.$this->num_tag_close;
                    }
                }
                $pagination.= "...";
        
                $num_pages_minus = $num_pages-1;
                $pagination .= $this->num_tag_open.'<a >base_url.$num_pages_minus.'">'.$num_pages_minus.'</a>'.$this->num_tag_close;
                $pagination .= $this->num_tag_open.'<a >base_url.$num_pages.'">'.$num_pages.'</a>'.$this->num_tag_close;    
            }
            //close to end; only hide early pages
            else
            {
                $one=1;
                $two=2;
        
                $pagination .= $this->num_tag_open.'<a >base_url.$one.'">'.$one.'</a>'.$this->num_tag_close;
                $pagination .= $this->num_tag_open.'<a >base_url.$two.'">'.$two.'</a>'.$this->num_tag_close;
                
                $pagination.= "...";
                
                for ($counter = $num_pages - (2 + ($this->num_links * 2)); $counter <= $num_pages; $counter++)
                {
                    if ($counter == $this->cur_page)
                    {
                        $pagination.= "<span class=\"current\">$counter</span>";
                    }
                    else
                    {
                        $pagination .= $this->num_tag_open.'<a >base_url.$counter.'">'.$counter.'</a>'.$this->num_tag_close;
                    }
                }
            }
        }
        
        // Render the "next" link
        if ($this->cur_page < $counter - 1)
        {
            $next = $this->cur_page + 1;
            $pagination .= $this->next_tag_open.'<a >base_url.$next.'">'.$this->next_link.'</a>'.$this->next_tag_close;
        }
        else
            $pagination.= "<span class=\"disabled\">&gt;</span>";
        
        
        // Render the "Last" link
        if ($this->cur_page < $counter - 1)
        {
            $pagination .= $this->last_tag_open.'<a >base_url.$num_pages.'">'.$this->last_link.'</a>'.$this->last_tag_close;
         }
        else
            $pagination.= "<span class=\"disabled\">".$this->last_link."</span>";

        // Kill double slashes.  Note: Sometimes we can end up with a double slash
        // in the penultimate link so we'll kill all double slashes.
        $pagination = preg_replace("#([^:])//+#", "\\1/", $pagination);

        // Add the wrapper HTML if exists
        $pagination = $this->full_tag_open.$pagination.$this->full_tag_close;
        
        return $pagination;        
    }
}
// END Pagination Class

/* End of file Pagination.php */
/* Location: ./system/libraries/Pagination.php */

Secondly, a new pagination plugin to keep our controller files a bit cleaner So, make a new file here /system/plugins/pagination_pi.php

&lt;?php

/**
 * Initalises pagination config settings and returns the create_links string.
 *
 * @param unknown_type $cur_page_seg
 * @param unknown_type $total_rows
 * @param unknown_type $per_page_val
 * @param unknown_type $per_page_seg
 * @param unknown_type $pbase_url
 * @return string
 */
function init_paginate ($cur_page_seg, $total_rows, $per_page_val, $per_page_seg, $pbase_url) 
{    
    $obj =& get_instance();
    
    //load relevant libraries
    $obj->load->library('pagination');
    
    //pagination setup
    //echo $per_page_val;
    $pbase_url = $pbase_url."/".$per_page_val;
    $config['base_url'] = $pbase_url;
    $config['total_rows'] = $total_rows;
    $config['per_page'] = $per_page_val; 
    $config['uri_segment'] = $cur_page_seg;
    
    //pagination initialization
    $obj->pagination->initialize($config); 
    
    return $obj->pagination->create_links();
    // end paging
}


/**
 * Works out how many records should be shown per page. The value will either be taken from
 *     1. The uri segment
 *  2. The default value set in the controller; or
 *  3. A default upper/lower range if people try to do tricky things in the uri segment (e.g. negative
 *     values).
 *
 * @param unknown_type $per_page_val
 * @param unknown_type $per_page_seg
 * @return unknown
 */
function get_per_page($per_page_val, $per_page_seg)
{    
    $per_page = validate_per_page($per_page_val, $per_page_seg);
    
    return $per_page;
}

/**
 * Validates the per page val and returns the value. Will check for out of bounds and non-numerical values
 *
 * @param unknown_type $per_page_val
 * @param unknown_type $per_page_seg
 * @return unknown
 */
function validate_per_page($per_page_val, $per_page_seg)
{
    $obj =& get_instance();
    
     //use per page if set from the uri, otherwise use default value set in controller
    $per_page = $obj->uri->segment($per_page_seg, $per_page_val);
    
    //security check - ensure value is numeric
    if (is_numeric($per_page)) : $per_page=$per_page; else: $per_page = $per_page_val; endif;
    
    //limit the per page value - prevents someone from doing a query of 1000 rows etc
    if ($per_page > 100) :    $per_page = 100; endif;
    
    //limit the per page value from negative numbers
    if ($per_page < 1) : $per_page = 10; endif;
    
    return $per_page;
}

/**
 * Will work out what offset should be used for a given sql query for a given pag
 *
 * @param unknown_type $cur_page_seg
 * @param unknown_type $per_page_val
 * @return unknown
 */
function get_offset($cur_page_seg, $per_page_val)
{
    $obj =& get_instance();
    
    //determine offset - per_page_val has already been validated
    $page = 1;
    if ($obj->uri->segment($cur_page_seg, 0)) : $page = $obj->uri->segment($cur_page_seg, 0); endif;
    if ( $page == 1):    $offset = 0; else :    $offset = ($page - 1) * $per_page_val; endif;
     
     return $offset;
}
?&gt;

Thirdly, our controller file. To use the pagination, add this to the controller.

        $this->load->model('My_Model', 'sql', TRUE);
        
        //********************SET UP PAGINATION VALUES****************************
        //set up per_page_value, per_page_seg, cur_page_seg and $data['pbase_url']
        //************************************************************************
        $this->load->plugin('pagination'); 
        
        $per_page_value = 5;  //default - unless overridden later
        $per_page_seg = 5;    //the uri segment for the per page value
        $cur_page_seg = 6;      //the url segment for the current page value (generally +1 of per page seg)
        
        $per_page = get_per_page($per_page_value, $per_page_seg); 
        $offset = get_offset($cur_page_seg, $per_page); 
        
        //generate the query
        $data['query'] = $this->sql->get_records($offset, $per_page);
        
        //find out the total amount of records
        $total_rows = count($this->sql->get_records(NULL, NULL));
        
        $data['pbase_url'] = base_url().'path/to/controller/';
        $data['pagination'] = init_paginate($cur_page_seg, $total_rows, $per_page, $per_page_seg, $data['pbase_url']); 
                
        $this->load->view('path/to/controller', $data);

Fourthly, an example function from out model file.

    function get_records($offset, $limit)
    {
               //add where clause processing here if required
        
        $this->db->select('*');    
        //$this->db->having($having);
        
                if ($offset != NULL)
            $this->db->offset($offset);
        if ($limit != NULL)
            $this->db->limit($limit);
        
        if ($limit == NULL && $offset == NULL)
        {
            $count = $this->db->get('my_table');
            return $count->result_array();
        }
        else
            return $this->db->get('my_table');
    }

Fifthly, our view file. Wherever you want the pagination to be, add this:

<div class="pagination" align="left">
    &lt;?php echo $pagination;?&gt;    
</div>
&lt;!-- this is the number of records part - an example only --&gt;
<div align="right"><a href="&lt;?=$pbase_url;?&gt;/10">10</a> | <a href="&lt;?=$pbase_url;?&gt;/20">20</a></div>

Sixthly, and also lastly, here is an example css file

div.pagination 
{
    padding: 3px;
    margin: 3px;
}

div.pagination a 
{
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #AAAADD;
    text-decoration: none; /* no underline */
    color: #000099;
}

div.pagination a:hover, div.pagination a:active 
{
    border: 1px solid #000099;
    color: #000;
}

div.pagination span.current 
{
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #000099;
    font-weight: bold;
    background-color: #993300;
    color: #FFF;
}
div.pagination span.disabled 
{
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #EEE;
    color: #DDD;
}

Hope it helps someone out there!

Clone this wiki locally