CodeIgniter: Friendly pagination with search system

by Vicente Russo Neto on January 29, 2009

After a long pause, here am I. Well, there’s a few tutorials around, including the official CI wiki suggesting a more friendly pagination implementation, with page numbers on the link, because the actual CI pagination works with the offset from the database. The code I use today is from the official wiki, but the search system I implemented myself, So, here we go…

First, on the original file, we have the create_link() function. On mine, there is an argument $search, it will be permanent on every link, just like this: http://www.server.com/products/page/2/notebook-sony

I modified the ident style from CI Pagination.php, I Just can’t work with the Allman ident style (more info info: a href=”http://en.wikipedia.org/wiki/Indent_style”>wikipedia). Analising the whole code, there is no big tricks, I just concatenate the search argumento in the end of links.

Pagination.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * CodeIgniter
 *
 * An open source application development framework for PHP 4.3.2 or newer
 *
 * @package     CodeIgniter
 * @author      ExpressionEngine Dev Team
 * @copyright   Copyright (c) 2008, EllisLab, Inc.
 * @license     http://codeigniter.com/user_guide/license.html
 * @link        http://codeigniter.com
 * @since       Version 1.0
 * @filesource
 */


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

/**
 * Pagination Class
 *
 * @package     CodeIgniter
 * @subpackage  Libraries
 * @category    Pagination
 * @author      ExpressionEngine Dev Team
 * @link        http://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         = '&lsaquo; First';
    var $next_link          = '&gt;';
    var $prev_link          = '&lt;';
    var $last_link          = 'Last &rsaquo;';
    var $uri_segment        = 3;
    var $full_tag_open      = '';
    var $full_tag_close     = '';
    var $first_tag_open     = '';
    var $first_tag_close    = '&nbsp;';
    var $last_tag_open      = '&nbsp;';
    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      = '';
    var $page_query_string  = FALSE;
    var $query_string_segment = 'per_page';

    /**
     * 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($search='') {
   
        if ($search!='') $search = '/'.$search;
       
        // 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->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE){
            if ($CI->input->get($this->query_string_segment) != 0){
                $this->cur_page = $CI->input->get($this->query_string_segment);
                // Prep the current page - no funny business!
                $this->cur_page = (int) $this->cur_page;
            }
        } else {
            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 = (int) $this->cur_page;
            }
        }
       
        if ( ! is_numeric($this->cur_page)) {
            $this->cur_page = 0;
        }
       
        if ($this->cur_page==0) {
            $this->cur_page = 1;
        }
       
        // Is the page number beyond the result range?
        // If so we show the last page
        if ($this->cur_page > $this->total_rows) {
            $this->cur_page = ($num_pages - 1) * $this->per_page;
        }
       
        $uri_page_number = $this->cur_page;
       
        // Calculate the start and end numbers. These determine
        // which number to start and end the digit links with
        $start = (($this->cur_page - $this->num_links) > 0) ? $this->cur_page - ($this->num_links - 1) : 1;
        $end = (($this->cur_page + $this->num_links) < $num_pages) ? $this->cur_page + $this->num_links : $num_pages;
       

        // Is pagination being used over GET or POST?  If get, add a per_page query
        // string. If post, add a trailing slash to the base URL if needed
        if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) {
            $this->base_url = rtrim($this->base_url).'&amp;'.$this->query_string_segment.'=';
        } else {
            $this->base_url = rtrim($this->base_url, '/') .'/';
        }
       
        // And herwe go...
        $output = '';
       
        // Render the "First" link
        if ($this->cur_page > $this->num_links) {
            $output .= $this->first_tag_open.'<a href="'.$this->base_url.'1'.$search.'">'.$this->first_link.'</a>'.$this->first_tag_close;
        }
       
        // Render the "previous" link
        if (($this->cur_page - $this->num_links) >= 0) {
            $i = $this->cur_page - 1;
            if ($i == 0) $i = '';
            $output .= $this->prev_tag_open.'<a href="'.$this->base_url.$i.$search.'">'.$this->prev_link.'</a>'.$this->prev_tag_close;
        }
       
        // Write the digit links
        for ($loop = $start -1; $loop <= $end; $loop++) {
            $i = ($loop * $this->per_page) - $this->per_page;
            if ($i >= 0) {
                if ($this->cur_page == $loop) {
                    $output .= $this->cur_tag_open.$loop.$this->cur_tag_close; // Current page
                } else {  
                    $n = ($i == 0) ? '1' : $loop;
                    $output .= $this->num_tag_open.'<a href="'.$this->base_url.$n.$search.'">'.$loop.'</a>'.$this->num_tag_close;
                }
            }
        }
       
        // Render the "next" link
        if ($this->cur_page < $num_pages) {
            $output .= $this->next_tag_open.'<a href="'.$this->base_url.($this->cur_page + 1).$search.'">'.$this->next_link.'</a>'.$this->next_tag_close;
        }
       
        // Render the "Last" link
        if (($this->cur_page + $this->num_links) < $num_pages) {
            $i = $num_pages;
            $output .= $this->last_tag_open.'<a href="'.$this->base_url.$i.$search.'">'.$this->last_link.'</a>'.$this->last_tag_close;
        }
       
        // Kill double slashes.  Note: Sometimes we can end up with a double slash
        // in the penultimate link so we'll kill all double slashes.
        $output = preg_replace("#([^:])//+#", "\\1/", $output);
       
        // Add the wrapper HTML if exists
        $output = $this->full_tag_open.$output.$this->full_tag_close;
       
        return $output;
    }
}
// END Pagination Class

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

Copy the above file inside the application/libraries, CI try to find the files there first, and if not found, it uses from the core library. Well, on controller I use three functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
function index() { // Forcing the use of page function...
    redirect('products/page/1');
}
   
/* Function search()
* Search function of the controller. If you have a "product" controller, the
* action form from of your view will be something like action="product/search"
*/

function search() {
    $terms = array();
    /*
    * Here you have to adjust your "uri segment",
    * if your controller is inside a directory
    */

    if ($this->uri->segment(3)!='') {
        /*
        * Here, I suppose I already have a search running...
        */

        $search = $this->uri->segment(3);
        $search = str_replace(' ','-',$search);
        valid_char_only($search); // Function to remove accents and invalid characters, use any function
        // Preparando array
        $aux = explode('-',$search);
        foreach($aux as $key => $valor) {
            $terms[] = $valor;
        }
    } else {
        /*
        * Here I get the value from the field (myField) and split if there is
        * more than 2 words, and put an hyphen separating the words.
        */

        $search = $this->input->post('myField');
        $search = str_replace(' ','-',$search);
        valid_char_only($search); // Function to remove accents and invalid characters, use any function
        // Preparando array
        $aux = explode('-',$search);
        foreach($aux as $key => $valor) {
            $terms[] = $valor;
        }
    }
   
    /*
    * You see, there is no database query in here, this function
    * only format the url and send it to the controller that does
    * everything, the "page" controller
    */

    redirect('product/page/1/'.$search);
   
}

function page($page='',$terms='') {
    /*
    * If there is no page on argument, I force using the page 1
    */

    if ($page=='') {
        redirect('produto/page/1');
    }
   
    /*
    * On my application, the segment 4 is the search string
    */

    if ($this->uri->segment(4)!='') {
        $terms = $this->uri->segment(4);
    }
   
    $this->load->library('pagination');
   
    $limit = 20;
    $offset = ($page-1)*$limit;

    if ($terms=='') {
        /*
        * If there is no search, I do the normal query
        */

        $this->db->limit($limit, $offset);
        $query = $this->db->get('products');
        $data['total'] = $query->num_rows();
    } else {
        /*
        * Preparing the search, spliting the terms and inserting
        * one LIKE for each field on database
        */

        $search = str_replace(' ','-',$terms);
        // Preparing array
        $mysearch = array();
        $aux = explode('-',$search);
        foreach($aux as $key => $valor) {
            $mysearch[] = $valor;
        }
       
        /*
        * On this example, I'm using only one search field,
        * but looking for 3 fields on table on same time, name, id, and description.
        */

        $this->db->like('name', $mysearch[0]);
        $this->db->or_like('id', $mysearch[0]);
        $this->db->or_like('description', $mysearch[0]);
       
        $tt = count($mysearch);
        if ($tt>0) {
            for ($j=1;$j<$tt;$j++) {
                $this->db->or_like('name', $mysearch[$j]);
                $this->db->or_like('id', $mysearch[$j]);
                $this->db->or_like('description', $mysearch[$j]);
            }
        }
       
        /*
        * Here I recover the total number of products,
        * just to show the total on my view;
        */

        $query_tt = $this->db->get('products');

        $this->db->like('name', $mysearch[0]);
        $this->db->or_like('id', $mysearch[0]);
        $this->db->or_like('description', $mysearch[0]);

        $tt = count($mysearch);
        if ($tt>0) {
            for ($j=1;$j<$tt;$j++) {
                $this->db->or_like('name', $mysearch[$j]);
                $this->db->or_like('id', $mysearch[$j]);
                $this->db->or_like('description', $mysearch[$j]);
            }
        }
       
        $this->db->limit($limit, $offset);
        $query = $this->db->get('products');
        $data['total'] = $query_tt->num_rows();
   
    }

    $config['base_url'] = base_url().INDEX.'product/page/';
    $config['total_rows'] = $data['total'];
    $config['per_page'] = $limit;
    $config['uri_segment'] = 3;
    $config['num_links'] = 5;
   
    $this->pagination->initialize($config);
   
    $data['num_of_pges'] = round($data['total'] / $limit);
    $data['products'] = $query;
   
    $this->load->view('products',$data);
}

Well, using this class, your website will search things in the following format:

http://www.server.com/products/page/13/notebook-sony

http://www.server.com/products/page/10/cannon-digital-cam

Just to mention, I didn’t tested with query strings on! ;)

Did you like this? Share it:

{ 7 comments… read them below or add one }

padom March 24, 2009 at 3:49 pm

Not work for me, probable the segmet from the post of search. Is not there. Can you please, sent me all files for pagination ?

Harpreet Bhatia August 23, 2009 at 11:49 pm

Hello guys the above worked a bit.. but needs few changes.
follow this, i have successfully done it
http://codeigniter.com/forums/viewthread/120770/

follow me on twitter http://www.twitter.com/bluepicaso

Julian October 9, 2009 at 11:41 pm

Excellent! Nice tutorial.

huglester October 19, 2009 at 4:00 pm

Hello

your tutorial is good :)

can you share valid_char_only function also?:) thanks

saijin March 28, 2010 at 9:21 pm

Can you give me a hint on how can I show the result on the VIEW page? Thanks

GiN January 20, 2011 at 1:10 pm

You can use SQL_CALC_FOUND_ROWS instead of double …->get(‘products’);
It will increase search speed. You should try.

Vicente Russo Neto January 20, 2011 at 1:15 pm

I’ll try, thanks for sharing!

Leave a Comment