Bootstrap Pagination Buttons


Pagination?

You've seen this on various websites -- there's too much data to display on a single page. They may show 10 items, or 20 items, and at the bottom you will see a set of buttons (or simply anchor tags) that give specific page numbers, typically there are previous and next buttons, and some get more involved. If what you're looking for isn't on the current page, or you're just browsing through the information, you can go to another page, and so on.

Getting Started

The hardest thing about pagination is not really using the [Bootstrap pagination] buttons (that part isn't too bad), it's combining the pagination buttons with actual code to make the full pagination work.

What Does It Look Like?

The following is an example of what this should look like, assuming (details on what I mean by some of this below) let's say you have set the max pages to 10, you are set to display 10 records to a page, and you have 150 records in the table, and page 3 is currently the one being displayed (note that these buttons won't actually do anything if clicked ...):

Concepts

This one takes some conceptual work to understand, and I'm not all that great at drawing the pictures and so on, but what it really comes down to is that you have to determine a few things when a page loads, in order to use pagination properly.

The first is to know how much data you're going to be displaying. If you have an authorization field in your table, that says "this item is authorized, but this one isn't", you need to have a filter on the data (a "where" clause in MySQL). BUT, it's not quite that simple.

1) You have to know how many rows of that data you're going to display on the current page.

Let's use a simple example. If you have a database of customers and you are displaying a list of those customers, your typical SQL statement might be:

               select * from customers
            

But if you wanted to show just the first 10 customers, for example, you would need to limit the data:

               select * from customers limit 0,10
            

The limit option for MySQL says to start at a specific row (row 0 is the first row, which can be confusing -- most web software starts counting at 0 -- so the first row is 0, the second is 1 ...). You then tell it to limit the data to 10 records or rows.

If you have all your code set up properly (and we'll get to that), when the user clicks on the "Next" button, or perhaps a "page 2" button, the SQL command would need to change:

               select * from customers limit 10, 10
            

Remembering that the first "10" here is actually record number 11 out of the whole table, this would provided records 11 through 20. The "next" button again would need to change this to limit of "20,10", and so on.

2) You also need to determine the number of pages total that you're going to need. If you are displaying 10 records at a time, this is pretty easy, you determine at the outset how many rows you have in the table, and divide by 10. If you have 100 records in your table that's an easy one -- but otherwise, what about the remainder? EEK. There's always something, right? We'll come back to this, but it becomes important fairly early on.

3) If you have limited space at the bottom of the page (most people do), you will want to limit the number of pagination buttons at the bottom of the page, so you need to get an idea for how many pages you want buttons for.

Setup

These are the absolute basics. So, how do you start? My code is based on something I found digging around on the web, and modified (among other things to use those Bootstrap buttons, instead of having to come up with my own good-looking display in my stylesheet).

At the start of any page (or near the start) that uses pagination, I do the following (remember, this is PHP):

               $num_per_page = 10; // number of records/rows to display at a time
               $max_pages = 15;    // the largest number of page buttons you want to
                                   // display in the pagination area. This is useful
                                   // when the sql statement returns a huge amount
                                   // of information, the pagination area will
                                   // add "First" and "Last" buttons, and will change
                                   // the page numbers so that only max_pages number
                                   // will appear. 
         
               // Don't change these, they are important and should default to these
               // values:
               $start = 0;         // starting position in the table
               $page = 1;          // starting page number
            

Note that I tend to heavily document my code -- it saves me a lot of headache later trying to remember why I did something.

You have to deal with what happens when the user clicks on a pagination button. Normally I prefer using POST for passing data back and forth, but GET works here, and is easier, because otherwise you would have to set the whole page up as a form (or at least the pagination buttons) and deal with it that way. Sometimes the "easy" way is best.

So the next part of the code has to see if the user has reloaded the page by clicking a pagination button. The code below deals with that, although later this will be expanded a bit to deal with some other options as well.

               if( isset( $_GET['page'] ) )
               {
                  $page = $_GET['page'];
                  
                  // if empty, reset to starting point:
                  if ( $page == "" )
                  {
                     $page = 1;
                     $start = 0;
                  }
                  else
                  {
                     // if page is 2 and limit is 10, this would give us
                     // the starting point of (2-1) * 10 or 10
                     $start = $num_per_page * ( $page - 1 );
                  } // endif $page == ""
               } // endif user clicked a pagination button
            

The Data

The next thing to deal with is your data. I keep some very complex notes because I always trip over some of the dumbest things, such as MySQL requiring that some of the options be in a specific order -- it gets confused if they're not and throws out error messages that can be frustrating.

What I mean by that is if you are using the standard MySQL select statement, and you want to order the data, set a where clause, and use the limit that we need to for the pagination part of all this, the elements must be in the correct order.

               select (fields) from (table) where (condition) order by (field[s]) limit (options)
            

The items in parens are obviously what needs to be replaced in the command, but it should give you the idea.

In your code, you might want to do something like the following:

               $whereClause = "where amount_owed > 0";
               $orderBy     = "order by business_name";
               $command = "select * from customers " . $whereClause . " " . $orderBy;
            

If you know your SQL, you know the asterisk or star symbol is a wildcard stating to use all fields in the table. (Not always a good idea, depending on your table size, but ...)

If you know your PHP, you know the dots (.) are concatenation symbols, so you are building a string. I find sometimes it is useful to output that when testing things (especially if I get an error):

               echo $command . "<br />";
            

From here, things may look pretty weird, but you need to be able to not only get the data to display for the current page, but you also need to know how many records total are going to be output (based on your where clause), so you know how many pages (page buttons, etc.) will be used, and such.

               // and number of records to display:
               $command_p = $command; // need this one without limits
         
               // this is the most important as it limits the data displayed
               $command .= " limit " . $start . ", " . $num_per_page;
      
               // get number of rows for pagination, number of pages -- may not seem necessary
               // but absolutely vital:
               $result_p = mysqli_query( $connect, $command_p );
               $num_rows_p = mysqli_num_rows( $result_p );
               $num_pages = ceil( $num_rows_p / $num_per_page );
            

Briefly, the second command variable is so we know how many pages we have to deal with. So we're getting that value without the "limit" option, and then the standard sql command statement is getting the limit added to it, using the variables we defined earlier.

Note that for our purposes the $connect variable is created somewhere outside the code, included at the top of the .PHP file and deals with connecting to your database -- this is the object reference to that connection.

You would want to process the data as "usual", something like:

               $result = mysqli_query( $connect, $command );
               $num_rows = mysqli_num_rows( $result );
               if( $num_rows > 0 )
               {
                  // process your data:
                  while( $row = mysqli_fetch_array( $results ) )
                  {
                     echo "<p class='company_name'>Company Name: " . $row['business_name'] . "</p>";
                     // etc.
                  } // endwhile
               } // endif: num_rows > 0
            

The Actual Pagination Code

For the pagination code itself, which is below, please note that I tried to insert comments that were useful everywhere throughout. When I first got this code from a user online (I didn't manage grab the URL, or I would list the author and a reference here; I am pretty sure it was from a question on Stack Overflow, however), it was the only version of this that was readable and made sense to me. I modified it in places to make it work better for me, and now I'm pretty happy with it.

Interestingly, the Bootstrap pagination buttons are contained inside a "nav" tag, but each individual page button is a list item in an unordered list. It is possible to set a value to disabled, as you can see early on (which is handy as the user moves the mouse over it and not only is it greyed out, but the mouse pointer changes to a "not" symbol). You can set something to the active state which shows it is the current page. The code is quite involved and looks like it repeats, which it does, but each repeat is based on specific conditions.

               // if we have only one page, we don't need
               // the pagination code at all:
               if( $num_pages > 1 )
               {
                  echo '<center>';
                  echo '<nav>'; // Bootstrap navigation
                  echo '<ul class="pagination">'; // Bootstrap pagination 
                  
                  if( $num_pages > $max_pages )
                  {
                     // -------------------------------------------------------------------------------------
                     // first page button -- only if needed ...
                     if( $page <= 1 )
                     {
                        echo '<li class="disabled">
                                 <span aria-hidden="true">First</span>
                              </li>';
                     }
                     else
                     {
                        // First 'button' is active
                        echo '<li>
                                 <a href="' . $sFileName . '?page=' . '1" aria-label="First">
                                    First
                                 </a>
                              </li>';
                     }
                  } // need a 'first' button
                  
                  // ----------------------------------------------------------------------------------------
                  // Previous button
                  if( $page <= 1 )
                  {
                     echo '<li class="disabled">
                              <span aria-hidden="true">
                                 Previous
                              </span>
                           </li>';
                  }
                  else
                  {
                     // previous 'button' is active
                     echo '<li>
                              <a href="' . $sFileName . '?page=' . ($page-1) . '" aria-label="Previous">
                                  Previous
                              </a>
                           </li>';
                  }
                  
                  // --------------------------------------------------------------------------------------
                  //         PAGE Buttons are tricky, based on a variety of factors ... this works,
                  //         please don't mess with it!
                  // if number of pages <= max display of pages:
                  if( $num_pages <= $max_pages )
                  {
                     for( $i = 1; $i <= $num_pages; $i++ )
                     {
                        // if the counter is the same as the current page:
                        if( $i == $page )
                        {
                           // current page:
                           echo '<li class="active">
                                 <a href="' . $sFileName . '?page=' . $i . '">' . $i . '
                                    <span class="sr-only">' . $i . '</span></a>
                                 </a>
                              </li>';
                        }
                        else
                        {
                           // page # button -- active
                           echo '<li>
                                    <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
                                 </li>';
                        } // endif/else $i == $page
                     } // endfor 
                  }
                  else
                  {
                     // current page is less than 4
                     if( $page < 4 )
                     {
         
                        // total number of pages is < $max_pages
                        if( $num_pages < $max_pages )
                        {
                           for( $i = 1; $i <= $num_pages; $i++ )
                           {
                              // if the counter is the same as the current page:
                              if( $i == $page )
                              {
                                 // current page:
                                 echo '<li class="active">
                                 <a href="' . $sFileName . '?page=' . $i . '">' . $i . '
                                    <span class="sr-only">' . $i . '</span></a>
                                 </a>
                              </li>';
                              }
                              else
                              {
                                 // page # button -- active
                                 echo '<li>
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
                                       </li>';
                              } // endif/else $i == $page
                           } // endfor 
                        } // endif $num_pages < $max_pages
                        else // this would mean $num_pages => $max_pages?
                        {
                           // different loop similiar to above, but stopping at max_pages:
                           for( $i = 1; $i <= $max_pages; $i++ )
                           {
                              // counter = current page
                              if( $i == $page )
                              {
                                 // current page:
                                 echo '<li class="active">
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '
                                             <span class="sr-only">' . $i . '</span></a>
                                          </a>
                                       </li>';
                              }
                              else
                              {
                                 // page # button -- active
                                 echo '<li>
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
                                       </li>';
                              }
                           } // endfor
                        } // $num_pages => $max_pages
                     } // endif $page < 4
                     
                     // current page is > (total number of pages - 4)
                     else
                     {
                        if( $num_pages < $max_pages )
                        {
                           for( $i == 1; $i <= $num_pages; $i++ )
                           {
                              if( $i == $page )
                              {
                                 // current page:
                                 echo '<li class="active">
                                       <a href="' . $sFileName . '?page=' . $i . '">' . $i . '
                                          <span class="sr-only">' . $i . '</span></a>
                                       </a>
                                    </li>';
                              }
                              else
                              {
                                 // page # button -- active
                                 echo '<li>
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
                                       </li>';
                              } // endif $i == $page
                           } // endfor
                        } // endif $num_pages < $max_pages
                        else
                        {
                           // tricky -- going to the max number of pages - 1
                           for( $i = $num_pages - ($max_pages-1); $i <= $num_pages; $i++ )
                           {
                              if( $i == $page )
                              {
                                 // current page, no link needed:
                                 echo '<li class="active">
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '
                                             <span class="sr-only">' . $i . '</span></a>
                                          </a>
                                       </li>';
                              }
                              else
                              {
                                 // page # button -- active
                                 echo '<li>
                                          <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
                                       </li>';
                              } // endif ...
                           } // endfor
                        } // end else
                     } // end elseif $page > $num_pages-4
                  }  $num_pages <= $max_pages
                  
                  // --------------------------------------------------------------------------------------
                  // next button:
                  if( $page == $num_pages )
                  {
                     echo '<li class="disabled">
                              <span aria-hidden="true">
                                 Next
                              </span>
                           </li>';
                  }
                  else
                  {
                     echo '<li>
                              <a href="' . $sFileName . '?page=' . ( $page + 1 ) . '" aria-label="Next">
                                 Next
                              </a>
                           </li>';
                  } // next button
         
                  // -----------------------------------------------------------------------------------
                  //    LAST Page will only happen if needed ...
                  // do we need a "last page" button?
                  if( $num_pages > $max_pages )
                  {
                     // last page button -- only if needed ...
                     if( $page == $num_pages )
                     {
                        echo '<li class="disabled">
                                 <span aria-hidden="true">
                                 Last (' . $num_pages . ')
                                 </span>
                              </li>';
                     }
                     else
                     {
                        // last 'button' is active
                        echo '<li>
                              <a href="' . $sFileName . '?page=' . $num_pages . '" aria-label="Last Page">
                                 Last (' . $num_pages . ')
                              </a>
                           </li>';
                     }
                  }
                  echo '</ul>'; // Bootstrap pagination 
                  echo '</nav>'; // Bootstrap navigation class
                  echo '</center>';
               } // endif $num_pages > 1
            

Hope you're not feeling overwhelmed. As noted, the code repeats itself a lot. Basically the sequence works like this:

  1. We check to see if we need to display any pagination buttons at all. really simply by checking to see if the number of pages is greater than 1.
  2. If yes, we center, we set up the outer code that contains the pagination buttons.
  3. Then we have to look at a combination of factors. If the number of pages we can display is greater than the maximum number of pages, we're going to need a "First" button. If we do, we check to see if we're on the first page or not, and if yes, we disable the first button, otherwise it is active ...
  4. We do the same for the previous button, although we assume we need this one, so there will always be a "previous" button.
  5. The individual page buttons is where things get tricky. These are the ones with just a number on them. Again, this code repeats based on several different possible permutations. Read the code carefully, trying to explain every permutation here is a bit complex.
  6. Once we get past all that, we check to see if the next button needs to be enabled or disabled, depending on which page we're on.
  7. Next to last, we check to see of a "Last" button is needed. For a situation where you have a lot of data, this will be useful. Among other things it shows that the last page is a specific page number.
  8. Finally the code closes off the container code (the end of the unordered list, the end of the nav container, and finally the end of the centering tag).

Didn't Want To Read All That?

I understand ... it's complicated. There are a few things you need to understand, however.

First, this code relies on a variable called $sFileName. This variable is not defined in the code above. However, it is defined in starting code for every PHP file in my site. What it looks like is simply this:

                  // current filename without path:
                  $sFileName = basename( $_SERVER['SCRIPT_FILENAME'] );
            

The next thing you need to understand is what the code scattered all over the place is actually streaming out. Under each "button" is an anchor tag that will link back to the same file, passing the parameter of "page":

               <a href="' . $sFileName . '?page=' . $i . '">' . $i . '</a>
               
               //becomes when PHP evaluates and streams it out:
               <a href="MyProgram.php?page=10">10</a>
            

When a user clicks on that particular button, it will reload the page, passing the name/value pair of "page=10", which can be intercepted as shown earlier on this page with PHP using: $_GET['page'].

Download?

Why yes, this one is complex enough, that I have a download for you. This file has a LOT of comments in it, more than what was shown here. There is code in there that deals with setting search options on a page and retaining those options, the same for retaining options for sort orders if you allow your users to modify those.

The actual download: [pagination.php]

Save it to a test area, and experiment.

If This is Useful

Please remember to put in your code a link back to here (at the least) so I get some credit for the work I did putting it together. I hope this makes sense. Please feel free to click the "Contact" link in the menu at the top and drop me a note.