Modifying Document Properties in Nested DocPad Layouts

August 15th 2015 DocPad

DocPad Layout Basics

One of the main DocPad features is support for layouts, which are used to ensure a common design for multiple pages. This is an example of a simple layout:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%= @document.title %></title>
</head>
<body>
    <h1>Site Name</h1>
    <h2><%= @document.title %></h2>
    <%- @content %>
</body>
</html>

It's using Eco (Embedded CoffeeScript) templating engine and shouldn't be difficult to understand even if you're seeing it for the first time.

This could be a page using this layout:

---
layout: "base"
title: "Page Title"
---
<p>Page content</p>

The header part specifies the name of the layout to use and sets document properties, which are accessed in the layout using the @document.propertyName syntax. The rest of the page replaces @content in the layout file. Here's the resulting generated page:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Page Title</title>
</head>
<body>
    <h1>Site Name</h1>
    <h2>Page Title</h2>
    <p>Page content</p> 
</body>
</html>

Nested Layouts

Layouts can even be nested, which opens doors to a new set options. A typical scenario for using them would be several different types of pages in a single site with a common basic design; e.g. articles could always display their author:

---
layout: "base"
---
<p>Author: <%= @document.author %></p>
<p>Published on: <%= @document.date %></p>
<%- @content %>

The sub-layout will get injected into the base layout. The page will need to define the additional properties, expected by it:

---
layout: "article"
title: "Article Title"
author: "Damir Arh"
---
<p>Page content</p>

The page will be generated as one would intuitively expect:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Page Title</title>
</head>
<body>
    <h1>Site Name</h1>
    <h2>Page Title</h2>
    <p>Author: Damir Arh</p>
    <p>Page content</p>
</body>
</html>

Common Document Properties in Layouts

Once I started using nested layouts, I soon wanted to set certain document properties in the sub-layout instead of directly on the page, because the value was common to all pages based on that layout and I didn't want to repeat myself.

Section name is a good example of such a property. The base layout would take care of displaying it:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%= @document.title %></title>
</head>
<body>
    <h1>Site Name - <%= @document.section %></h1>
    <h2><%= @document.title %></h2>
    <%- @content %>
</body>
</html>

To set the section name directly in the page, I could just add it to the header along with all the other properties:

---
layout: "article"
title: "Article Title"
author: "Damir Arh"
section: "Articles"
---
<p>Page content</p>

Somehow I expected this to work even if I would put it in the sub-layout header instead. To my surprise, the property value remained uninitialized. Giving it some more thought, I realized it couldn't have worked: instead of setting a property on the page, I was setting it on the sub-layout. There's a different syntax to access page properties from a layout, and I've already been using it to display them. There's no reason I couldn't set the values the same way:

---
layout: "base"
---
<% @document.section = "Articles" %>
<p>Author: <%= @document.author %></p>
<%- @content %>

Now, the pages don't need to include this value any more:

---
layout: "article"
title: "Article Title"
author: "Damir Arh"
---
<p>Page content</p>

The generated page will still have properly initialized property value:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Page Title</title>
</head>
<body>
    <h1>Site Name - Articles</h1>
    <h2>Page Title</h2>
    <p>Author: Damir Arh</p>
    <p>Page content</p>
</body>
</html>

I didn't manage to find any such example online and spent too much time figuring it out. In my opinion, that's a reason enough for writing this blog post.

Copyright
Creative Commons License