YAML and markdown based website rendering with AngularJS

A couple of weeks ago, Clark / @jeaneymerit told me he was digging into AngularJS, and as I’m working on a private project where a static website is involved, I thought this framework could help me make that website lightweight in terms of external dependencies.

The site I’m working on contains exclusively static content, and most of it is text, I wanted a simple and elegant method in order to manipulate that content easily, so I wrote a basic website generator in python based on jinja2, for the record it’s available here.

The principle is that a basic YAML file will contain the text of a page in a key / value fashion. For my own needs, there are two kinds of text, simple or markdown, the file look like this:

welcome: 'welcome!'
md_about: |
  **Yes**! this _new_ website is powered by [markdown][1]

  [1]: http://daringfireball.net/projects/markdown/

Now, with modern frameworks poping all around the web, I felt that “building” the website, even if it was a really simple task, was an oldschool approach, plus I needed a pretext to get my hands on AngularJS :)

It turns out there were plenty of modules to handle the YAML / markdown mechanism, I picked js-yaml and marked after trying a couple of other similar modules which were less effective and / or buggy.

Now the interesting part. I won’t explain AngularJS principles in this blog post as there are very informative and well written documentation on the subject, among them this quick tutorial from w3schools and of course the official AngularJS documentation; instead, I’ll drop here the main component of the AngularJS-based website, the controller:

var mySite = angular.module('mySite', ["ngSanitize"]);

mySite.controller('contentCtrl', function($scope, $parse, $http, $rootScope) {

    $http.get('content/index_' + $scope.lang + '.yaml')
    .success(function(response) {
        angular.forEach(jsyaml.load(response), function(v, k) {
            if (k.startsWith('md_') == true) {
                $parse('md.' + k.substring(3)).assign($scope, marked(v));
            } else {
                $parse(k).assign($scope, v);
            }
            if (k == 'title') {
                $rootScope.pageTitle = v;
            }
        });
    });
})

That’s right, in less than 20 lines of code, we read our YAML file, converted it to good old JSON and passed the content of both simple and markdown values to the HTML view.

A word of explanation on this controller. I chose that the YAML file name would correspond to the language the site is displayed with, for example, if lang == 'en', content/index_en.yaml will be read. The distinction between simple and markdown content is made by reading the first 3 characters of the key, if it begins with md_, then it’s markdown and it should be interpreted by marked.

There are two tricky traps here. First about the rendered markdown. For security reasons, AngularJS will not display the produced HTML when the expression is called with double braces {% raw %}{{ .. }}{% endraw %} within the HTML view, instead it will show the HTML code escaped. It is mandatory to reference the key variable through a ng-bind-html tag, and as the documentation explains, you’ll have to add angularjs-sanitize.js to the list of loaded modules.

Now about the scope variables organization. I learned that an AngularJS expression will not be interpreted as a scope variable if it is the result of a string manipulation, for example, if foo == 'about':

will only print md_foo, it will not reference the md_about scope variable. In order to play with dynamic naming, I was forced to organize the markdown variables in an array, thus the following trick in the controller:

so I can have the following code in the HTML view:

Why so much pain? Because thanks to this little trick, I’ll be able to create loops for similar blocks in the HTML view, such as:

I like to factorize.

I’m yet a total AngularJS newbie, if something in this blogpost seem awful to you, ng-guru, please feel free to contact me!