Pagination for Static Templates
Note: The archive pagination feature was added in MT4.3 and MT5.
Movable Type publishes archives of entries based upon date, category, author and combinations thereof, and also creates links to navigate between these archives. This is done to minimize the amount of publishing when content changes. But this solution is not ideal because based upon the frequency of publishing the number of entries on each archive may vary significantly.
Many desired to have a solution that displayed a standard number of entries per page with links to navigate chronologically through them. To provide this solution, many people have created multiple index templates, one per page of entries. Using a combination of the lastn
and offset
attributes of the mt:Entries
tag and hard-coding navigation between the index templates they were able to create “pagination”.
There were a few drawbacks of to using multiple index templates as a pagination solution:
- Increased publishing times. Each time a new entry is published all of the index templates have to be republished as all of the entries are shifted back one page.
- Doesn’t Scale. Once the number of published entries divided by the number of entries per page exceeds the number of of “static pagination” index templates. Once a viewer navigates to the last page, if there are older entries they must now view the entries in date-based archives.
Pagination in Movable Type 4.3 and 5.x
The pagination solution added in Movable Type 4.3 uses static publishing for the first page and then uses the MT-Search scripts to dynamically render pages 2 through N. Pagination can be used on index or archive template.
Note: This method of pagination can be combined with the above static pagination solution by passing a hard-coded offset once jumping from the statically published pages to the dynamically published pages.
Below is an overview of the new search parameters for pagination and some sample templates showing how to implement pagination.
Warning: If there are multiple
mt:Entries
loops in a template, use thesearch_results
attribute on themt:Entries
block tag to define whichmt:Entries
block to use:<mt:Entries limit="$entries_per_page" search_results="1"> <!-- content tags here --> </mt:Entries>
Security
Entry pagination passes the request to the search template in order to display the search results. Search requires pages to be displayed using CGI, thus if PHP/ASP code is used in these templates it can’t be executed and data not designed to be viewed by the end user will be displayed. Although there are ways to run PHP under CGI, the following barriers have been put in place:
- Only allow the templateid parameter when the archivetype parameter exists.
- Force the template being used to match the archive type (e.g. if you’re trying to paginate category archives, the template you’re using has to be one that is producing category archives).
- Not allow the use of the template_id parameter when the extension is php or asp.
- SearchAlwaysAllowTemplateID config directive when set to 1 will allow the use of template_id to not have to match the Archive Type (and Template Type for index templates).
Conditioning Static and Dynamic Content is possible if PHP/ASP is used in templates can be conditioned.
Search Parameters for Pagination
archive_type
author
category
- Date based:
year
/month
/day
page
template_id
archive_type
The archive type of template being paginated.
The value “Index” may also be used to indicate use of an index template which is identified by the template_id
parameter.
author
The author ID. Used to filter results by author. See template_id tip for finding the author ID.
category
The category ID. Used to filter results by category. See template_id tip for finding the category ID.
day
The 2-digit day of entries to display in a date-based archive (format: DD)
month
The 2-digit month of entries to display in a date-based archive (format: MM)
page
The page of results to display; based upon the entries per page variable and the total entries.
The value of page
is used to compute the offset
. If the limit
(ie. entries_per_page
in the code below) is 10 and page 3 is desired then page 3 will show entries 21 to 30, thus the offset is 20.
template_id
The ID of the template to use for displaying the queried entries.
Tip: Find the ID of a category, author or template by looking for the
id
querystring parameter in an application URL.To use a template ID, view the template edit screen, the URL would look like this:
http://yourdomain/cgi-bin/mt/mt.cgi?__mode=view&_type=template&id=384&blog_id=1
In this case, the template ID would be 384.
year
The 4-digit year of entries to display in a date-based archive (format: YYYY)
Conditioning Static and Dynamic Content
The <mt:Entries>
loop tag will be replaced with the queried entries and the value of the variable search_results
can be used to condition content in the template.
For example, place this code in an index template being used for pagination to condition content based upon if the page is statically or dynamically rendered.
<mt:If name="search_results">
Search Results are being displayed! Woot!
<mt:Else>
Static content from normal publishing of the index template.
</mt:If>
Step by Step Tutorials and Sample templates
Note: These templates have been tested, but have not gone through a full QA review thus they were not officially bundled with any default template set.
This code is based on the Classic Blog template set, but can be used in other template sets.
Two tutorial and sample templates are available:
- Paginating the Main Index : Step by Step tutorial
- Entry Listing (for archive listing templates)
Sample Templates : Main Index
Replace the Main Index template (or any index template) with this code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
<head>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="<$mt:Link template="rsd"$>" />
<title><$mt:BlogName encode_html="1"$></title>
<$mt:Include module="HTML Head"$>
</head>
<body id="<$mt:BlogTemplateSetID$>" class="mt-main-index <$mt:Var name="page_layout"$>">
<div id="container">
<div id="container-inner">
<$mt:Include module="Banner Header"$>
<div id="content">
<div id="content-inner">
<div id="alpha">
<div id="alpha-inner">
<mt:Ignore><!-- Set the number of entries displayed on each page. --></mt:Ignore>
<$mt:Var name="entries_per_page" value="3"$>
<mt:Ignore><!-- Construct the url for querying entries. --></mt:Ignore>
<mt:SetVarBlock name="search_link">
<$mt:CGIPath$><$mt:SearchScript$>?IncludeBlogs=<$mt:BlogID$>
&template_id=<$mt:BuildTemplateID$>
&limit=<$mt:Var name="entries_per_page"$>
&archive_type=Index
&page=
</mt:SetVarBlock>
<mt:Ignore><!-- Strip spaces and trim value. --></mt:Ignore>
<$mt:Var name="search_link" strip="" trim="1" setvar="search_link"$>
<mt:Ignore><!-- Entries loop for publishing static and dynamic pages. --></mt:Ignore>
<mt:Entries limit="$entries_per_page" search_results="1">
<mt:Ignore><!-- Use the Entry Summary module for each entry published on this page. --></mt:Ignore>
<$mt:Include module="Entry Summary"$>
</mt:Entries>
<mt:Ignore><!-- Create pagination navigation. Condition based upon if page is statically or dynamically rendered using the search_results variable. --></mt:Ignore>
<mt:SetVarBlock name="pagination_navigation">
<mt:If name="search_results">
<mt:Ignore><!-- Navigation for dynamic pages (same as navigation found in the Search Results system template). --></mt:Ignore>
<mt:IfPreviousResults>
<a href="<$mt:PreviousLink$>" rel="prev" onclick="return swapContent(-1);">< Previous</a>
</mt:IfPreviousResults>
<mt:PagerBlock>
<mt:IfCurrentPage>
<$mt:Var name="__value__"$>
<mt:Else>
<a href="<$mt:PagerLink$>"><$mt:Var name="__value__"$></a>
</mt:IfCurrentPage>
</mt:PagerBlock>
<mt:IfMoreResults>
<a href="<$mt:NextLink$>" rel="next" onclick="return swapContent();">Next ></a>
</mt:IfMoreResults>
<mt:Else>
<mt:Ignore><!-- Navigation for statically published page. --></mt:Ignore>
<mt:If name="archive_template">
<$mt:ArchiveCount setvar="total_entries"$>
<mt:Else>
<$mt:BlogEntryCount setvar="total_entries"$>
</mt:If>
<mt:Ignore><!-- If blog contains more entries than the number of entries to display per page. --></mt:Ignore>
<mt:If name="total_entries" gt="$entries_per_page">
<mt:Ignore><!-- Set the total number of entries to iterate through the pages. --></mt:Ignore>
<mt:Ignore><!-- IF total entries divided by entries per page is a whole number. --></mt:Ignore>
<mt:If name="total_entries" op="%" value="$entries_per_page" eq="0">
<mt:Ignore><!-- Set total pages to total entries divided by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="/" value="$entries_per_page" setvar="total_pages"$>
<mt:Else>
<mt:Ignore><!-- Get the remainder when dividing total entries by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="%" value="$entries_per_page" setvar="remainder"$>
<mt:Ignore><!-- Subtract remainder from total entries. --></mt:Ignore>
<$mt:Var name="total_entries" op="-" value="$remainder" setvar="total_entries"$>
<mt:Ignore><!-- Determine total pages by dividing total entries (minus remainder) by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="/" value="$entries_per_page" setvar="total_pages"$>
<mt:Ignore><!-- Add one page to handle the remainder of entries. --></mt:Ignore>
<$mt:SetVar name="total_pages" op="++"$>
</mt:If>
<mt:Ignore><!-- Loop through total pages, creating links to all but the first page (which is the current page). --></mt:Ignore>
<mt:For from="1" to="$total_pages" step="1">
<mt:If name="__first__">
<$mt:Var name="__index__"$>
<mt:Else>
<a href="<$mt:Var name="search_link"$><$mt:Var name="__index__"$>"><$mt:Var name="__index__"$></a>
</mt:If>
</mt:For>
<mt:Ignore><!-- Hard-coded link to the next page (page 2). --></mt:Ignore>
<a href="<$mt:Var name="search_link"$>2" rel="next">Next »</a>
</mt:If>
</mt:If>
</mt:SetVarBlock>
<mt:Ignore><!-- Strip space and trim navigation code. --></mt:Ignore>
<$mt:Var name="pagination_navigation" strip=" " trim="1" setvar="pagination_navigation"$>
<div class="content-nav">
<mt:Ignore><!-- Output variable if exists. --></mt:Ignore>
<$mt:Var name="pagination_navigation" strip=" " trim="1" setvar="pagination_navigation"$>
<mt:If name="pagination_navigation">
<div class="pagination-navigation">
<$mt:Var name="pagination_navigation"$>
</div>
</mt:If>
<a href="<$mt:Link template="archive_index"$>">Archives</a>
</div>
</div>
</div>
<$mt:Include module="Sidebar"$>
</div>
</div>
<$mt:Include module="Banner Footer"$>
</div>
</div>
</body>
</html>
Entry Listing
Replace the “Monthly Entry Listing” and “Category Entry Listing” archive templates with this code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
<head>
<mt:If name="archive_template">
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="<$mt:Link template="rsd"$>" />
</mt:If>
<$mt:Include module="HTML Head"$>
<title><$mt:BlogName encode_html="1"$>: <$mt:ArchiveTitle$> Archives</title>
<mt:If name="archive_template">
<mt:If name="datebased_archive">
<mt:ArchivePrevious><link rel="prev" href="<$mt:ArchiveLink$>" title="<$mt:ArchiveTitle encode_html="1"$>" /></mt:ArchivePrevious>
<mt:ArchiveNext><link rel="next" href="<$mt:ArchiveLink$>" title="<$mt:ArchiveTitle encode_html="1"$>" /></mt:ArchiveNext>
</mt:If>
</mt:If>
</head>
<body id="<$mt:BlogTemplateSetID$>" class="mt-archive-listing mt-<$mt:Var name="archive_class"$> <$mt:Var name="page_layout"$>">
<div id="container">
<div id="container-inner">
<$mt:Include module="Banner Header"$>
<div id="content">
<div id="content-inner">
<div id="alpha">
<div id="alpha-inner">
<mt:If name="archive_template">
<mt:If name="datebased_archive">
<mt:Ignore><!-- Title for category-monthly entry listings --></mt:Ignore>
<h1 id="page-title" class="archive-title"><$mt:ArchiveTitle$> Archives</h1>
<mt:Else>
<mt:Ignore><!-- Title for category entry listings --></mt:Ignore>
<h1 id="page-title" class="archive-title">Recently in <em><$mt:ArchiveTitle$></em> Category</h1>
</mt:If>
</mt:If>
<mt:Ignore><!-- Set the number of entries displayed on each page. --></mt:Ignore>
<$mt:Var name="entries_per_page" value="2"$>
<mt:Ignore><!-- Construct the url for querying entries. --></mt:Ignore>
<mt:SetVarBlock name="search_link">
<$mt:CGIPath$><$mt:SearchScript$>?IncludeBlogs=<$mt:BlogID$>
&template_id=<$mt:BuildTemplateID$>
&limit=<$mt:Var name="entries_per_page"$>
<mt:If name="archive_template">
&archive_type=<$mt:ArchiveType$>
<mt:If name="datebased_archive">
&year=<$mt:ArchiveDate format='%Y'$>&month=<$mt:ArchiveDate format='%m'$>&day=<$mt:ArchiveDate format='%d'$>
</mt:If>
<mt:If name="category_archive">
&category=<$mt:CategoryID$>
</mt:If>
<mt:If name="author_archive">
&author=<$mt:AuthorID$>
</mt:If>
<mt:Else>
&archive_type=Index
</mt:If>
&page=
</mt:SetVarBlock>
<mt:Ignore><!-- Strip spaces and trim value. --></mt:Ignore>
<$mt:Var name="search_link" strip="" trim="1" setvar="search_link"$>
<mt:Ignore><!-- Entries loop for publishing static and dynamic pages. --></mt:Ignore>
<mt:Entries limit="$entries_per_page" search_results="1">
<mt:Ignore><!-- Use the Entry Summary module for each entry published on this page. --></mt:Ignore>
<$mt:Include module="Entry Summary"$>
</mt:Entries>
<mt:Ignore><!-- Create pagination navigation. Condition based upon if page is statically or dynamically rendered using the search_results variable. --></mt:Ignore>
<mt:SetVarBlock name="pagination_navigation">
<mt:If name="search_results">
<mt:Ignore><!-- Navigation for dynamic pages (same as navigation found in the Search Results system template). --></mt:Ignore>
<mt:IfPreviousResults>
<a href="<$mt:PreviousLink$>" rel="prev" onclick="return swapContent(-1);">< Previous</a>
</mt:IfPreviousResults>
<mt:PagerBlock>
<mt:IfCurrentPage>
<$mt:Var name="__value__"$>
<mt:Else>
<a href="<$mt:PagerLink$>"><$mt:Var name="__value__"$></a>
</mt:IfCurrentPage>
</mt:PagerBlock>
<mt:IfMoreResults>
<a href="<$mt:NextLink$>" rel="next" onclick="return swapContent();">Next ></a>
</mt:IfMoreResults>
<mt:Else>
<mt:Ignore><!-- Navigation for statically published page. --></mt:Ignore>
<mt:If name="archive_template">
<$mt:ArchiveCount setvar="total_entries"$>
<mt:Else>
<$mt:BlogEntryCount setvar="total_entries"$>
</mt:If>
<mt:Ignore><!-- If blog contains more entries than the number of entries to display per page. --></mt:Ignore>
<mt:If name="total_entries" gt="$entries_per_page">
<mt:Ignore><!-- Set the total number of entries to iterate through the pages. --></mt:Ignore>
<mt:Ignore><!-- IF ` divided by entries per page is a whole number. --></mt:Ignore>
<mt:If name="total_entries" op="%" value="$entries_per_page" eq="0">
<mt:Ignore><!-- Set total pages to total entries divided by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="/" value="$entries_per_page" setvar="total_pages"$>
<mt:Else>
<mt:Ignore><!-- Get the remainder when dividing total entries by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="%" value="$entries_per_page" setvar="remainder"$>
<mt:Ignore><!-- Subtract remainder from total entries. --></mt:Ignore>
<$mt:Var name="total_entries" op="-" value="$remainder" setvar="total_entries"$>
<mt:Ignore><!-- Determine total pages by dividing total entries (minus remainder) by entries per page. --></mt:Ignore>
<$mt:Var name="total_entries" op="/" value="$entries_per_page" setvar="total_pages"$>
<mt:Ignore><!-- Add one page to handle the remainder of entries. --></mt:Ignore>
<$mt:SetVar name="total_pages" op="++"$>
</mt:If>
<mt:Ignore><!-- Loop through total pages, creating links to all but the first page (which is the current page). --></mt:Ignore>
<mt:For from="1" to="$total_pages" step="1">
<mt:If name="__first__">
<$mt:Var name="__index__"$>
<mt:Else>
<a href="<$mt:Var name="search_link"$><$mt:Var name="__index__"$>"><$mt:Var name="__index__"$></a>
</mt:If>
</mt:For>
<mt:Ignore><!-- Hard-coded link to the next page (page 2). --></mt:Ignore>
<a href="<$mt:Var name="search_link"$>2" rel="next">Next »</a>
</mt:If>
</mt:If>
</mt:SetVarBlock>
<mt:Ignore><!-- Strip space and trim navigation code. --></mt:Ignore>
<$mt:Var name="pagination_navigation" strip=" " trim="1" setvar="pagination_navigation"$>
<div class="content-nav">
<mt:Ignore><!-- Output variable if exists. --></mt:Ignore>
<$mt:Var name="pagination_navigation" strip=" " trim="1" setvar="pagination_navigation"$>
<mt:If name="pagination_navigation">
<div class="pagination-navigation">
<$mt:Var name="pagination_navigation"$>
</div>
</mt:If>
<mt:ArchivePrevious><a href="<$mt:ArchiveLink$>">« <$mt:ArchiveTitle$></a> |</mt:ArchivePrevious>
<a href="<$mt:Link template="main_index"$>">Main Index</a> |
<a href="<$mt:Link template="archive_index"$>">Archives</a>
<mt:ArchiveNext>| <a href="<$mt:ArchiveLink$>"><$mt:ArchiveTitle$> »</a></mt:ArchiveNext>
</div>
</div>
</div>
<$mt:Include module="Sidebar"$>
</div>
</div>
<$mt:Include module="Banner Footer"$>
</div>
</div>
</body>
</html>
genjipress.com on July 28, 2009, 2:10 p.m. Reply
As far as I can tell, one of the disadvantages of using this scheme is that if my pages depend on plugins which do not work correctly with dynamic publishing, then I can’t paginate them.
Gregg Davis on September 1, 2009, 12:49 p.m. Reply
I’ve placed a question in the forums to see if anyone has been able to make this work. However, I would like to say that the above code for Index Templates does not produce the desired results. The pagination Links for navigation seem to display correctly, but the display of each subsequent dynamic page only displays the Most Recent Entries, with no offset. It seems as though there is a line of code missing after the “&page=” line near the top, within <mt:SetVarBlock name=”search_link”>.
Thank you if anyone knows how to fix this. I’m working with a fresh Classic Blog Template Set within MT4.31.
Gregg Davis on September 1, 2009, 6:52 p.m. Reply
Ok, I got it to work by accident, and the solution I stumbled upon is posted at http://forums.movabletype.org/2009/09/anyone-set-up-pagination-for-index-with-431.html
bartjohnston.myopenid.com on October 7, 2009, 9:34 a.m. Reply
I’m having the same problem as Gregg above (and I think the ‘solution’ he found wasn’t really a solution).
I’m trying to paginate my main index template (but not using the ajax pagination). Whenever I put together the search URL it always returns the most recent n results, regardless of the
limit
orpage
value passed in the querystring.For example:
This URL always return the 10 most recent entries (which is exactly what my main_index template does).
From here, it looks like the only template that handles
page
andlimit
properly is the search results template (and theMT:SearchResults
tag. Wheneverarchive_type=Index&template_id=###
is used, theMT:Entries
tag in the specified template doesn’t seem to get the limit and offset info.Am I missing something here?
Laura Henze on October 14, 2009, 12:33 p.m. Reply
In MT 4.31, a flag was added to deal with this issue…
To make sure tag is used for search results, try including search_results=”1”
An example is: <mt:Entries limit=”$entriesperpage” search_results=”1” >
HTH!
Beau Smith on October 15, 2009, 10:12 a.m. Reply
@Brian Moy - Use apache rewriting for pretty urls. For search when MT is on a different url than the site you are publishing, create apache aliases to map paths to the CGI directory.
miscdebris on October 26, 2009, 9:33 p.m. Reply
How does one determine what the ID value is for a given template in order to use the template_id directive?? We’d like to use a customized, alternate search template to handle pagination of our category entry listing in MT 4.32, but we don’t know how to find the ID of an existing template.
Currently we’re receiving an error on the resulting search results page because we’ve customized our search results from the default provided with the MT installation. We’re currently receiving an error when using the Entry Listing code provided above: “Template must have identifier entry_listing for non-Index archive types”
raffaele on November 29, 2009, 3:30 a.m. Reply
Hi,
I put in my Index template the standard Main Index code and it list pages and entries. I want list and pagination only entries. It’s possible?
raffaele
Phillip Smith on March 14, 2010, 11:34 a.m. Reply
Copy and pasted the templates shown above. Save and published them. Tried the pagination links and received the following errors:
“Template must have identifier main_index for Index archive type”
“Template must have identifier entry_listing for non-Index archive types”
This is on 4.34-en with: Community Pack 1.62, Professional Pack 1.21
Anyone at Six Apart have a solution to this? I’ve searched the forums, MT site, and the bug database to no avail.
Phillip.
Phillip Smith on March 14, 2010, 1:26 p.m. Reply
After some digging, I believe I’ve found a workaround for the “Template must have identifier…” error messages.
Per the notes on the pagination documentation page, it is possible to enable a configuration directive called SearchAlwaysAllowTemplateID. Enabling this directive bypasses the template type check in Search.pm and allows the pagination to execute without any errors.
Per the documentation, this has some security implications that should be considered before setting this directive. That said, if you’re publishing static pages without PHP or other dynamic includes, there should be little to worry about.
Hope this helps,
Phillip.
PRO IT Service on March 22, 2010, 6:48 a.m. Reply
Good to hear that movable type is adding this pagination feature.
I’ve personally worked with Paged Archives for over 3 years now and I’m very happy with it.
http://www.aldenbates.com/plugins/pagedarchives.html
With the Paged Archives plugin I’ve been able to get any kind of pagination I wanted, based on each and every project specs.
PRO IT Service on May 24, 2010, 3:32 a.m. Reply
One could use the above mentioned technique to paginate any kind of templates.
I’ve been able to paginate even global templates using a tricky technique which involves cloning the global template as index template (main_index) and linking both templates to the same file.
On the other hand I’m not very happy to rely on the mt-search.cgi script, especially for very busy Websites.
Kind Regards,
Mihai Bocsaru
Daily Movable Type Consultant
Web Development - Movable Type Consulting - Six Apart Partner
http://www.pro-it-service.com/
Movable Type Demo
http://www.movabletypedemo.org/