Site Pagination

Introduction

This is the evolution of this site's pagination navigation from the simple HTML navigation of 1999, to the JavaScript version in 2005 and then to the redesign in 2021.

1999

Ever since the site was first created in 1999 there has been content that didn't fit onto one page. Pages that were part of the multipage content were named, for example, bemmy1.htm, bemmy2.htm and so on, and then the main site menu and submenus were written with each page as below.

Menu from the original site design

Above, part of the main site menu from 1999 including the awful colors that were used. Below, the Bedminister menu from the Bristol section of the site.

Menu from the original site design

2005

In 2005, one way in which I decided to try and improve navigation around the site was to include a menu at the bottom of the pages that are a continuation of another. For example, the "Postcards" section of "Terre Haute" contained 11 pages, the "Castle" section of "Bristol, England' contained 6 pages.

What I wanted was a system where a single piece of JavaScript could write a menu showing the number of pages in the section, with links and also have links for the first, previous, next and last pages. What I came up with looks like this...

Example Multi-Page Menu

Example Multi-Page Menu
Image from Terre Haute > Postcards, page 6

Implementation

I've seen menu's like this on other sites but in this case decided simply to write my own.

What I came up with is...


function multipage(){

var path = location.pathname;
path = path.replace(/\\/g, '/');
var fullName = ""
var fileName = path.substring(path.lastIndexOf('/')+1);
var numStart = fileName.search(/\d/);
var numEnd = fileName.indexOf('.');
var pageNum = fileName.substring(numStart,numEnd);
var firstPart = fileName.substring(0, numStart);

switch(firstPart){
case "tpcards" :
   fullName = "Postcards";
   maxPages=11;
   break;
case "bcastle" :
   fullName = "Bristol Castle";
   maxPages=6;
   break;
}

document.write('<p class="ctr">' + fullName + ': ');

if(pageNum>"1"){
   document.write(''<a href="' + firstPart + '1.htm">First'</a>');
   document.write(' '<a href="' + firstPart + (pageNum-1) + '.htm">Prev.'</a>');
}
else{
   document.write('First');
   document.write(' Prev.');
}

for(cnt=1;cnt<=maxPages;cnt++){
   if(pageNum!=cnt){
      document.write(' '<a href="' + firstPart + cnt + '.htm">' + cnt + ''</a>');
   }
   else{
      document.write(' ' + cnt);
   }
}

if(pageNum<maxPages){
   document.write(' '<a href="' + firstPart + (++pageNum) + '.htm">Next'</a>');
   document.write(' '<a href="' + firstPart + maxPages + '.htm">Last'</a>');
}
else{
   document.write(' Next');
   document.write(' Last');
}

document.write(''</p>');
}

Dissection

location.pathname is used to obtain the path from the root and the current document name. For example, /th/tpcards6.htm

path = path.replace(/\\/g, "/"); is for when the site is on a CD or hard drive. What this code does is replace all the instances of "\" in the path name and changes them to a "/". This is the regular expression form of replace that is capable of changing all the looked for characters in a string.

path.substring(path.lastIndexOf('/')+1); looks for the last instance of the substring "/" in the variable named path. and returns the position number from the left. The first character of a string starts at position 0. ie lastIndexOf() starts the search from the right of the string. In this instance it's extracting the filename from the full path.

fileName.search(/\d/); looks for any numeral in the string variable fileName and returns its position number. This is a regular expression, where \d means digit. \d\d would look for a substring of two digits. Because each of the files is named to a convention ie tpcards1.htm, tpcards2.htm etc. it's picking out the position at where the digits start. In tpcards12.htm it would return the position of the first digit it finds. In all these examples it would return the number 7.

fileName.indexOf('.'); looks for the first instance of the substring "." in the variable named fileName and returns the position number from the left. The first character of a string starts at position 0. ie IndexOf() starts the search from the left of the string. In tpcards1.htm it would return 8, in tpcards12.htm it would return 9.

fileName.substring(numStart,numEnd); uses the previous two searches to extract the number in the file name. In tpcards1.htm it would return 1, in tpcards12.htm it would return 12.

fileName.substring(0, numStart);  extracts the first part of the filename. In tpcards1.htm and tpcards12.htm it returns "tpcards"

The switch function makes the last extracted string more human readable. For example, tpcards is pretty meaningless so it makes sure it is associated with "Postcards". Because JavaScript can't search for the number of pages in a section (it has no server file search facilities) the number of files in a section has to be hard coded.

The final part of the code simply writes the links and page numbers to the HTML page. The actual page numbers are inserted using a FOR loop

Calling the function

The JavaScript function can be called by using any of the standard techniques. Because the code is used on several pages on the site I put the function in the main JS file for the site, brisray.js

The pages have to be told where this file is. This is done by putting <script type="text/javascript" src="../common/brisray.js" language="javascript"></script> in the HEAD section of each page.

Where the menu is to appear in the BODY of the page the following code is used...

<script language="JavaScript" type="text/javascript">
<!--
multipage();
-->
</script>

2021

The site is now 22 years old and had 3 design changes since the original. Those have been mostly because in the original site the colours were awful and I didn't separate all the CSS from the HTML properly and the other two because I wasn't happy with it, mostly because of the main navigation. Over the years the web pages that make up the site have been getting shorter. This has mostly been because I started using things like Fancybox, version 2 or lately version 3 to use an image lightbox rather than display full-size images directly on the page. Another reason some pages have gotten shorter is that I've started using Prism code highlighter in a div with max-height set. Even so, there are some parts of the site that need multiple pages for me to tell a story.

One thing that hasn't changed is the 2005 pagination JavaScript code. The last site redesign started in December 2019 but it wasn't until January 2021 that I seriously looked at the pagination. The principles of pagination have not changed much in 15 years, compare Pagination – Examples And Good Practices (2007) by Sven Lennartz with Best Practices for Designing Pagination in Web (2017) by Abhishek Jain.

Some things about JavaScript, the way it is used, how it handles the path, and how functions are called have changed. The mime type text/javascript is no longer required in the script tag. When calling functions they no longer need to be wrapped in <!-- and --> tags.

Windows File Explorer displays displays file paths with backslashes such as C:\Users\Documents\Websites\brisray\utils. When opening files locally most modern browsers change the backslashes to forwardslashes such as file:///C:/Users/Documents/Websites/brisray/utils/pagination.htm (Chrome v88, Edge v88, Firefox v85, Opera v74). The only modern browser that does not is Internet Explorer for Windows 10 which still displays C:\Users\Documents\Websites\brisray\utils\pagination.htm. The good news is that all of them, even Internet Explorer, returns forwardslashes from JavaScript's window.location.pathname. For an explanation of the forwardslash/backslash thing see Why Windows Uses Backslashes and Everything Else Uses Forward Slashes by Chris Hoffman.

As I use simple naming system such as bemmy1, bemmy2, ... for the multiple pages in a particualr section, what the JavaScript needs to do is:

CSS styled button-like elements look attractive and are easy to use. JavaScript can take care of how many page selectors to draw and which page the user is on. I like the idea of using first, previous, next and last buttons, as well as highlighting the current page button.

What I came up with looks like this:

Menu from the original site design

The basic 2021 pagination design
The current page is in light blue/green and hovered link is grey
Example Pages: Bristol Castle, Windows Web Server, and Other Ships.

The JavaScript used looks like this:


function pagination(){
   var url = window.location.href;
   var filename = url.split("/").pop();
   var fileParts = filename.split(".");
   var firstPart = fileParts[0];
   var secondPart = fileParts[1];
   var activePage = Number(firstPart.match(/\d+/));
   var textOnly = firstPart.match(/\D+/).toString();
   var fullName = "";
   var maxPages = 0;
   var fileSection = textOnly.toLowerCase();
   switch(fileSection){
   case "stgeorge" :
      fullName = "St. George";
      maxPages = 4;
      break;
   }

   document.write("

" + fullName + ":

"); if (activePage>1){ var firstLink = "<p><a href='" + textOnly + "1." + secondPart +"'>First</a>"; document.write(firstLink); var prevLink = "<a href='" + textOnly + (activePage-1) + "." + secondPart +"'>Previous</a>"; document.write(prevLink);} else { firstLink = "<p><a class='active'>First</a>"; document.write(firstLink); prevLink = "<a class='active'>Previous</a>"; document.write(prevLink);} var i = 0; for (i=1; i<=maxPages; i++ ){ if (i!== activePage){ document.write("<a href='" + textOnly + i + "." + secondPart +"'>" + i + "</a>"); } else { document.write("<a class='active'>" + i + "</a>");} } if (activePage<maxPages){ var nextLink = "<a href='" + textOnly + (activePage+1) + "." + secondPart +"'>Next</a>"; document.write(nextLink); var lastLink = "<a href='" + textOnly + maxPages + "." + secondPart +"'>Last</a></p>"; document.write(lastLink);} else { var nextLink = "<a class='active'>Next</a>"; document.write(nextLink); var lastLink = "<a class='active'>Last</a></p>"; document.write(lastLink);} }

The CSS for the menu is:


#pagination {text-align: center;}
	
#pagination a {
	text-decoration: none;
	padding: 0.6em 0.8em;
	background-color: #ffe4c4;
	border-radius: .6em;
	margin: .1em;
	display: inline-block;
	font-weight: bold;
}

#pagination .active {background-color: #ddd;}
	
#pagination a:hover:not(.active) {background-color: #00FFBF;}

2023

The pagination is the only thing I now use JavaScript for on the site apart from things such as the Google Charts API pages and pages that use Fancybox image carousels or the Prism code highlighter.

I like my current JavaScript pagination. See the bottom of such pages as Bristol Castle, Windows Web Server, and Other Ships. The JavaScript can get the page URL, work out how many boxes to draw, and which one is currently active. A problem is that people without JavaScript enabled do not see the menu at all.

One solution is to use a server side cgi program written in a language such as Perl, PHP or Python. I already have Strawberry Perl installed so it would probably be best to use that.

Another solution would be to use a <noscript> tag and use a simple unordered list in an include with CSS Counters, as on the bottom of Serial Listings. Pure HTML and CSS cannot calculate the current page, so all the links have to be active, with no Previous or Next links, but in the unordered list, the First and Last pages can be specified and accounted for accounted if needed. They shouldn't be needed because the buttons run from the first to last pages anyway.

Of course, the whole point of this is to have the HTML used as some sort of include so that the menus only need editing in one place rather than put them on every page. Copying the menu on each page does mean that the appropriate current page <li> tag can be given an individual class or id, but then any changes have to be applied to every page it is on and I'll be back where I was in 1999.

CSS Counter Pagination Examples

Skip to page number:

The HTML

The HTML for the above is:


<div class="cssBtns">
<ul>
  <li><a href="https://brisray.com/bristol/castle1.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle2.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle3.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle4.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle5.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle6.htm" target="_blank"></a></li>
</ul>
</div>

The CSS

The CSS for the above is:


<style>
.cssBtns ul {
	list-style-type: none;
	counter-reset: cssCounter 0;
	text-align: center;
} 
	
.cssBtns ul li {display: inline-block }

.cssBtns ul li a{
	border: solid 1px #444;
	color: #000; 
	margin: 0 2px;
	padding: 8px 16px;
	text-align: center;
	background: #ffe4c4;
	border-radius: .6em;
	text-decoration: none;
}
	
.cssBtns ul li a:hover {background-color: #00FFBF;}	

/* Setting styles for the inner text */
.cssBtns ul li a:before {
	counter-increment: cssCounter;
	content: counter(cssCounter, decimal);
}
</style>

Notice in the above HTML and CSS that the links are empty and the <a> tags are given the styling but not so much the <li> ones. This is to allow the clickable area to be the <li> and not just the <a> which would be normal.

If you want to use First and Last then the counter needs to be reset and turned off for those particular elements.

Skip to page number:

The HTML

The HTML for the above is:


<div class="cssFLBtns">
<ul>
  <li><a class="nocount" href="https://brisray.com/bristol/castle1.htm" target="_blank">First</a></li>
  <li><a href="https://brisray.com/bristol/castle2.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle3.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle4.htm" target="_blank"></a></li>
  <li><a href="https://brisray.com/bristol/castle5.htm" target="_blank"></a></li>
  <li><a class="nocount" href="https://brisray.com/bristol/castle6.htm" target="_blank">Last</a></li>
</ul>
</div>

The CSS

The CSS for the above is:


<style>
.cssFLBtns ul {
	list-style-type: none;
	counter-reset: cssCounter 1;
	text-align: center;
} 
	
.cssFLBtns ul li {display: inline-block }

.cssFLBtns ul li a{
	border: solid 1px #444;
	color: #000; 
	margin: 0 2px;
	padding: 8px 16px;
	text-align: center;
	background: #ffe4c4;
	border-radius: .6em;
	text-decoration: none;
}
	
.cssFLBtns ul li a:hover {background-color: #00FFBF;}	

/* Setting styles for the inner text */
.cssFLBtns ul li a:not(.nocount)::before {
	counter-increment: cssCounter;
	content: counter(cssCounter, decimal);
}
</style>

Notice the small differences between the two codes. The first and last <a> tags are given a different class and excluded from getting the CSS counter code. The CSS counter now starts from 1 instead of 0.

Sources

8 Rules for Perfect Button Design
Best Practices for Designing Pagination in Web
CSS Counter Styles
How to Get Current URL in JavaScript
JavaScript - String Object - String functions and regular expressions
Object: Document - Document objects and properties (Internet Archive)
Optimal Size and Spacing for Mobile Buttons
Pagination – Examples And Good Practices
Parsing the Querystring with JavaScript - Location and document objects and properties
Strings in JavaScript - String functions
Using CSS counters - Mozilla MDN Docs article
Using Regular Expressions with JavaScript and ECMAScript - Regular expressions

This page created October 19, 2005; last modified February 23, 2023