Learning Assemble, Step 11 - More Navigation

2015-02-05

Back to navigation. In earlier posts in this series, we've looked at some page navigation and blog list navigation issues. Both of which centered around the challenge of selecting a subset of pages, and getting them formatted in the right sort order. I found several ugly ways of approaching these problems, but not a single, elegant way.

I was further challenged trying to answer this Stack Overflow question about how to make a list of the 5 most recent blog posts through a combination of selecting tagged pages, sorting by posted date, and then taking the top 5 most recent pages. I would have this problem in my sample blog, except that my sample blog only has three posts. Current, I use the following markup to show blog posts in sorted order by most recently posted date, with no limit:

{{#each navTags}}
    {{#is navTag "blog"}}
        {{#withSort pages "data.posted" dir="desc"}}
            <div>
                <h2><a href="/{{relativeLink}}">{{data.title}}</a></h2>
                <p>By {{data.author}} on {{formatDate data.posted "%F"}}</p>
                <div>
                    {{#markdown}}{{data.summary}}{{/markdown}}
                </div>
                <p><a href="/{{relativeLink}}">more...</a></p>
            </div>
        {{/withSort}}
    {{/is}}
{{/each}}

This snippet illustrates two of the three obviously documented techniques related to this problem:

  • Using the #each helper to iterate through a collection, with a nested #is to limit the result to one particular tag value.
  • #withSort helper to sort the tagged pages.
  • #withFirst helper to limit the tagged pages to the first 5 (not shown above).

However, #withSort and #withFirstare mutually exclusive, since they both directly iterate the pages collection. There is a solution to this problem though:

  • Select pages from a collection using #each and #is to limit the pages to one tag value.
  • Sort the pages within the collection by defining the sortBy attribute of the collection in the Gruntfile (new for me).
  • Limit the number of pages in the result using #withFirst.

I'll update my own blog post listing to use this technique.

Collection Definition

I had earlier used a generic navTags collection for various navigation purposes, but now we'll need a special one for the blog posts alone. This time, we'll define sorting as part of the collection:


        assemble: {
            hello: {
                options: {
                    data: 'source/data/*.{json,yml}',
                    layoutdir: 'source/templates/layouts',
                    layout: 'default.hbs',
                    partials: 'source/templates/partials/**/*.hbs',
                    collections: [
                        { name: 'navTags', inflection: 'navTag' },
                        { name: 'posts', inflection: 'post', sortby: 'posted', sortorder: 'desc' }
                    ],
                    helpers: ['source/helpers/**/*.js'],
                    plugins: ['assemble-middleware-sitemap'],
                    sitemap: {
                        homepage: "http://awesome-site.bogus",
                        relativedest: true,
                        exclude: ['diagnostics'],
                        changefreq: 'monthly'
                    }
                },

Blog Pages

Our blog pages now need to be updated to use our new posts collection rather than the old navTags. This doesn't look like a big change:

---
title: Hello, Blog!
layout: blog-layout.hbs
posts:
 - all
posted: 2014-10-01
author: James
summary: |
 This is the summary of our first
 blog post. It will cover:
  * Stuff
  * More Stuff
  * Even More Stuff
---
This is the lowly body of our first blog post.

For the purpose of the blog list, we don't care what the actual posts values are ("all" on line 5 above), only that the page is included in the posts collection. These values should probably match topics or tags in the blog, but we do not need to use it for any other purpose than listing posts.

Blog List Markup

Our blog list page, blog.hbs needs to be updated to stop using the old navTags and start using the new posts collection. This is not a big change, we still iterate a collection and select a single collection value, but now we're going to use the #withFirst helper instead of #withSort, since sorting is now provided by the collection itself.

    {{#each posts}}
        {{#is post "all"}}
            {{#withFirst pages 5}}
                <div>
                    <h2><a href="/{{relativeLink}}">{{data.title}}</a></h2>
                    <p>By {{data.author}} on {{formatDate data.posted "%F"}}</p>
                    <div>
                        {{#markdown}}{{data.summary}}{{/markdown}}
                    </div>
                    <p><a href="/{{relativeLink}}">more...</a></p>
                </div>
            {{/withFirst}}
        {{/is}}
    {{/each}}

I also created a set of 5 older posts, all equally unimaginative, to give me some test data to verify that I am sorting and limiting the blog posts properly.

Lessons Learned on Page Collections

This seems pretty simple in hindsight, but I find that I get confused about page collections. I imagine them to be a list of user-facing attributes like "tags" or "categories". I think I mentally blocked myself from creating a completely new collection for posts because I felt like I should reuse the same navTags I started using earlier. But collections are never visible to users unless I make them visible, and all of the collections I have used are for internal use only. Collections are a cheap and easy convenience to the developer, so feel free to create them for each and every purpose.

For the code, please see the learning-assemble repository on GitHub. The branch step11-more-navigation contains the completed code from this post.

Next: Learning Assemble, Step 12 - Navigation Helper