How to introduce pagination to Movable Type blog entries

The pagination tutorial I present here

Sample pagination for an index page.
  • does not require any PHP support (as MTPagniate does)
  • gives you full control over the pagination styles
  • can be used to paginate any page (index, category archive, blog entry, date archive etc.)
  • achieves pagination using free plugins
  • does not require you to modify your Movable Type installation
  • requires only little JavaScript to switch between pages
Movable Type 4 logo

Follow this step-by-step guide to set up pagination in your blog in less than an hour!

Update: This pagination technique also works in Movable Type 4.

Movable Type pagination - how it works

If you don't want to know how it works you can skip directly to the Step-by-Step guide to Movable Type pagination.

Movable Type (as of its version 4) does still not offer any pagination out of the box. Worse still, Movable Type does not know much about arithmetic either. Hence our first task it to install some plugins to teach Movable Type some maths which we need for our pagination control.

Server-side Pagination

While it is possible to write a pure JavaScript pagination, server-side pagination is far more efficient because your users' browser has less work to do and can render pages faster.

The pagination technique presented here creates <div>s which serve as a virtual 'page'. All of these divs except the first are initially hidden so the user sees the first 'page'.

Below all divs are the pagination links. A click on any pagination link lets JavaScript code hide all divs and reveal just that div which is associated with the page link the user clicked on. To make this pagination technique work we assign ids to each page div which we then can easily reference with JavaScript.

If you know a bit of JavaScript you can follow this pagination tutorial very easily. The most complex part is when we try to teach Movable Type to execute a simple if(a or b)then-else statement.

Step 1: Get the plugins required for pagination

Download and install the following extension and plugins:

Expressions is the foundation we need for Arithmetic to work and Compare adds some compare logic to Movable Type. After you've copied the files to your MT installation open the plugin page to make sure Movable Type recognised them.

Movable Type plugins required for pagination.

Expressions does not show up here since it's not a plugin but a library rather.

Finally, download the pagination JavaScript file [4kB].

Step 2: Add pagination to your index templates

The pagination I describe here works for both index and individual archive pages. Let's have a look at index pages first which includes category index and date index pages as well as category archive and date archive pages. Note that for category index pages you have to replace the Movable Type variable 'MTBlogEntryCount' with 'MTArchiveCount' (thanks to Adam Portilla for this hint).

Step 2a: Prepare pagination in the header

We are going to define the variables in the header of your template to keep them all together and be able to hand over some settings to the JavaScript routine. Add this code to your <head> section:

<script type="text/javascript">
   <!-- Prepare pagination -->
   <MTSetVar2 name='bWritePage' value='true'>        <MTIgnore>Boolean var which indicates if MT creates a new page div </MTIgnore>
   <MTSetVar2 name='iPageNo' value='0'>              <MTIgnore>Counts the total number of pages generated </MTIgnore>
   <MTSetVar2 name='iMaxEntriesPerPage' value='10'>  <MTIgnore>After iMaxEntriesPerPage a new page is created </MTIgnore>
   <MTSetVar2 name='iEntryNo' value='0'>             <MTIgnore>Counts the overall number of comments </MTIgnore>
   <MTSetVar2 name='iEntryPageCounter' value='0'>    <MTIgnore>Counts the number of comments already written for a page </MTIgnore>
   // Prepare variable for pagination JavaScript:
   var iNumberOfEntries = <$MTBlogEntryCount$>;
   var iNumberOfPages = Math.ceil(<MTDivide x="[MTBlogEntryCount]" y="[MTGetVar2 name='iMaxEntriesPerPage']">);
</script>
<script type="text/javascript" src="/js/pagination.js"></script>

I've used the Movable Type tag <MTIgnore> to add comments throughout the code. This way they don't show up in the published pages.

Using <MTSetVar2> we set one Boolean (b) and a few Integer (i) variables. You should only be concerned about iMaxEntriesPerPage where you can set after how many entries pagination should kick in. We then pass on the number of entries to JavaScript and calculate how many pages we would need according to the maximum number of entries per page. (Unfortunately we cannot pass this value back to Movable Type.)

Remember that you might need to adjust the path to the pagination JavaScript file you downloaded in Step 1.

Step 2b: Add pagination to the entries loop

Now locate where you have your <MTEntries> loop in your template. Pagination starts right after that tag. I'll explain the code in chunks. If you cannot wait, skip to the full pagination code.

<a name="entries"></a>
<MTEntries>
   <MTAddVar name="iEntryNo" value="1">
   
   <MTIfEqual a="[MTGetVar2 name='bWritePage']" b="true">
      <MTAddVar name="iPageNo" value="1">
      <div class="pageprint" id="contentSwap<MTGetVar2 name='iPageNo'>">
   </MTIfEqual>

We mark this section with an anchor which we'll need when pagination jumps to this location of your page. You'll find this name also in the JavaScript routine later on.

The first statement reads 'iEntryNo = iEntryNo + 1' in the language of the Arithmetics plugin and gives us the current entry number.

Next is an if-comparision. If our boolean variable bWritePage is true we need to open the div which constitutes a page. In doing so we also increment the counter which keeps track of the pages we've already written. The div's id is necessary for the JavaScript routine used later to make individual divs visible or hide them.

   <div class="blog-entry">
      <h2><$MTEntryTitle$></h2>
      <p>Posted by <$MTEntryAuthorNickname$>
         on <$MTEntryDate format="%A %e %B, %Y %I:%M %p"$>
      </p>
      <$MTEntryBody convert_breaks="1"$>
      <p><a href="<$MTEntryPermalink$>" title="More">More</a> | 
               <a href="<$MTEntryPermalink$>#comments" title=""">Comments (<$MTEntryCommentCount$>)</a>
      </p>
   </div>

Now we write the entry which might be an index entry or an entry of a category index or date index. This is totally up to you to configure. You might want to modify this code to match your own template. The code above has nothing to do with pagination so you can change at will. Make sure you close your tags though.

   <MTAddVar name='iEntryPageCounter' value='1'>

After each entry we increase the counter which counts the entries per page. This is vital so we can decide later when to start a new page.

Prepare yourself now for a bit of ugly code:

   <MTSetVar2 name='bExecuteIf' value='false'>
   <MTSetVar2 name='bExecuteElse' value='true'>
   
   <MTIfGreaterOrEqual a="[MTGetVar2 name='iEntryPageCounter']" b="[MTGetVar2 name='iMaxEntriesPerPage']" numeric="1">
      <MTSetVar2 name='bExecuteIf' value='true'>
   </MTIfGreaterOrEqual>
   
   <MTIfGreaterOrEqual a="[MTGetVar2 name='iEntryNo']" b="[MTBlogEntryCount]" numeric="1">
      <MTSetVar2 name='bExecuteIf' value='true'>
   </MTIfGreaterOrEqual>
   
   <MTIfEqual a="[MTGetVar2 name='bExecuteIf']" b="true">
      <MTSetVar2 name='bWritePage' value='true'>
      <MTSetVar2 name='iEntryPageCounter' value='0'>
      <MTSetVar2 name='bExecuteElse' value='false'>
   </MTIfEqual>
   
   <MTIfEqual a="[MTGetVar2 name='bExecuteElse']" b="true">
      <MTSetVar2 name='bWritePage' value='false'>
   </MTIfEqual>

Having written an entry we need to decide if we have to close the page. Two conditions would require this:

  1. With the current entry we have reached the maximum number of entries allowed on a page, or
  2. The current entry is the last entry of your list.

So we have to program: if (1. or 2.) then (close page) else (don't close page).

The code above is what it takes to create this simple statement in Movable Type. Unless there's a more powerful arithmetic plugin we have to put up with this. Here's what it does:

  • Set default to execute the 'else' part of the comparison (don't close page)
  • Check if the number of entries of the current page is greater or equal to the maximum number of entries for a page. If so, enable execution of 'then' part (and close the page)
  • Check if the current entry is in fact the last one. If so, enable 'then' part as well (close page)
  • Perform the actual if-then-else statement:
    If we need to close the page,
    • set the boolean variable to true
    • reset the counter which counts the entries of a page
    • inhibit execution of the 'else' part
    If we don't need to close the page,
    • set the boolean variable to say so

Since we initialised our boolean variable to true to get the opening div of our first page we need to set it to false here to keep the page open for subsequent entries.

All that remains to be done now is close the page if the previous if-then-else statement has decided so. This concludes the loop over the entries:

   <MTIfEqual a="[MTGetVar2 name='bWritePage']" b="true">
      </div><!-- class="pageprint" -->
   </MTIfEqual>
</MTEntries>

Step 2c: Add JavaScript to display pagination links

Now that we've used Movable Type to prepare all the page divs we can send the page to the user. We use JavaScript to generate an unordered list of page links which switch the divs.

Add this code right below the previous code:

   <div class="pagination-controls">
      <script type="text/javascript">
         if ( <MTGetVar2 name='iPageNo'> > 1 ) {
            if (sPagination) { document.write(sPagination); }
            if (iCurrentPage && iNumberOfPages && (bIsFullPage == false) ) {
                content_swap(iCurrentPage,iNumberOfPages);
            }
         }
      </script>
   </div>

When Movable Type processes the page it replaces the MTGetVar2 statement with the content of the iPageNo variable. At the end of our loop iPageNo gives us the last page number we created. Only if this page number is greater than one need we to display pagination links.

If pagination links need to be displayed the code writes the pagination string which was created using the JavaScript code you referenced in the header of your page.

As the user navigates on the page the second if statement checks if all relevant variables are set before it calls the function which shows or hides our pagination divs.

Pagination code to copy&paste

<MTEntries>
   <MTIgnore> Increment the entry counter: </MTIgnore>
   <MTAddVar name="iEntryNo" value="1">
   
   <MTIgnore> Write a new page div if we have to and increment page counter: </MTIgnore>
   <MTIfEqual a="[MTGetVar2 name='bWritePage']" b="true">
      <MTAddVar name="iPageNo" value="1">
      <div class="pageprint" id="contentSwap<MTGetVar2 name='iPageNo'>"><a name="contentSwap<MTGetVar2 name='iPageNo'>"></a>
   </MTIfEqual>
   
   <MTIgnore> Write the entry: </MTIgnore>
   <div class="blog-entry">
      <h2><$MTEntryTitle$></h2>
      <p>Posted by <$MTEntryAuthorNickname$>
         on <$MTEntryDate format="%A %e %B, %Y %I:%M %p"$>
      </p>
      <$MTEntryBody convert_breaks="1"$>
      <p><a href="<$MTEntryPermalink$>" title="More">More</a> | 
               <a href="<$MTEntryPermalink$>#comments" title="">Comments (<$MTEntryCommentCount$>)</a>
      </p>
   </div>
      
   <MTIgnore> Remember how many entries we've already written (so we'll know when to start a new page): </MTIgnore>
   <MTAddVar name='iEntryPageCounter' value='1'>
      
   <MTIgnore> Check if we need to close a page (when iMaxEntriesPerPage or last entry is reached).
              This rather ugly construct mimicks an if(a or b)-then-else construct: </MTIgnore>
   <MTSetVar2 name='bExecuteIf' value='false'>
   <MTSetVar2 name='bExecuteElse' value='true'>
   <MTIfGreaterOrEqual a="[MTGetVar2 name='iEntryPageCounter']" b="[MTGetVar2 name='iMaxEntriesPerPage']" numeric="1">
      <MTSetVar2 name='bExecuteIf' value='true'>
   </MTIfGreaterOrEqual>
   <MTIfGreaterOrEqual a="[MTGetVar2 name='iEntryNo']" b="[MTBlogEntryCount]" numeric="1">
      <MTSetVar2 name='bExecuteIf' value='true'>
   </MTIfGreaterOrEqual>
   <MTIfEqual a="[MTGetVar2 name='bExecuteIf']" b="true">
      <MTSetVar2 name='bWritePage' value='true'>            <MTIgnore> Now we need to close the page... </MTIgnore>
      <MTSetVar2 name='iEntryPageCounter' value='0'>        <MTIgnore> ...so reset the counter of entries per page </MTIgnore>
      <MTSetVar2 name='bExecuteElse' value='false'>         <MTIgnore> ...and inhibit execution of else. </MTIgnore>
   </MTIfEqual>
   <MTIfEqual a="[MTGetVar2 name='bExecuteElse']" b="true">
      <MTSetVar2 name='bWritePage' value='false'>           <MTIgnore> We don't need to close the page div yet. </MTIgnore>
   </MTIfEqual>

   <MTIgnore> Check if we need to close the page div: </MTIgnore>
   <MTIfEqual a="[MTGetVar2 name='bWritePage']" b="true">
      </div><!-- class="pageprint" -->
   </MTIfEqual>
      
</MTEntries>

<div class="pagination-controls">
   <script type="text/javascript">
      if ( <MTGetVar2 name='iPageNo'> > 1 ) {
         if (sPagination) { document.write(sPagination); }
         if (iCurrentPage && iNumberOfPages && (bIsFullPage == false) ) {
             content_swap(iCurrentPage,iNumberOfPages);
         }
      }
   </script>
</div>

Step 3: Style your blog pagination

Here is the CSS that styles your pagination controls as shown. Feel free to modify it to your needs. The lines starting with '* html' are for Internet Explorer 6 only.

.pagination-controls {height:24px;margin:0.5em 0}
.pagination-controls ul {list-style-type:none;color:#000;float:right;font-size:11px;font-weight:bold;
                         font-family:Verdana, Arial, Helvetica, sans-serif;line-height:2.2;overflow:hidden;width:auto}
.pagination-controls li {float:left;height:24px;line-height:2.2;width:auto}
.pagination-controls li a {display:block;height:24px;overflow:hidden;text-decoration:none;padding:1px 7px 7px}
.pagination-controls li a:hover,.pagination-controls li.pagecount a:hover,.pagination-controls li.viewAll a:hover 
                        {background-color:#3467bb;color:#fff}
.pagination-controls li.pagecount {border-left:1px solid #ccc;border-right:1px solid #ccc;height:auto;line-height:2.7;
                         margin:-2px 5px 0pt;overflow:visible;text-align:left;padding:0 4px 0 5px}
.pagination-controls li.pagecount a {background:transparent none repeat scroll 0%;color:#023985;display:inline;margin:0pt;
                         padding:6px 7px 7px;text-indent:0}
.pagination-controls li.pagecount em {border:1px solid #3467BB;color:#3467BB;margin:0pt;padding:4px 6px 5px;font-weight:bold;
                         font-style:normal}
.pagination-controls li.viewAll a {text-align:center;text-indent:0pt;width:140px}
* html .pagination-controls li.previous a {width:57px;height:29px}
* html .pagination-controls li.next a {width:30px;height:20px}
* html .pagination-controls li.viewAll a {height:20px}
Movable Type pagination

Step 4: Pagination for comments on individual archive pages

Besides index pages you might want to paginate the comments on individual archive pages. If you have posts which attract many comments pagination might make it easier to navigate the page.

You can copy and paste the same code as presented for the index pages. Then do the following modifications.

  1. In the header section, replace the two JavaScript declarations to consider comments rather than entries:
    var iNumberOfEntries = <$MTEntryCommentCount$>;
    var iNumberOfPages = Math.ceil(<MTDivide x="[MTEntryCommentCount]" y="[MTGetVar2 name='iMaxEntriesPerPage']">);
    
  2. Modify the section 'Write the entry' to write the comment body and commenter's name.
  3. Change the condition of the if-statement to check against the entry's comment count:
    <MTIfGreaterOrEqual a="[MTGetVar2 name='iEntryNo']" b="[MTEntryCommentCount]" numeric="1">
    
  4. You might also want to change 'View all X entries' to 'View all X comments' in the JavaScript file.

Caution with comment pagination!

If you have an unpaginated individual entry page you usually identify each comment with a unique id in the HTML tag. This way you can link to them individually. With pagination implemented these links don't work anymore if the linked comment is on any other page than the first because these divs are hidden.

To solve this problem you need to link to the individual comment using the full page mode of the pagination script.

If your link was before:

<a href="<$MTEntryPermalink$>#<$MTCommentID$>">Link to good comment</a>

you now need to write:

<a href="<$MTEntryPermalink$>?page=fullpage#<$MTCommentID$>">Link to good comment</a>

Happy? Do you have feedback? Shoot me an email!