--- title: 'More on compilers: load, and templates' author: Jasper Van der Jeugt type: main --- Loading items ------------- The compiler Monad is a complex beast, but this is nicely hidden for the user of the Hakyll library. Suppose that you're generating `index.html` which shows your latest brilliant blogpost. This requires `posts/foo.markdown` to be generated *before* `index.html` (so we don't have to generate it twice). But you don't have to care about any of that: Hakyll will sort this out for you automatically! Let's see some quick examples. We can load a specific item: ```haskell load "posts/foo.markdown" :: Compiler (Item String) ``` Or a whole bunch of them: ```haskell loadAll "posts/*" :: Compiler [Item String] ``` Sometimes you just want the *contents* and not the `Item`: ```haskell loadBody "posts/foo.markdown" :: Compiler String ``` This is all useful if we want to use Hakyll's templating system. Templates --------- ### Basic templates Let's have a look at a simple template: ```html

$title$

Posted on $date$
$body$ ``` As you can probably guess, template files just contain text and only the `$` character has special meaning: text between dollar signs ("fields") is replaced when the template is applied. If you want an actual dollar sign in the output, use `$$`. You usually compile the templates from disk using the aptly named `templateBodyCompiler`: ```haskell match "templates/*" $ compile templateBodyCompiler ``` Notice the lack of `route` here: this is because we don't need to write the templates to your `_site` folder, we just want to use them elsewhere. ### Templates: Context We can easily guess the meaning of `$title$`, `$date$`, and `$body$`, but these are not hard-coded fields: they belong to a certain [Context]. A `Context` determines how the fields are interpreted. It's a [Monoid] and therefore very composable. [Context]: /reference/Hakyll-Web-Template-Context.html [Monoid]: http://learnyouahaskell.com/functors-applicative-functors-and-monoids `field` allows us to create a `Context` for a single field: ```haskell field :: String -> (Item a -> Compiler String) -> Context a ``` Let's try this out. Note that this is for illustration purposes only: you shouldn't have to write complicated fields often. We can implement the `$body$` field like this: ```haskell field "body" $ \item -> return (itemBody item) :: Context String ``` And `$title$` like this: ```haskell titleContext :: Context a titleContext = field "title" $ \item -> do metadata <- getMetadata (itemIdentifier item) return $ fromMaybe "No title" $ lookupString "title" metadata ``` And compose them using the `Monoid` instance: ```haskell context :: Context String context = mconcat [ titleContext , field "body" $ return . itemBody ] ``` Obviously, it would be tedious to implement things like `titleContext` over and over again for different websites and different fields. This is why hakyll provides `defaultContext`. `defaultContext` is a composed `Context` and allows you to use: - `$body$` for the body of the page; - `$url$` for the destination URL of the page; - `$path$` for the original filepath of the page; - `$foo$` where foo is specified in the metadata. `$date$` is not provided by default. In the scaffold, we use the convenience context function `dateField`, which will parse an `Item`'s filename to check if it begins with a date. You can see how we add it in the definition of `postCtx` in `site.hs`: ```haskell postCtx :: Context String postCtx = dateField "date" "%B %e, %Y" `mappend` defaultContext ``` ### Loading and applying templates Now we know about templates, context and how to load arbitrary items. This gives us enough background information in order to understand how you can apply a template: ```haskell compile $ do tpl <- loadBody "templates/post.html" pandocCompiler >>= applyTemplate tpl postCtx ``` Loading and then immediately applying a template is so common there's a shorthand function: ```haskell compile $ pandocCompiler >>= loadAndApplyTemplate "templates/post.html" postCtx ``` Control flow in templates ------------------------- Sometimes string interpolation does not suffice, and you want a little more control over how your templates are laid out. Hakyll provides a few control structures for this. The syntax for these structures was based on the syntax used in pandoc templates, since Hakyll already has tight integration with pandoc. ### Conditionals In `templates/post.html` of the example site we generated using `hakyll-init`, we see an example of a conditional: ```html
Posted on $date$ $if(author)$ by $author$ $endif$
``` This example should be pretty straightforward. One important thing to notice is that `$if(foo)$` **does not** check the truth value of `$foo$`: it merely checks if such a key is present. Note that an if-else form is supported as well: ```html
Posted on $date$ $if(author)$ by $author$ $else$ by some unknown author $endif$
``` ### Partials Partials allow you to [DRY] up your templates by putting repetitive actions into separate template files. You can then include them using `$partial("filename.html")$`. [DRY]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself An example can be found in `templates/archive.html`: ```html Here you can find all my previous posts: $partial("templates/post-list.html")$ ``` This partial is just another template and uses the same syntax. Note that in order to use something like this, we also need to load the partial template in our `site.hs`: ```haskell match "templates/post-list.html" $ compile templateCompiler ``` Fortunately, we usually don't need to add this since we already have: ```haskell match "templates/*" $ compile templateCompiler ``` ### Producing a list of items: for At this point, everything in the example site we generated should be clear to you, except for how we produce the list of posts in `archive.html` and `index.html`. Let's look at the `templates/post-list.html` template: ```html ``` This uses the `$for(foo)$` construct. This construct allows you loop over a list, in this case, `$posts$`. Inside the body of this for loop, all fields refer to the current post, e.g.: `$url$`, `$title$` and `$date$`. You can also add a simple separator with the special `$sep$` field. Everything between `$sep$` and `$endfor$` will be regarded as a separator that will only be shown if there is more than one item in the list. ```html ``` Of course, $posts$ does not magically appear. We have to specify this in `site.hs`. Let's look at how `archive.html` is generated: ```haskell posts <- recentFirst =<< loadAll "posts/*" let archiveCtx = listField "posts" postCtx (return posts) `mappend` constField "title" "Archives" `mappend` defaultContext ``` We discussed `loadAll` earlier in this tutorial. `recentFirst` sorts items by date. This relies on the convention that posts are always named `YYYY-MM-DD-title.extension` in Hakyll -- or that the date must be present in the metadata. ```haskell recentFirst :: [Item a] -> Compiler [Item a] ``` After loading and sorting the items, we use `listField` to create the `$posts$` key. ```haskell listField :: String -> Context a -> Compiler [Item a] -> Context b ``` The first parameter is simply the name of the key (`"posts"`). Secondly we have a `Context` with which all items should be rendered -- for our example site, we already wrote such a `Context` for posts: `postCtx`. Lastly, we have a `Compiler` which loads the items. We already loaded the items so we can just use `return posts`. The following snippet would produce the same result: ```haskell let archiveCtx = listField "posts" postCtx (recentFirst =<< loadAll "posts/*") `mappend` constField "title" "Archives" `mappend` defaultContext ```