Welcome to the second entry in my Building an Assemble.io Blog series. In Building an Assemble.io Blog: Part 1, I covered the basics including installation, layouts, adding entries, and showing a posts list.

In this entry, I’ll add to the maintainability of the system by splitting the layout into reusable components. Afterward, we’ll setup a second layout and load our index page through that instead. Finally, we’ll make a better home page using a custom Handlebars helper.

Note: The code in this tutorial is hosted on GitHub at https://github.com/webercoder/assembleio-blog. This post begins at the example-4 tag. Feel free to follow along if you’d like, or make you own!

Partials

Our layout from the previous article included everything from the doctype to the closing body tag. That approach is completely unmaintainable; it breaks DRY. There are always common components on a site, and with Assemble.io, it’s best to break these out into partials.

Let’s have a look at our current layout:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{{title}} - My Blog</title>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1.0">
    </head>
    <body>

{{#markdown}}

# {{title}}

{{#if created}}
*Posted: {{created}}
{{#if updated}}
<br>Updated: {{updated}}
{{/if}}*
{{/if}}

{{>body}}

{{/markdown}}

    </body>
</html>

There are many issues with this template:

  • It assumes that all content will be written in Markdown.
  • It assumes that we want the title to appear at the top of the entry as an <h1>.
  • If we want to add more layouts later, we’ll have to duplicate the header and footer.

A much better approach is to split this layout into a header and footer partials, and two layout files: one for our Markdown blog entries and one for vanilla Handlebars pages.

First, change to the root directory of the blog – where Gruntfile.js and package.json reside – and create a directory for the partials:

% mkdir src/layouts/partials

Now create two new partials:

src/layouts/partials/header.hbs

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{{title}} - My Blog</title>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1.0">
    </head>
    <body>

src/layouts/partials/footer.hbs

</body>
</html>

Now modify the default template to include those partials:

src/layouts/default.hbs

{{>header}}

{{#markdown}}

# {{title}}

{{#if created}}
*Posted: {{created}}
{{#if updated}}
<br>Updated: {{updated}}
{{/if}}*
{{/if}}

{{>body}}

{{/markdown}}

{{>footer}}

Finally, tell Grunt where to find the partials:

Gruntfile.js

// ...
assemble: {
    options: {
        layout: 'default.hbs',
        layoutdir: './src/layouts/',
        partials: './src/layouts/partials/**/*.hbs'
    },
    blog: {
        files: [{
            cwd: './src/content',
            dest: './build/',
            expand: true,
            src: ['**/*.md']
        }]
    }
}
// ...

Now run grunt to test that everything worked out. The blog should look exactly as it did before, but the code is much easier to maintain.

Git Checkpoint: The code above is available on the tag example-5 in the example Git repository.

Adding a Layout

Now that the header and footer have their own partials, let’s split our layout into one for normal pages and another for blog entries. In a flat-file blog like this one, we’d expect to have many more blog entries than other types of pages, so we’ll continue using the default layout for blog entries. Let’s create a new layout for other types of pages:

src/layouts/page.hbs

{{>header}}
{{>body}}
{{>footer}}

I’ve removed the created and updated block, the title, and the Markdown triggers to allow for maximum customization on each page.

Now modify the home page:

src/content/index.md

---
title: "Welcome to My Blog"
layout: page.hbs
exclude: true
---

<h1>{{title}}</h1>

<ul>
{{#withSort pages "data.created" dir="desc"}}
	{{#unless data.exclude}}
		<li>
			<a href="posts/{{basename}}.html">{{data.title}}</a>
			({{data.created}})
		</li>
	{{/unless}}
{{/withSort}}
</ul>

I’ve also added {{title}} and changed the title itself. The index page originally disabled Markdown by disabling and then reenabling it with Handlebars expressions (a super hack), but our new template doesn’t have to do that, so those Markdown expressions can be removed.

Finally, rename src/content/index.md to src/content/index.hbs since the home page is not processed as Markdown any longer.

Assuming all went well, run grunt to test your changes. The blog should look the same aside for the modified home page title.

Git Checkpoint: The code above is available on the tag example-6 in the example Git repository.

Improving the Home Page

Most blogs have other pages such as a pretty landing page or an “about” page. Using the new layout, we can create those fairly easily.

For the home page, let’s show our latest blog entry with a summary and a Read More link.

First, copy the content from src/content/index.hbs into src/content/blog.hbs and change the title to “Blog”:

src/content/blog.hbs

---
title: "Blog"
layout: page.hbs
exclude: true
---

<h1>{{title}}</h1>

<ul>
{{#withSort pages "data.created" dir="desc"}}
	{{#unless data.exclude}}
		<li>
			<a href="posts/{{basename}}.html">{{data.title}}</a>
			({{data.created}})
		</li>
	{{/unless}}
{{/withSort}}
</ul>

src/content/blog.hbs will now be the entries list page instead of the home page.

Next add a brief summary to each blog entry in the YAML Front Matter (Reminder: the YFM is the top section of each content page).

src/content/posts/my-first-day-as-a-wallaby.md

---
title: My First Day as a Wallaby
created: 2015/05/01 5:45 pm
summary: My first day as a Wallaby was quite an experience.
---

On my first day as a Wallaby, I ran around the bush for a while and ate shrubs.
It was a pleasurable experience.

src/content/posts/my-second-day-as-a-wallaby.md

---
title: My Second Day as a Wallaby
created: 2015/05/02 6:05 pm
updated: 2015/05/03 7:53 pm
summary: |
  During my second day, I hopped quickly, exploring the land. I encountered many
  dangerous reptiles during my journey.
---

Today was entirely different from yesterday, having narrowly escaped a crocodile
attack. It was a harrowing experience.

Note how the summary in the second entry is formatted. To create multiline YFM entries, use a pipe symbol and space indentation like above.

Now that each post has a summary, let’s change the index page to show the latest entry.

src/content/index.hbs

---
title: "Welcome to My Blog"
layout: page.hbs
exclude: true
---

<h1>{{title}}</h1>

<h2>Latest Entry</h2>
<article>
	{{#getLatestEntries pages 1}}
		<h3><a href="posts/{{basename}}.html">{{data.title}}</a></h3>
		<p>
			<em>{{data.created}}</em>
		</p>
		<p>
			{{data.summary}}
			<a href="posts/{{basename}}.html">Read More</a>
		</p>
	{{/getLatestEntry}}
</article>

<p><a href="blog.html">View All Posts</a></p>

The Handlebars expression {{#getLatestEntry}} is a custom Handlebars block helper. It accepts a collection and a number (n) as parameters, orders the collection by date, executes the enclosed markup n times, and returns the result.

To include this helper in your blog, first configure Grunt to load helpers:

Gruntfile.js

// ...
assemble: {
    options: {
        layout: 'default.hbs',
        layoutdir: './src/layouts/',
        partials: './src/layouts/partials/**/*.hbs',
        helpers: './src/helpers/**/*.js'
    },
    blog: {
        files: [{
            cwd: './src/content',
            dest: './build/',
            expand: true,
            src: ['**/*.md', '**/*.hbs']
        }]
    }
}
// ...

I’ve added the helpers: line above.

Next, create the helper file src/helpers/helpers.js and add the helper to it:

src/helpers/helpers.js

var moment = require("moment");

module.exports.register = function(Handlebars, options) {
	'use strict';

	/**
	 * Get the first enabled item of a list..
	 * @param items An array of items.
	 * @return A subset of items.
	 */
	Handlebars.registerHelper('getLatestEntries', function(items, limit, options) {

		var rv = "";
		var count = 0;

		// Sort items by date
		var sortedItems = items.sort(function(a, b) {
			var momentA = moment(a.created);
			var momentB = moment(b.created);
			return momentB.diff(momentA);
		});

		if (!limit) {
			limit = 10;
		}

		// Pull out the most recent
		for (var i = 0; i < sortedItems.length && count++ < limit; i++) {
			if ("exclude" in sortedItems[i] && sortedItems[i].exclude === true) {
				continue;
			}
			rv += options.fn(sortedItems[i]);
			count++;
		}

		return rv;

	});

};

Most of this is pretty self-explanatory, but I’ll cover two important things. First, Handlebars passes options.fn to helpers. It takes an argument and runs it through the template that you provide. In the example above, I’m looping through each item in a list, and making <article>’s with each one. The output of options.fn is a rendered string, so I combine all of the outputs into one string and return that from my helper.

Second, sorting dates is tricky business when weird date formats are used. I decided to use Moment.js for that, which makes sorting a breeze with the Moment.diff function. So we’ll need to install the Moment.js npm module:

% npm install --save-dev moment

With all that saved, run grunt. Your old index page should be called blog.html and the new index.html should look like this:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Welcome to My Blog - My Blog</title>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1.0">
    </head>
    <body>


<h1>Welcome to My Blog</h1>

<h2>Latest Entry</h2>
<article>
		<h3><a href="posts/my-second-day-as-a-wallaby.html">My Second Day as a Wallaby</a></h3>
		<p>
			<em>2015/05/02 6:05 pm</em>
		</p>
		<p>
			During my second day, I hopped quickly, exploring the land. I encountered many
dangerous reptiles during my journey.

			<a href="posts/my-second-day-as-a-wallaby.html">Read More</a>
		</p>
</article>

<p><a href="blog.html">View All Posts</a></p>

    </body>
</html>

Git Checkpoint: The code above is available on the tag example-7 in the example Git repository.

Conclusion

This entry expanded on the original example blog by splitting the original template into reusable components called partials, creating a second template for non-blog pages, and adding a better home page using a helper to create custom Handlebars functionality.

In Building an Assemble.io Blog: Part 3, I will improve our blog by adding static assets and basic bootstrap styles.