Creating a Sequential Series of Posts in Jekyll

7 years ago - #Jekyll#Ruby#Liquid

One of the features that I needed to add to Jekyll was the ability to easily group a collection of posts as a series. There are times when I need to link posts together in sequential order – like a travelog, for instance. I want a table of contents in the sidebar to let the user know that they're in the middle of a series and I want a link at the bottom to encourage the user to go to the next post in the series.

By default, Jekyll creates next and previous properties that link a post to the next post in the site. That doesn't work for me because posts don't really have anything to do with each other most of the time. But I like the properties, so I decided to override how they work.

To create a table of contents, I added a new property to posts called siblings. This property is a hash of all the other posts in the series.

To easily denote that a post is part of a series, enter something like the following into the post's YAML front matter:

series:
  name:   Name of Series Goes Here
  index:  1

The name attribute is an arbitrary string to group posts into a series. The index attribute describes where the current post lives in relation to its siblings.

For the code, create a file in the _plugins directory and name it series.rb. We're going to monkeypatch posts, so create a Post class. Give it an attribute of series:

module Jekyll
  class Post
    attr_accessor :series
  end
end

The first thing we want to do is override the initialize method so that we can assign any series data to the object itself. We still want to call the original initialize method, so we alias it from the main Post class, and then redefine it. This method calls the main initialize method, then assigns series data to the object if it finds it in the YAML front matter.

alias series_initialize initialize
def initialize(site, source, dir, name)      
  series_initialize site, source, dir, name
  self.series = { :name => self.data['series']['name'], :index => self.data['series']['index'].to_i } if self.data['series']
end

Next, we want to define a new method for getting the sibling data. Create a method that loops through the posts and selects only the ones that have a matching series name. Be sure to sort them by their index so that they are in the correct order.

def siblings
  posts = self.site.posts.select { |p| p.series && p.series[:name] == self.series[:name] }
  posts.sort_by { |p| p.series[:index] }
end

Then, to make sure that the next and previous attributes work as expected, we override the functionality of those functions to pull the next and previous posts in the series.

alias series_next next
def next

  if self.series
    posts = self.siblings.select { |p| p.series[:index] > self.series[:index] }
    return posts.first
  else
    series_next
  end

end

alias series_previous previous
def previous

  if self.data['series']
    posts = self.siblings.select { |p| p.series[:index] < self.series[:index] }
    return posts.last
  else
    series_previous
  end

end

And finally, we override the to_liquid method to add the series data if it exists.

alias series_to_liquid to_liquid
def to_liquid
  if self.series
    series_to_liquid.deep_merge({ "siblings" => self.siblings })
  else
    series_to_liquid
  end
end

To see the whole code, go to the project on Github.

To use the code in a template, you can reference the next and previous properties of the page like this:

{% if page.series and page.next %}

  <div class="next-box">
    <div class="help">This post is part of a series called...</div>
    <div class="series-title">{{ page.series.name }}</div>
    <div class="help">The next in the series is...</div>
    <div class="series-item">
      <div class="title"><a href="{{ page.next.url }}">{{ page.next.title }}</a></div>
      <div class="info">{{ page.next.description }}</div>
      <div class="next-link"><a href="{{ page.next.url }}">Read the next post in the series >></a></div>
    </div>
  </div>

{% endif %}

If you want to create a table of contents showing all the items in the series, use something like this in the templates:

{% if page.series %}

<div class="info">This post is part of a series called...</div>
<div class="title">{{ page.series.name }}</div>

{% for sibling in page.siblings %}
  {% if sibling.id == page.id %}
  <div class="series-item current">
    <div class="title">{{ sibling.title }}</div>
  </div>
  {% else %}
  <div class="series-item">
    <div class="title"><a href="{{ sibling.url }}">{{ sibling.title }}</a></div>
    <div class="info">{{ sibling.description }}</div>
  </div>
  {% endif %}                            
{% endfor %}

{% endif %}    
blog comments powered by Disqus
Trying to show liquid code in a liquid template on Jekyll? This is so easy and obvious that no one bothered to post a solution. I tracked down the answer by looking at someone's source code. I hope this makes someone else's life easier.
I've created a jekyll plugin to easily import photosets from my Flickr account into posts. In this article I walk-through the code and integration.