Setting up a basic taxonomy on this site.

DocPad is incredibly flexible. So flexible, that it can be a challenge to get started.

I'd urge everyone to begin with the very helpful Getting Started guide. Rather than rehash that, I'll share how I've used DocPad to set up this site.

Content Types

I've organized my content into four major categories:

  • Posts: Regular blog posts.
  • Projects: Major projects I'm working on.
  • Pictures: Pictures, galleries, etc.
  • Pages: Supporting pages. About, Contact, etc.

The document types will be presented with different layouts and separate index pages.

File Structure

/src   
    /documents
        /pages
        /pictures
        /posts
        /projects
    /files
    /layouts

To start, I've created sub-folders for each document type.

Collections

DocPad uses collections to manage sets of documents. Index pages, recent posts, posts tagged with..., are derived from a collection.

DocPad collections are based on an extension of Backbone.js Collections called QueryEngine. QueryEngine is very powerful, and I recommend reading the guide. In the meantime, I hope my examples will be clear enough to get started.

The easiest way to use collections is to define them in the docpad configuration file.

Beginning with posts, I have set up a collection (using CoffeeScript):

collections:
    posts: ->
        @getCollection("html").findAllLive({relativeOutDirPath: 'posts'},[{date:-1}])

Let's look at this in more detail:

  • I've defined a collection called posts.
  • @getCollection("html") gets the parent collection. In this case, a default DocPad collection called html that contains all the html documents on the site. This is a common starting point.
  • .findAllLive() is the method used to find documents for a collection.
  • {relativeOutDirPath: 'posts'}: The first parameter of findAllLive is the query used to filter the collection. relativeOutDirPath is the directory the document will be output into. Since I put my posts into the posts directory, that's what I'll search for.

    The DocPad documentation has a list of available metadata fields.

    You also have access to any metadata specified in the document's YAML front matter. We'll look at an example later.

  • [{date:-1}]: The second parameter specifies collection sorting. In this case, sorting by date, in reverse order.

To use this collection in a template, to generate a list of posts, I use this (example in Eco):

<ul>
<% for post in @getCollection("posts").toJSON(): %>
    <li><a href="<%= post.url %>"><%= post.title %></a></li>
<% end %>
</ul>

Setting metadata with collections.

DocPad collections can do more than retrieve documents. You can also use them to set metadata.

For example, while I want to use different layouts for each of the four document types, I don't want to have to add layout: post to every post document. Instead, I can just modify my collection:

posts: ->
    @getCollection("html").findAllLive({relativeOutDirPath: 'posts'},[{date:-1}]).on "add", (model) ->
        model.setMetaDefaults({layout:"post"})

Without going into too much detail, I've created a responder to the "add" event, that's called whenever something is added to the collection. This function updates the default metadata to include layout: "post".

Now, the act of placing a file in the /posts directory will add the metadata I need. Magic!

Extending this to each of my four post types, I end up with the following collections:

collections:
    posts: ->
        @getCollection("html").findAllLive({relativeOutDirPath: 'posts'},[{date: -1}]).on "add", (model) ->
            model.setMetaDefaults({layout: "post"})
    projects: ->
        @getCollection("html").findAllLive({relativeOutDirPath: 'projects'},[{title: 1}]).on "add", (model) ->
            model.setMetaDefaults({layout: "project"})
    pages: ->
        @getCollection("html").findAllLive({relativeOutDirPath: 'pages'}).on "add", (model) ->
            model.setMetaDefaults({layout: "page"})
    pictures: ->
        @getCollection("html").findAllLive({relativeOutDirPath: 'pictures'},[{date: -1}]).on "add", (model) ->
            model.setMetaDefaults({layout: "picture"})

You'll notice I sorted the projects by title, instead of by date. Pages have no sorting specified. I don't use this collection to generate a list of pages, but I rely on it to automatically add 'layout: "page"' to any document I put into the /pages folder.

The Home Page

These work great for my document type index pages, but on my home page I show the recent documents across several different types. How's that done?

It's similar to the other collections, though now I specify multiple directories to look for.

frontpage: ->
    @getCollection("html").findAllLive({relativeOutDirPath: $in: ['posts','projects','pictures']},[{date: -1}])

This returns all post, project, and picture documents, in reverse date order.

I use CoffeeScript array slicing to grab the proper document for each section of the home page.

First, the most recent article is displayed as a headline.

<% headline = @getCollection("frontpage").toJSON()[0] %>

To the right of that, I list the next 4 recent entries.

<% for recent in @getCollection("frontpage").toJSON()[1..4]: %>

At the bottom of the page, I show a grid of the following twelve items.

<% for article in @getCollection("frontpage").toJSON()[5..16]: %>

I also use one more special collection. On the homepage, I show a few articles that I've designated as "featured".

Featured documents are selected by me, and aren't related to the document type. I add featured: true to the YAML front matter.

Because the YAML front matter is added to the document's metadata, I can add another collection to look for it. I sort the featured articles by title.

featured: ->
    @getCollection("html").findAllLive({featured: true}, [{title: 1}])

Alternatives

This isn't the only way I could have implemented document types. Let's look at another way I could have accomplished the same thing. Perhaps I didn't want my URLs to have subdirectories:

/posts/this-post-is-interesting.html
/projects/secret-project.html
/pictures/vacation-2013.html

and instead look like this:

/this-post-is-interesting.html
/secret-project.html
/vacation-2013.html

I could remove my content from the document type folders, and instead rely on the YAML front matter to specify document type.

---
title: My Project
type: project
...
---

I'd have to update my collection:

projects: ->
    @getCollection("html").findAllLive({type: 'project'},[{title:1}]).on "add", (model) ->
        model.setMetaDefaults({layout:"project"})

However, because it is the same collection, I wouldn't need to update my template at all. I'm able to completely alter how the site works, with very little effort.

So, while DocPad doesn't come with any sort of content organization out of the box, it makes it easy to build the beginnings of a basic taxonomy. And because I built it, it works exactly how I expect, with as much complexity as I need.

March 27 2013.