Building a Statocles Theme
Statocles, like most content management systems, uses templates to render structured data into HTML. In Statocles, a collection of templates is called a theme.
A Statocles theme is a directory in your site. Inside this directory, the theme is organized into subdirectories based on which application or purpose a template serves. A simple Statocles theme could contain these directories:
blog
: Templates for the Blog applicationlayout
: Layout templates, wrappers for application contentsite
: Site-wide meta-templates likesitemap.xml
androbots.txt
css
: CSS files required by our theme
These directories can contain any kind of files, including CSS,
JavaScript, and images, but the most important files they contain are
Statocles templates, which end in a .ep
file extention.
Embedded Perl
The .ep
stands for "Embedded Perl", and is the template syntax used by
the Mojolicious web application framework.
This syntax is similar in concept to Ruby's "Embedded Ruby" (.erb
):
<%
and%>
surround sections of Perl code<%= ... %>
writes the result of the...
expression%
on the beginning of a line means the rest of the line is Perl code%=
is a single-line expression that prints the result
For example, to create a variable, we could use the single-line %
template directive:
% my $name = "Hazel Murphy";
Then later, to write the variable, we could use the tag directive (<%=
... %>
):
Hi, my name is <%= $name %>!
If we need to have multiple lines of code, we could prefix each line
with a %
:
% my @crew = (
% 'Quentin Quinn',
% 'Jodene Sparks',
% 'Debbie Dupree',
% 'Hesh Hefflewhite',
% );
Or we can use the tag directive:
<%
my @crew = (
'Quentin Quinn',
'Jodene Sparks',
'Debbie Dupree',
'Hesh Hefflewhite',
);
%>
Or, if we need to have template content inside our line of code, we
can use the special begin
/end
directives. These are especially
helpful when using template helpers, below.
%= markdown begin
* This is plain text
* Which will be given
* To the Markdown helper
% end
The rest of this guide will walk through creating a theme from scratch using Statocles's template syntax.
Create a Blog Post Template
The first template we will create is the easiest: Individual blog posts. This template will show the full text of our blog post, along with the list of tags for our post which will be linked to the tag pages.
To start creating our new theme, create a theme
directory. Inside
that, create a blog
directory. Inside the blog
directory, create
a file called post.html.ep
. This is the default name
for the blog post template (blog/post.html.ep
).
Each template has its own set of variables inside. These variables are
documented in the application class. Read the Blog app
documentation. By default, all templates have
access to the current page object ($page
), the current application
object ($app
), and the current site object ($site
).
Templates also have access to "helpers", which are functions that can insert content, render Markdown, and include other files into the current template. Custom helpers can be added to a Statocles site through plugins (see the development guide for more information on custom helpers).
Template Values
For our blog post template, lets start by displaying the post title in
an <h1>
tag. The blog post is our page object ($page
), so to get
the title, we call the title
method: $page->title
. To insert the
page title, we use the <%= ... %>
template expression syntax, like so:
<h1><%= $page->title %></h1>
Before we go any further, we have a potential problem: What if our title
contains HTML characters like <
and >
? To quickly escape HTML from
accidentally wreaking havoc on our template, we can use <%== ... %>
instead:
<h1><%== $page->title %></h1>
Now our page title is safe from minor HTML escaping issues (though likely not against actual malice, so be sure you trust your content).
Template Conditionals
For out next piece of data, let's add the post's author. Like the title,
we can get the author by calling the author
method, like
$page->author
. The author field in the post is optional, so we only
want to add the tag if we know who the post's author is. For that, we
can use a Perl conditional if
statement. Let's add our byline in an
<aside>
tag, so people know it isn't part of the main post content.
% if ( $page->author ) {
<aside>by <%= $page->author %></aside>
% }
So now if our post has an author, say "Hazel Murphy", our post will be
properly attributed with <aside>by Hazel Murphy</aside>
.
Main Content
Next, let's add the blog post body content. This time, we'll use the
content
helper, which gives us the default content for the page. Since
this is on a line by itself, we can use the %
line syntax, like so:
%= content
Just like with <%= ... %>
and <%== ... %>
, one =
(%=
) means
"replace me with content" and two =
(%==
) means "replace me with
HTML-escaped content".
Template Loops
Finally, we need to list the tags our post has. Additionally, we need those tags to be linked to the tag page that lists all the posts with that tag. For this, we'll need to use Perl's for-loop syntax.
Just like the title and author, the post tags can be found in the tags
method. Unlike title and author, this method returns a list of
Statocles link objects. Link objects have their
own methods for getting the link text (text
) and URL (href
).
Let's add our tag links in a list element. We'll give this element the
tags
class so we can add some CSS to it later when we get to the
layout template. Inside the list, we'll loop over the tags, and create
links with the tag's URL and tag name:
<ul class="tags">
% for my $tag ( $page->tags ) {
<li>
<a href="<%= $tag->href %>">
<%= $tag->text %>
</a>
</li>
% }
</ul>
Notice that we can put <%= ... %>
anywhere we want, even inside of the
href="..."
attribute. Just make sure to close the template tag with
%>
before closing the attribute with quotes.
Finished Template
Now we've got a blog post template that shows the post title, the byline, the content, and the tags. Our finished template looks like this:
<h1><%== $page->title %></h1>
% if ( $page->author ) {
<aside>by <%= $page->author %></aside>
% }
%= content
<ul class="tags">
% for my $tag ( $page->tags ) {
<li>
<a href="<%= $tag->href %>">
<%= $tag->text %>
</a>
</li>
% }
</ul>
Remember to save this in your theme's blog
directory as post.html.ep
.
Create a Blog Index Template
Next, we need to create a template to display a list of posts, which will become the front page of our blog (and our entire site). This list of posts will display the post title and byline, the first section of the post's content, and the list of tags.
Since the index contains a list of pages, we need a loop. Once again, we
have a $page
object, but this time we have a pages
method which will
return all the posts we should display on this page.
% for my $page ( $page->pages ) {
For each post, we should display the title, linked to the main post. To
ensure we have a complete URL for our page, we use the url
helper,
which accepts our page object and returns the absolute URL to the page,
including the site's base URL. And, like before, to get the title, we
use the title
method, making sure to HTML escape the output with
<%==
.
% for my $page ( $page->pages ) {
<article>
<h1>
<a href="<%= url $page %>">
<%== $page->title %>
</a>
</h1>
Finally, we should display an excerpt from the post. Each individual
post can divide their content into sections using ---
. By default, the
first section will be shown here on the index, along with a link to read
the entire article. To get the first section, we can use the sections
method. We can pass in all the section numbers we want, starting with
0
as the first section.
So, first lets show the first section's content:
<%= $page->sections( 0 ) %>
Then we can, if necessary, show the link to read the rest of the post.
Once again we'll use the url
helper to get the right link.
% if ( $page->sections > 1 ) {
<p>
<a href="<%= url $page %>">
Continue reading <%== $page->title %>
</a>
</p>
% }
Remember to save this in your theme's blog
directory as
index.html.ep
.
Deep Linking to Sections
If you'd like to link users directly to the second section, you can
replace the blog post content
helper with a loop over the post
sections, adding an ID to each section. This way the user doesn't have
to skip over parts of the post they've already read.
So, in the blog/post.html.ep
, replace the %= content
with:
% for my $i ( 0..$page->sections ) {
<section id="section-<%= $i %>">
<%= $page->sections( $i ) %>
</section>
% }
Then, in the blog/index.html.ep
, you can add #section-1
to the
Continue reading
link to drop the user right where they left off:
<a href="<%= url $page %>#section-1">
Continue reading <%== $page->title %>
</a>
Create a Layout Template
Finally, we should make a layout template. The layout template surrounds every content template, like the blog post and blog index we just made. This allows us to have site-wide navigations, headers, footers, scripts, and themes.
Unlike the content templates, the layout template is extremely simple. The most important additions the layout contributes are the HTML boilerplate tags, like so:
<!DOCTYPE html>
<html>
<head>
<title><%== $page->title %> - <%== $site->title %></title>
</head>
<body>
%= content
</body>
</html>
In this basic layout template, we provide the bare minimum HTML
structure: A DOCTYPE
and <html>
element, a <head>
element that
contains a <title>
, in which we add the page's title
attribute and
the site's title
attribute (HTML escaped of course), and a <body>
element that uses the content
helper to print the main page content
(the blog post or blog index).
Document Scripts and Stylesheets
Unfortunately, this basic layout template does not enable many of the features that our content requires. Remember from the content guide, every document is allowed to add custom scripts and stylesheets. This feature is implemented by the layout template. Every theme bundles with Statocles has this feature, so we should add it to our theme, too.
Like the content templates we created above, the layout template gets
the current page, the current app, and the current site as variables.
The links to the scripts and stylesheets that we want are available from
the links()
method. This method takes an argument, which is the links
key. For stylesheets, the key is stylesheet
, and for scripts, the key
is script
. The method then returns a list of link
objects which have methods to get the link URL
(href
) and link text (text
).
Documents can have multiple links, so we need a loop. First we'll loop
over the stylesheets and add <link/>
tags, then we'll loop over the
scripts and add <script>
tags.
<head>
<title><%== $page->title %> - <%== $site->title %></title>
% for my $link ( $page->links( 'stylesheet' ) ) {
<link href="<%= $link->href %>" rel="stylesheet" />
% }
% for my $link ( $page->links( 'script' ) ) {
<script src="<%= $link->href %>"></script>
% }
</head>
Now when users add custom scripts and stylesheets to their documents,
they will be added to the <head>
element.
Navigations
The final thing we should add to our layout is a navigation. This is how
the site's nav
configuration works. See the config guide
for information on nav
.
Like the page object's links()
method, the site object has a nav()
method which gets the named nav and returns a list of link
objects which have methods to get the link URL
(href
) and link text (text
).
Let's allow users to create a nav called main
, which we will display
at the very top of the page. To do this, we call the nav()
method with
the name of the nav we want to get: main
. We then loop over the links
and create our list.
<nav>
<ul>
% for my $link ( $site->nav( 'main' ) ) {
<li>
<a href="<%= $link->href %>">
<%= $link->text %>
</a>
</li>
% }
</ul>
</nav>
If we want to make the main nav optional, we can enclose our <ul>
in
a conditional (if ( $site->nav( 'main' ) ) {
).
Our final layout template looks like this:
<!DOCTYPE html>
<html>
<head>
<title><%== $page->title %> - <%== $site->title %></title>
% for my $link ( $page->links( 'stylesheet' ) ) {
<link href="<%= $link->href %>" rel="stylesheet" />
% }
% for my $link ( $page->links( 'script' ) ) {
<script src="<%= $link->href %>"></script>
% }
</head>
<body>
<nav>
<ul>
% for my $link ( $site->nav( 'main' ) ) {
<li>
<a href="<%= $link->href %>">
<%= $link->text %>
</a>
</li>
% }
</ul>
</nav>
%= content
</body>
</html>
But we have one more thing we need to do before our site is ready.
Copy templates from other themes
There are a lot of templates that make up a Statocles site. Though this does make themes a bit more work to create, ultimately it means flexibility to make your site look how you want it to, even in the syndicated feed and sitemap files.
But, when we don't need to customize these files, we can just copy them
from the default theme using Statocles's bundle theme
command. The
remaining templates we need for our site are:
blog/index.atom.ep
- The blog Atom feedblog/index.rss.ep
- The blog RSS feedsite/sitemap.xml.ep
- The sitemap filesite/robots.txt.ep
- The robots.txt file
Rather than walk through writing all these templates, let's simply copy them from the Statocles default theme. To do this, we run the following commands:
statocles bundle theme default blog/index.atom.ep blog/index.rss.ep
statocles bundle theme default site/sitemap.xml.ep site/robots.txt.ep
This will copy the specified files from the default
theme to our theme
directory.
Now we have everything we need to build our site. Use the statocles
build
command to test your site, which will be written to the
.statocles/build
directory. Or use the statocles daemon
command to
launch a local web server to view your site in your web browser.
Be sure to read the content guide to see what content
features you want to add to your theme, especially links
and images
.
Data Attributes
Every page, site, and application is allowed to define additional,
free-form data in the data
attribute. Theme authors can use this data
to make optional configurable sections. For example, a user could add
their Disqus site name to their site object to enable Disqus comments.
Or a user could add their personal Twitter account to show a link to
their Twitter, and a few entries from their Twitter timeline.
To use data attributes, you can use the data
method of the object you
want ($site
for site data, $app
for application data, $page
for
page data). All data attributes are hashes, which are accessed using
curly braces ({...}
). As an example, to access the "github" key of the
site data attribute, we do:
The root of the data attribute must be a hash, but it allows arbitrary
data inside the values. So, to allow a Twitter handle and a configurable
number of tweets from the timeline, we could add this to our site.yml
config file:
---
site:
data:
twitter:
username: preaction
timeline_size: 5
And then we can access the data like so:
% if ( $site->data->{twitter} ) {
<script src="twitter.js"></script>
<script>
/* This JavaScript is completely made up */
show_timeline(
"<%= $site->data->{twitter}{username} %>",
<%= $site->data->{twitter}{timeline_size} || 20 %>
);
</script>
% }
Notice that we made a default for the timeline_size
value of 20
. It's
good to make sane defaults as often as possible to make it easier on
the users of your theme.
Good use of data attributes can make customizing your sites a lot easier, at the expense of making your theme more complex. You can use data attributes to build grids, configure the size of elements, show and hide navigations, and more.
The default themes allow some data
attributes. These attributes are
detailed in the config guide.
Template Helpers
Template helpers are a pluggable bit of code that add some power to your content. Statocles comes with a few built-in helpers, listed below. Additional helpers can come from plugins. See the development guide for more information about building custom helpers.
Includes
The include
helper allows you to include another template into this
template. This can be useful when there are sections of template that
need to be duplicated in multiple places, or when you want to provide
a way for users of your theme to customize parts of it.
The include
helper takes the file to include as an argument. In
templates, include paths are relative to the theme root directory.
For example, let's create an include that contains a standard
disclaimer. We'll put this include in the include/disclaimer.html.ep
file:
<aside><strong>Disclaimer:</strong> This blog is written by a trained
professional. Viewer discretion is advised.</aside>
Now we can include this file in any template we wish, or, as mentioned in the content guide, any document we wish, like so:
%= include 'include/disclaimer.html.ep'
Include Parameters
The included template has access to all the template variables and
helpers of the parent template, like $page
, $app
, and $site
, so we
could create an include that explains how to cite our page in academic
papers (like Wikipedia):
<p>To cite this article: <code>
<%= $site->url( $page->path ) %>;
<%= $page->author %>;
<%= $page->date %>
</code></p>
In addition to the variables from the parent template, we can pass
extra variables in the include
helper. These variables override those
from the parent template.
So, we can create an image template that displays an image along with a caption and a custom background color.
First, we create our include. Let's call it include/image-bg.html.ep
.
Inside this template, we'll use the $color
variable to control the
background color and have a default background color to lightblue
. We'll
use the $image_src
variable for the image's URL, and the $caption
variable for the image caption:
<div style="background-color: <%= $color || 'lightblue' %>">
<img src="<%= $image_src %>" />
<p><%= $caption %></p>
</div>
Now we can pass in this data using the include
helper:
<%= include 'include/image-bg.html.ep',
image_src => '/images/logo-1984.jpg',
caption => 'Our logo in the year 1984',
color => 'grey',
%>
The final rendered HTML will look like:
<div style="background-color: grey">
<img src="/images/logo-1984.jpg" />
<p>Our logo in the year 1984</p>
</div>
Default Includes
The default Statocles themes come with the following default includes which you can customize:
site/head_after.html.ep
- This include comes immediately before the closing
</head>
tag and allows additional scripts and stylesheets.
- This include comes immediately before the closing
site/navbar_extra.html.ep
- This include is inside the header navigation bar, and allows for custom icons and links.
site/header_after.html.ep
- This include comes immediately after the page header, which includes the main navigation.
site/sidebar_before.html.ep
- This include is at the top of the sidebar.
site/footer.html.ep
- This include is located in the page footer.
These files are not overwritten when using statocles bundle theme
, so
it is safe to edit these files.
Markdown
The markdown
helper allows you to render Markdown into HTML from
inside the template. The markdown
helper takes a single argument, the
Markdown to render, and returns the rendered HTML, like so:
%= markdown begin
* This list will be turned to HTML
* Because of the `markdown` helper
% end
This is very useful when it comes to data attributes. For example, we can add a "byline" to our document that we can then display when appropriate.
We add our byline in the document header under the data
attribute:
---
data:
byline: |-
[Author Writerson](http://example.com)
([email](mailto:writerson@example.com)
[twitter](http://twitter.com/authorwriter))
And then we can render the byline Markdown in our template:
%= markdown $page->data->{byline};
Highlight
The highlight
helper adds syntax highlighting for code and
configuration blocks. This helper uses the optional
Syntax::Highlight::Engine::Kate module from
CPAN. See the
Install guide for instructions on installing optional
dependencies.
The highlight
helper takes two arguments: The syntax name to use and
the content. The content will then be given to the syntax highlighter
and the resulting HTML placed into your page. The syntax name comes from
Syntax::Highlighter::Kate. Some examples are:
- perl
- html
- javascript
- css
For demonstration, here's an example of Perl code highlighted with the
highlight
helper:
%= highlight perl => begin
use strict;
use warnings;
my $name = "Hazel Murphy";
print $name;
% end
use strict;
use warnings;
my $name = "Hazel Murphy";
print $name;
Named Content Sections
Named content sections are an advanced feature of Statocles that allows special content to be passed up through the templates that make up a single page. For example, the document content can include some content that will be then be used by the page template or layout template. This makes it easier to do things like adding sections to the layout sidebar or footer, but only on specific pages.
To create a named content section, we use the content
helper. The
first argument is the section name, and the second argument is the
content. Remember you can use begin
/end
to use template directives
in the content section.
% content byline => begin
by <%== $page->author %>
% end
With this byline
content created, we can recall it in our layout using
the content
helper with only the name of the content we want.
%= content 'byline'
The default Statocles themes include content sections for tags
and
feeds
which allow the Blog app to list all of its tags and feeds,
respectively.