I really wanted to have the ability to tag my blog posts. I am getting to the point now that there are enough of them now that they are a bit more tricky to navigate. Jekyll supports tagging, and it is actually pretty easy to use. You can add tags to any post by adding some meta data to the top of the file like so:
---
title: "Some Title"
tags: ["these", "are", "all", "tags"]
---
Then, it is really easy to collect all of the unique tags that you have across all of your posts in a template:
<ul>
{% for tag in site.tags %}
<li>{{ tag | first }}</li>
{% endfor %}
</ul>
The | first
is important, otherwise you will likely get more than what you really want. That is about as much as Jekyll offers as far as tagging goes. The unfortunate thing about tags with Jekyll is that when you want to list everything for a specific tag you would be required to have a dedicated file for displaying that tag’s posts. I do not want that. I want something dynamic enough that I can easily add a new tag without having to do anything else to display that tag’s posts. That is where Ember.js came in for me.
I figured that I could build an Ember.js application that would deliver the dynamic functionality that I wanted which was not provided by Jekyll. The first step was somehow getting the data to this theoretical Ember application.
As I showed before, it is not hard to get all of the tags using vanilla Jekyll. My first step was to have Jekyll generate a json
file that contained all of the tags, plus each article with it’s tags. I would also need, in addition to the title of the article, the date it was published as well as a link to the full article.
---
---
[
{% for tag in site.tags %}{% assign posts = 0 %}
{
"tag": "{{ tag | first}}",
"articles": [
{% for post in site.posts %}
{% assign tag_name = tag | first %}
{% if post.tags contains tag_name %}
{% if posts > 0 %},{% endif %}{
"date": "{{ post.date | date: "%Y-%m-%d" }}",
"title": "{{ post.title }}",
"url": "{{ post.url }}"
}
{% assign posts = 1 %}
{% endif %}
{% endfor %}
]
}{% if forloop.last == false %},{% endif %}
{% endfor %}
]
Alright, I know that is a big mess, generating json
can be, but it does work correctly and generates something like this:
[{
"tag": "java",
"articles": [{
"date": "2010-08-05",
"title": "Django on Jython",
"url": "/2010/08/05/django-on-jython.html"
},{
"date": "2007-06-19",
"title": "Dom4j",
"url": "/2007/06/19/dom4j.html"
}]
},{
"tag": "xml",
"articles": [{
"date": "2007-06-19",
"title": "Dom4j",
"url": "/2007/06/19/dom4j.html"
}]
}]
Now, having all of the tag information available via json
it can be consumed by an Ember application.
The handlebars templates for the ember application are fairly straight forward:
<div id="tags-application"></div>
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul class="nav nav-pills">
{{#each item in content}}
<li {{bind-attr class="item.selected:active"}}>
{{#link-to 'tag' item.tag}}{{item.tag}} ({{item.articles.length}}){{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="tag">
<ul class="nav nav-tabs nav-stacked">
{{#each article in articles}}
<li>
<a {{bind-attr href=article.url}}>
{{article.date}} <i class="icon-chevron-right"></i> <strong>{{article.title}}</strong>
</a>
</li>
{{/each}}
</ul>
</script>
Just to quickly walk thought the templates, I’ve got the tags-application
div
which is where the Ember app will live on the page. Then the default outlet
which is just a placeholder for the whole application, then there are 2 templates. One for the index
view which will display all of the tags and another for the tag
view which will list all of the articles for a given tag.
The most important thing that will definately trip you up when putting handlebars templates inside of a Jekyll template is that handlebars and Jekyll use similar syntax for template tags, which means handlebars templates need to be escaped. This can easily be done by wrapping handlebars templates in raw
tags like this:
{% raw %}
<!-- Handlebars templates go here -->
{% endraw %}
The Ember application itself is fairly small, but there are a few tricky bits that I will discuss. The final application looks like this:
App = Ember.Application.create
rootElement: '#tags-application'
App.Router.map ->
@resource "index", path: "/", ->
@resource "tag", path: "/:tag"
App.TagRoute = Ember.Route.extend
model: (params) ->
@modelFor('index').filter((item) ->
if params.tag == item.tag
item.set('selected', true)
return item
item.set('selected', false)
return
)[0]
App.IndexRoute = Ember.Route.extend
model: (params) ->
App.Tag.find()
App.Tag = Ember.Object.extend(
selected: false
).reopenClass
find: ->
new Ember.RSVP.Promise (resolve, reject) ->
$.ajax(
url: '/tags/data.js'
dataType: 'json'
).done((response) ->
Ember.run ->
tags = Ember.A([])
response.forEach (result) ->
tags.pushObject(App.Tag.create(result))
resolve(tags)
).fail((response) ->
reject(reesponse)
)
Lets dissect the find
method of App.Tag
object first. The core of this is a basic jquery ajax
call. We are hitting the /tags/data.js
url (which is generated using the Jekyll template discussed earlier). Then we are wrapping that ajax call with a promise using Ember.RSVP.Promise
then calling resolve
in the done
and reject
in the fail
. When we call resolve
we are first building a list of App.Tag
objects with the results of the ajax
call. We will use those to display on the page.
We are using the App.Tag
objects returned from the find
method to display a list of tags in our index
template. In our handlebars index
template we have a link-to
to link to the tag
route for each tag. Then in our model
hook in our Tag
route we are doing a few different things. First we are making sure that we are setting selected
to true for the tag that the user chose. Second we are filtering all of the App.Tag
objects down to just the one that was selected.
The results of the model
hook will be used in the tag
handlebars template. In the tag
handlebars template we are displaying each article for the chosen tag. The other thing that is happening as a result of the selected
property being set in the model
hook is that the index
handlebars template will toggle the class of the <li>
tag used to display each tag so the most recently selected tag will display as being highlighted and the rest will not.
That is really all there is to it. If you are interested you can view my actual running source here.