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:
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
src/layouts/partials/footer.hbs
Now modify the default template to include those partials:
src/layouts/default.hbs
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
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
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
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
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.