Ember.js Applications in Jekyll

October 14, 2014

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.

Step 1: Get the data

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.

Step 2: Build some Handlebars templates to display the data

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 %}

Step 3: Write the Ember application

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.

The only good is knowledge, and the only evil is ignorance. - Herodotus