Coding an Ajax-Style Paged Document Viewer With jQuery

Recently, I’ve have seen a number of websites using this JavaScript-powered paged document interface. Users are presented a multi-page document set starting on the first page, and they have the ability to switch between pages dynamically. This can be a much better solution than linking directly to a PDF document. Or this may even be an alternative where you have a PDF download link, plus the embedded document images for easy access.

In this tutorial I want to demonstrate how we can build a small script which handles any set number of documents. The JS code is a little bit tricky since we need to adapt for a large number of pages. However it is not very difficult to customize and get the design looking exactly as you would need it! Check out my sample demo below to get an idea of what we will be creating.

2 Million+ Digital Assets, With Unlimited Downloads

Get unlimited downloads of 2 million+ design resources, themes, templates, photos, graphics and more. Envato Elements starts at $16 per month, and is the best creative subscription we've ever seen.

See More

Live DemoDownload Source Code

Getting Started

We need to first create the index.html and styles.css files in the same directory. I also downloaded a recent copy of jQuery to store with my other JS document. jQuery is the only dependency for what we need to build and the minified library does not take up much space.

<!doctype html>
<html lang="en-US">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>jQuery Document Viewer Demo</title>
  <meta name="author" content="Jake Rocheleau">
  <link rel="shortcut icon" href="https://designshack.net/favicon.ico">
  <link rel="icon" href="https://designshack.net/favicon.ico">
  <link rel="stylesheet" type="text/css" media="all" href="styles.css">
  <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
  <script type="text/javascript" src="js/docview.js"></script>
</head>

As you can see, the header does not contain anything too out-of-the-ordinary. The body content is also easy to understand and I have tried to keep the interface as simple as possible. There is a single div using the ID #documentcontainer which holds the first page image. This img tag will auto-update once the user clicks on either of the navigation links, or clicks on the image itself to move forward one page.

<div id="content">
  <h1>Dynamic JS Page Viewer</h1>
  <p>Click on the page image to automatically move onto the next document. The navigation arrows will also switch between the different page views. Also check out the <a href="https://www.docracy.com/3884/contract-of-works-for-web-design">original document source</a>.</p>
  
  <div id="controls">
    <a href="#" id="backpage">←</a>
    <a href="#" id="forwardpage">→</a>
  </div>
  
  <div id="documentcontainer">
    <img src="images/docset1/01.jpg" data-page="1">
  </div>
</div><!-- @end #content -->

I am only using the → and ← entities for displaying arrow links. But you could update this design to appear any way you like. The really important part would be the IDs attached onto each link. These correspond to jQuery event handlers which trigger after the user clicks on either of the arrows to move forward or back in the document set.

Configuring the Page

One interesting attribute you will find on the img tag is called data-page. We use this attribute to determine which page is currently being displayed without needing to guess from the current filename. jQuery will update this value when switching between alternate pages.

From the CSS styles we really only need to be concerned about the inner body content. You will find all of the typical resets and centered wrapper div styles within the stylesheet. This is all fairly common and I hope frontend developers would be familiar with these ideas already.

#documentcontainer {
  display: block;
  width: 100%;
  padding-top: 22px;
  margin-bottom: 10px;
}

#documentcontainer img {
  display: block;
  margin: 0 auto;
  border: 0;
  cursor: pointer;
}


#controls {
  display: block;
  padding: 0 35px;
  text-align: center;
}

#controls a {
  font-size: 3.6em;
  font-weight: bold;
}
#controls a:hover {
  text-decoration: none;
  color: #72ad69;
}
#controls a#backpage {
  margin-right: 20px;
}

The document container has a bit of padding on either side to keep the image centered on the page. Also the img tag itself has the property cursor: pointer; for whenever the user hovers to promote a clicking event. There is no anchor link wrapping the img because we do not want to link anywhere. But the image should be clickable, and this hand cursor mouse icon is the perfect user experience technique.

With everything else setup on the page we can now delve into the jQuery. I tried to keep the script very malleable using variables which you can update for your own documents. But since there are so many different methods to achieve this, I can’t guarantee my code is the absolute perfect end-all-be-all solution!

jQuery Paging Variables

Each individual document page is actually a JPG image exported from this PDF document using Adobe Photoshop. This process should be possible with any typical PDF, which makes publishing on the web using a JS document viewer even easier. We should take a look at my custom file docview.js and break down each of the code blocks.

var setDirectory = "images/docset1/"; // current documentset directory, include trailing slash
var fileTypes    = "jpg"; // your image(s) filetype, usually jpg or png
var lastDocNum   = 4;    // the very last document number in the set
var fileDigits   = 2;   // total number of filename digits ex: 01, 001, 0001 

These 4 variables may be customized if you are using a different set of documents. First is setDirectory which should lead to a local or absolute URL value, with the forward slash, pointing to the directory of the images. fileTypes would be the extension of your document images. lastDocNum is the total count of pages in your set, that way jQuery knows when we have reached the end of your document.

The last variable fileDigits is important to explain. This script assumes you will be labeling document sets in the order of 01.jpg, 001.jpg, or 0001.jpg, using however many zeros to fill the place of pages. If you have a 2,500 page document then the ordering should be numerically sequential and the fileDigits variable would be set to 4 because there are 4 numerical digits within each image name.

Handling Dynamic Images

When I was digging through Stack Overflow I found this amazing solution for appending zeros into the filename. We need to convert the filename into an integer to increase the value by 1. Then we convert it back to a string, add the proper number of zeros and give it the ending file extension.

/**
 * Source: http://stackoverflow.com/a/6466217/477958 
 */
function addLeadingZeros (n, length) {
  var str = (n > 0 ? n : -n) + "";
  var zeros = "";
  
  for (var i = length - str.length; i > 0; i--)
    zeros += "0";

  zeros += str;
  zeros += '.'+fileTypes;
  return n >= 0 ? zeros : "-" + zeros;
}

You probably don’t need to know exactly how this works but it is a great function. The final large block of code will manage jQuery event handlers for clicking on the image, or either of the two navigation links. We setup the main img into a JavaScript variable named docimage and then manipulate the attributes from there.

/* begin jQuery */
$(function(){
  var docimage  = $('#documentcontainer > img');
  
  $(docimage).on('click', function(e){
    e.preventDefault();
    
    var currentid = $(this).attr('data-page');
    var newimgid  = parseInt(currentid)+1;
    var newimgsrc = setDirectory;
    
    if(currentid == lastDocNum) {
      // if viewing the last page then do nothing
      return false;
    }
    
    newimgsrc += addLeadingZeros(newimgid, fileDigits);
    
    $(this).attr('data-page', newimgid);
    $(this).attr('src', newimgsrc);
  }); // event handler for clicking doc image

The new variables created inside will hold the next numerical page number, along with the new page ID number and the new image source. We first check if the ID is equal to the very final number of documents and if so we stop the script from executing further. This means we have hit the final page and the user is clicking on the final page to move on, but we have nothing else to display! Otherwise the img attributes are updated and the new image will display instantaneously.

  $('#controls > #forwardpage, #controls > #backpage').on('click', function(e){
    e.preventDefault();  
    
    var currentid = $(docimage).attr('data-page');
    var newimgsrc = setDirectory;
    
    if($(this).attr('id') == 'backpage') 
      var newimgid = parseInt(currentid)-1;
    else if($(this).attr('id') == 'forwardpage')
      var newimgid = parseInt(currentid)+1;
      
    if(currentid == lastDocNum && $(this).attr('id') == 'forwardpage') {
      // if viewing the last page then no more forward links
      return false;
    }
    if(currentid == 1 && $(this).attr('id') == 'backpage') {
      // if viewing the first page then no going backwards
      return false;
    }
    
    newimgsrc += addLeadingZeros(newimgid, fileDigits);
    
    $(docimage).attr('data-page', newimgid);
    $(docimage).attr('src', newimgsrc);
  }); // event handler for clicking arrow links
});

The last event handler is using jQuery selectors for both arrow links together. Based on the ID name we check if the current page ID should be subtracted or added by one. Also we have to perform a similar check to see if the user is at the very first page and can’t go back anymore, or if they’re at the last page and can’t go forward anymore. Otherwise the codes are almost identical and you should be able to follow all of it through until the end.

Live DemoDownload Source Code

Final Thoughts

I do hope this tutorial may prove useful to some web developers. This idea is still fairly new and there just isn’t a major demand for this functionality on all websites. But I do like the idea of pulling new image URLs dynamically so that the page does not refresh.

This gives the user a cleaner experience and the whole website loads fewer HTTP requests per page. If you have any similar ideas or questions on the tutorial feel free to share with us in the discussion area below!