In order to complete the switch to DocPad I wanted my blog to support a paged index of my blog posts. I didn’t want to display them all in one big long page, but split them up into groups of 3 or 5 with ‘Older’ and ‘Newer’ links to let users navigate the posts. Sadly this functionality isn’t available by default in DocPad, but never fear DocPad is built with a plugin framework so we can add this functionality ourselves.
In the next couple of posts I’m going to walk through the creation of the Paged Plugin which I recently put together for just this purpose.
The docpad site has a getting started guide for how to setup a basic plugin thats a part of your site. Simply create the following folder and file structure where
yourPlugin is the name of your plugin:
package.json is required and needs to use the format specified on the docpad website.
For my paging implementation I wanted to be able to create a document in DocPad that was ‘pageable’, either looping over a collection of documents to render out sub-pages like on a blog index page, or looping a set number of times so that we can genereate pages on the fly or split up a long document across multiple pages. I tried out various ideas, and read this issue which details some possible solutions for paging. In the end I settled on the following approach:
- Set the
isPagedproperty to true on pageable documents
- Specify a
pageCountproperty if you want a fixed number of pages or
- Specify a
pagedCollectionproperty which specifies the name of a collection over which you want paging to be applied.
- Specify a
pageSizeproperty to set how many documents are looped over in each page.
The plugin will function as follows:
- Before we render the documents
- Loop over the documents
- Find those that have the
- Read the
pagedCollectionproperty to determine number of pages
- Read the
pageSizeproperty to determine document indexes in each page
- For every page we need to create:
- Clone the document and add it to a new collection
- Add a
pageobject to the cloned document detailing current page details
- Add a
firstPageDocobject to the cloned document which points back to the original document
- Read the
- For every document in the new collection:
- Normalize and contextualize the new document
- Modify the
basenameof the new document to reflect its page number
- Render the document collection
So lets get started. Here’s the basic plugin code that we’re going to start with:
# Export Plugin
This setups my basic plugin, it extends from
BasePlugin and hooks into the
renderBefore event, a full listing of all events in docpad is available here. The
renderBefore event receives two values in its
opts argument, the
collection and the
collection is the collection of documents that we are rendering, and the
templateData is the data object that is passed to the documents when rendering. Note we also
bal-util module which we’ll use later on.
Now we need to loop over our documents and figure out how / if they need paging:
So for each document in the collection we retrieve the meta object and test to see if the
isPaged property is true. If it is we retrieve the
pageSize and optionally the
pagedCollection properties. We use this information to figure out the number of pages we’re going to render, and the start and end indexes of the documents in each page. If we’ve got more than one page then for each page we copy the current document, add a page object to the document and then add it to our collection of pages to render.
Almost there, now we just need to make these documents complete and contextual so they can be rendered:
Here we’ve used the
bal-util module. This module has a few helpful little methods that make doing sequences of tasks a lot cleaner. Here we’ve created a sequence of tasks that will be executed asyncronously and after all tasks are completed the
next method callback will be executed. The list of tasks is created by looping over the
pagesToRender and for each document first normalizing it then contextualizing it then modifying its output and base filenames. Finally the last task added to the queue is to render the pagesToRender collection.
Normalization is basically the task which goes through the document and ensures all the values are updated appropriately, for instance if you change one value it might update another value as a knock-on effect. Contextualize is similar in that it sets the document up in the context of the rest of the site, for instance it generates the url property. So its important that we call both these methods on any new documents before we render them otherwise things just don’t work quite right. This is also why we do the modification of the
outFilename properties after calling these methods as otherwise they would be reset back to the original document values.
tasks.async() we execute the queue of tasks and then pass execution onto
next allowing docpad to continue. With this the plugin is working! In the full version of the plugin I’ve gone ahead and added some helper functions to the
DocumentModel that make it a bit cleaner to render pagination and operate over pages, if you want to take a look checkout the full source here.
So we’ve built a plugin that operates within our site to render out paged documents based off an original source document. I decided to share this plugin with everyone so I’ve packaged it up as a package on NPM if you want to use it in one of your own sites. In my next post I’ll explain how I did that, and how to get Unit Testing working on DocPad plugins.