A theme consists of an HTML layout template, an optional CSS template and a series of template snippets.
HTML and CSS
HTML is a language used to describe content on the web. HTML documents usually consist of a <head>
tag and a <body>
tag, but Podiant themes are only concerned with the content of the <body>
tag.
HTML is a set of instructions, that tell a web browser what type of content to expect, and when that content has finished. For example, code like this:
<p>This is some text.</p>
is interpreted by a web browser as a paragraph of text. The browser starts the paragraph where the <p>
tag finishes, and knows to stop formatting the text like a paragraph, when it encounters </p>
. Instructions can be nested, like this:
<p>Some of this text is <em>very</em> important.</p>
The part in-between the <em>
and </em>
tags is meant to be emphasised, and is usually rendered by web browsers in italics.
While HTML governs the content, CSS dictates how that content is displayed. Each web browser has a basic stylesheet a set of rules for displaying text and images) that say things like: "make text in-between <strong>
and </strong>
tags bold, and make text in-between <em>
and </em>
tags italic). CSS code for those rules might look like this:
strong {
font-weight: bold;
}
em {
font-style: italic;
}
A set of rules in CSS is called a stylesheet, and rules can be overridden in different circumstances. This is called "cascading", and is the CSS, or "Cascading Style Sheets". For example:
strong {
font-weight: bold;
}
em {
font-style: italic;
}
strong em {
color: red;
}
This would mean that any text in-between <em>
and </em>
tags in the main part of the document (or the root) would be rendered in the normal way, but if we nest a, <em>
tag inside a <strong>
tag, that text will become red, but only in that circumstance. So, it applies to this text:
<strong><em>This text is red</em></strong>
But not this:
<em>This text is not red</em>
Or this:
<strong>This text is not red either</strong>
Or this!
<em><strong>This text is still not red</strong></em>
Codeacademy has a great set of tutorials for working with HTML and CSS.
Handlebars
Podiant’s templating system allows you to write your own HTML and CSS to display content from Podiant. The content is loaded into the templates and can be accessed using variables and loops. To do this, we use Handlebars, a simple templating language that uses braces ({
and }
characters) to inject data into HTML pages (and CSS documents). For example, a theme can welcome people to a podcast using the following syntax:
<p>Welcome to {{ podcast_name }}.</p>
In this instance, {{ podcast_name }}
is automatically replaced with the name of the podcast. More information is available on the Handlebars.js website. In our examples, we put spaces around certain parts of the Handlebars syntax. This is valid but not present in Handlebars.js’ own documentation (we just find it more readable).
Escaping
Some variables (as indicated below) contain HTML. By default, Handlebars escapes HTML, meaning, instead of allowing the browser to process text like this (<b>bold</b>
) as bolded text, it instead prints the code out onto the page. This prevents attackers from taking advantage of developers forgetting to strip out certain HTML code when saving values to a database.
In some instances, it is necessary to print HTML to the browser. For instance, each episode has an embedded player, which is accessed via the iframe object (basically a window within a web page, that looks out onto another web page). Instead of surrounding the variable name with two braces as in the example in the previous section, use three, like this ({{{ iframe }}}
). This instructs the templating engine not to re-format the text to protect it.
We strongly recommend you only surround variables with three braces when instructed to do so. This is usually because the HTML that is provided has been pre-sanitised by Podiant.
The anatomy of a Podiant theme
A theme contains an HTML layout, a stylesheet and a number of HTML template snippets. The templating system first renders the HTML layout, then replaces the {{ yield }}
section with the relevant template snippet (for example, the episode_list
snippet) depending on the page currently being viewed. The system then processes the CSS and adds that to the header of the HTML page.
The HTML layout
This is the main overarching template, including masthead, sidebars, footer, etc. Individual templates are included by the Podiant theming system, and are used to represent individual pages. A layout needs to contain a {{ yield }}
or {{ yield main }}
call, which instructs the theming system where to place the individual templates. Layouts should also contain a {{ yield widgets }}
call, which instructs the theming system to load third-party widgets (Twitter profiles, MailChimp signup forms, etc) into an area designated by the theme designer. The {{ yield }}
syntax is separate from Handlebars.js.
A simple example:
<header>
<h1><a href="{{ podcast_url }}">{{ podcast_name }}</a></h1>
<p>{{ podcast_subtitle }}</p>
<ul class="menu">
{{# each main_menu }}
<li{{# if menu_item_selected }} class="selected"{{/ if }}>
<a href="{{ menu_item_url }}">{{ menu_item_title }}</a>
</li>
{{/ each }}
</ul>
</header>
<main>
{{ yield main }}
</main>
<aside>
<a href="/">
<img src="{{ main_artwork }}" />
</a>
<ul class="subscription-links">
{{# each subscription_links }}
<li class="{ link_class }}">
<a href="{{ link_url }}">
<img src="{{ link_icon }}" />
{{ link_title }}
</a>
</li>
{{/ each }}
</ul>
<ul class="social-links">
{{# each social_links }}
<li class="{{ link_class }}">
<a href="{{ link_url }}">{{ link_title }}</a>
</li>
{{/ each }}
</ul>
{{ yield widgets }}
</aside>
<footer class="podiant-credit">
<div class="container">
<a href="{{ podiant_url }}" target="_blank">
Podcast powered by
<img src="{{ podiant_logo }}" height="25" />
</a>
</div
</footer>
Note that there are no <head>
or <body>
elements. This is by design, as all content is added to the body of the page automatically.
The stylesheet
Themes can include their own CSS. Third-party CSS libraries can be used via the @import
syntax, and variables available to the HTML layout are also available in CSS.
A simple example:
header {
color: #fff;
background-color: {{{ podcast_banner_colour }}};
background-image: url('{{{ podcast_banner_image }}}');
}
Data available to all templates
The following variables can be used in theme layouts, stylesheets and individual templates.
rtl
Set to true
when right-to-left layout has been enabled on the podcast. This setting should be honoured, and special care taken to ensure text flows in the correct direction.
main_menu
A list of objects containing item URL, name and selected status. For example:
[
{
"menu_item_url": "/",
"menu_item_title": "Home",
"menu_item_selected": true
},
{
"menu_item_url": "/b/",
"menu_item_title": "Blog",
"menu_item_selected": false
}
]
podcast_url
The full URL to the podcast.
podcast_name
The name of the podcast, as specified in settings.
podcast_description
The podcast’s description, as specified in settings.
podcast_subtitle
The podcast’s subtitle, as specified in settings.
podcast_banner_image
The URL to a banner image, if specified in settings.
podcast_banner_colour
A CSS-ready RGB colour value, derived from reading the dominant colour in the podcast artwork.
strings
An object containing a list of all the translated strings, as defined in settings. The complete list is as follows:
{
"about": "About the podcast",
"blog": "Blog",
"chapters": "Chapters",
"description": "Show notes",
"download_episode": "Download episode",
"guest": "Guest",
"guests": "Guests",
"home": "Home",
"hosts": "Meet the hosts",
"latest_episode": "Latest episode",
"links": "Links",
"page_not_found": "Sorry, but this page could not be found.",
"read_more": "Read more"
"search": "Search",
"share": "Share",
"transcript": "Transcript",
}
The guests and hosts strings will change depending on context. For example, when viewing a single episode, if there is only one guest, guests
will be set to the translated equivalent of the English word “Guest”. Similarly, if a podcast has more than one host, the hosts
string will be set to the translated equivalent of the English word “Hosts”. This basically means that theme developers don’t need to test for pluralisation.
subscription_links
A list of objects containing link URLs, titles and other useful information for building a list of subscription links (Apple Podcasts, Stitcher, etc). For example:
[
{
"link_class": "apple",
"link_url": "http://example.com/",
"link_title": "Apple Podcasts",
"link_icon": "/apple.svg"
},
{
"link_class": "google",
"link_url": "http://example.com/",
"link_title": "Google Play Music",
"link_icon": "/google.svg"
}
]
social_links
A list of objects containing link URLs, titles and other useful information for building a list of social network links (Facebook, Twitter, etc). For example:
[
{
"link_class": "twitter",
"link_url": "http://twitter.com/",
"link_title": "Twitter"
},
{
"link_class": "facebook",
"link_url": "http://facebook.com/",
"link_title": "Facebook"
}
]
main_artwork
A URL to a small-ish representation of the podcast artwork, as uploaded in settings.
hosts
A list of objects containing names, URLs and other basic info relating to a podcast’s host(s). For example:
[
{
"host_name": "Joe Bloggs",
"host_name_split": ["Joe", "Blogs"],
"host_url": "/hosts/#host-123",
"host_image": "/joe.jpg"
}
]
urls
An object containing other important URLs not necessarily covered by the main menu. For example:
{
"home": "/",
"guests": "/g/",
"hosts": "/hosts/",
"blog": "/b/",
"search": "/search/"
}
podiant_url
The URL to the Podiant frontend website. Please include this in the footer of your theme.
podiant_logo
The URL to the Podiant logo in SVG form. Please include this as part of a link in the footer of your theme.
Individual templates
Along with the HTML layout and stylesheet, a theme comprises the following templates:
episode_list
A generic list of podcast episodes.
The page_obj.object_list
variable is exposed in this template. It is a list of objects with the following properties:
-
title
: the episode title -
image
: the episode or podcast artwork -
summary
: a short (20 word) summary of the episode -
published
: the published date of the episode -
iframe
: the HTML necessary to render an iframe. Use three braces on either side of the variable name to prevent the templating system from escaping it -
share_url
: the main URL to the episode -
download_url
: the download URL for the episode MP3 -
filesize
: the size of the downloadable file, in friendly language (ie: “2.5 mb”) -
share_urls
: A n object containing the following keys (twitter_share_url, facebook_share_url and pinterest_share_url), which point to pages on those sites for sharing a specific episode -
guests
: a list of objects containing a guest_url, guest_image and guest_name for each of the guests in the episode
home
The homepage, which should operate almost exactly like episode_list, but may contain slight presentation differences, like highlighting the most recent episode and showing any pinned blog posts.
In addition to the variables provided by the episode_list
template, the homepage also exposes a pinned_blog_posts
variable, which is a list of objects (which will either contain 0 or 1 object). The objects represent blog posts which the user has elected to pin to their homepage. The object contains the following properties:
-
post_url
: the URL of the blog post -
post_title
: the title of the post, if present -
post_image
: the post’s image, if present -
post_embed
: the HTML necessary to render an iframe for embedded content like polls and videos. Use three braces on either side of the variable name to prevent the templating system from escaping it -
post_summary
: a short (30 word) summary of the post
episode_detail
An individual episode template. This template should contain a {{ yield comments }}
call somewhere, so that the user’s chosen comments system can be dropped in, and a review_form
variable, which is used when submitting episodes for review by listeners, clients or co-producers.
The object
variable is exposed in this template. It is an object with the following properties:
-
title
: the episode title -
image
: the episode or podcast artwork -
description
: the full HTML show notes for the episode. Use three braces on either side of the variable name to prevent the templating system from escaping it -
transcript
: the episode transcript, in HTML. Use three braces on either side of the variable name to prevent the templating system from escaping it -
published
: the published date of the episode -
iframe
: the HTML necessary to render an iframe. Use three braces on either side of the variable name to prevent the templating system from escaping it -
share_url
: the main URL to the episode -
download_url
: the download URL for the episode MP3 -
filesize
: the size of the downloadable file, in friendly language (ie: “2.5 mb”) -
share_urls
: A n object containing the following keys (twitter_share_url
,facebook_share_url
andpinterest_share_url
), which point to pages on those sites for sharing a specific episode -
links
: a list of objects containinglink_title
andlink_url
for each link in an episode -
chapters
: a list of objects containingchapter_time
(the start point, in seconds),chapter_title
andchapter_url
(the episode URL with an added timecode argument) and optionally achapter_link
(an external URL) for each chapter of the episode -
guests
: a list of objects containing aguest_url
,guest_image
andguest_name
for each of the guests in the episode -
similar_episodes
: a list of objects containingepisode_url
,episode_image
,episode_title
,episode_summary
,podcast_url
andpodcast_name
which constitute related episodes from this, or other podcasts, for use in building a “You might also like” section
A review_form
string is also exposed, which should be surrounded by three braces so it’s not auto-escaped. This should be included just below the episode’s show notes, and is used by producers or clients who want to review an episode and make notes on it.
host_list
The list of podcast hosts.
The object_list
variable is exposed in this template. It is a list of objects with the following properties:
-
title
: the host’s name -
image
: the URL of the host’s photo -
description
: the host’s biography, in HTML. Use three braces on either side of the variable name to prevent the templating system from escaping it -
links
: a list of objects containing link_url, link_title and link_class (a single word like “twitter” or “facebook”) for each link a host has specified
page_detail
A template for generic pages, and pages that may contain extra functionality that it would be too time-consuming to add extra theming capability for (for example, the “Book a recording time” page).
The object
variable is exposed in this template. It is an object with the following properties:
-
title
: the title of the page -
image
: the URL to a header image, if used -
description
: the page’s main HTML content. Use three braces on either side of the variable name to prevent the templating system from escaping it -
share_url
: the main URL to the episode -
share_urls
: A n object containing the following keys (twitter_share_url
,facebook_share_url
andpinterest_share_url
), which point to pages on those sites for sharing a specific page
search_list
A list of items matching a given search query, and presenting a search form if no items were found.
The page_obj.object_list
variable is exposed in this template. It is a list of objects with the following properties:
-
url
: the URL to the found item -
image
: the URL to a thumbnail of the item, if found -
title
: the title of the found item -
title_highlighted
: the title, with added HTML tags around matching words. Use three braces on either side of the variable name to prevent the templating system from escaping it -
description
: a short summary of the item -
description_highlighted
: a short summary of the item, with added HTML tags around matching words. Use three braces on either side of the variable name to prevent the templating system from escaping it
A query
variable is also exposed to this template, which is set to the text the user searched for.
blogpost_list
A list of blog posts.
The page_obj.object_list
variable is exposed in this template. It is a list of objects with the following properties:
-
title
: the post title -
image
: the URL of the post’s image, if specified -
summary
: a short (30 word) summary of the post -
published
: the published date of the post -
embed
: the HTML necessary to render an iframe for embedded content like polls and videos. Use three braces on either side of the variable name to prevent the templating system from escaping it -
link_url
: the permalink for the post. If a link post, this will be set to the URL that the author has linked to. Otherwise it will be the URL to the blog post page itself -
link_target
: when pointing to an external link, this will be set totarget=“_blank"
(note the leading space), so it should be used (with three braces so it’s not auto-escaped by the templating system) when generating an<a>
tag for a post link. For example:<a href=“{{ link_url }}{{ link_target }}>{{ title }}</a>
. When thelink_url
value points to an internal URL, thelink_target
variable will be an empty string. -
is_poll
: set totrue
for poll posts -
share_url
: the main URL to the post -
share_urls
: A n object containing the following keys (twitter_share_url
,facebook_share_url
andpinterest_share_url
), which point to pages on those sites for sharing a specific post
blogpost_detail
A single blog post. Like the episode_detail
template, this should also contain a {{ yield comments }}
call, to show the user’s selected comment form.
The object
variable is exposed in this template. It is an object with the following properties:
-
title
: the post title -
image
: the URL of the post’s image, if specified -
published
: the published date of the post -
embed
: the HTML necessary to render an iframe for embedded content like polls and videos. Use three braces on either side of the variable name to prevent the templating system from escaping it -
link_url
: the permalink for the post. If a link post, this will be set to the URL that the author has linked to. Otherwise it will be the URL to the blog post page itself -
link_target
: when pointing to an external link, this will be set totarget=“_blank"
(note the leading space), so it should be used (with three braces so it’s not auto-escaped by the templating system) when generating an<a>
tag for a post link. For example:<a href=“{{ link_url }}{{ link_target }}>{{ title }}</a>
. When thelink_url
value points to an internal URL, thelink_target
variable will be an empty string. -
is_poll
: set totrue
for poll posts -
share_url
: the main URL to the post -
share_urls
: An object containing the following keys (twitter_share_url
,facebook_share_url
andpinterest_share_url
), which point to pages on those sites for sharing a specific post
guest_list
A list of people who have been guests of podcast episodes.
The page_obj.object_list
variable is exposed in this template. It is a list of objects with the following properties:
-
title
: the guest’s name -
image
: the URL of the guest’s photo -
description
: the guest’s biography, in HTML. Use three braces on either side of the variable name to prevent the templating system from escaping it -
share_url
: the URL to the guest’s page within the podcast site. -
links
: a list of objects containinglink_url
,link_title
andlink_icon
(a Font-Awesome HTML snippet which should be surrounded by three braces so it’s not auto-escaped) for each link a guest has specified
guest_detail
A single guest page, containing information about the guest and the episodes on which they’ve appeared.
The object
variable is exposed in this template. It is an object with the following properties:
-
title
: the guest’s name -
image
: the URL of the guest’s photo -
description
: the guest’s biography, in HTML. Use three braces on either side of the variable name to prevent the templating system from escaping it -
share_url
: the URL to the guest’s page within the podcast site. -
links
: a list of objects containinglink_url
,link_title
andlink_icon
(a Font-Awesome HTML snippet which should be surrounded by three braces so it’s not auto-escaped) for each link a guest has specified -
episodes
: a list of objects withepisode_url
,episode_title
,episode_image
,episode_summary
(which is full HTML and should be surrounded by three braces so it’s not auto-escaped),podcast_url
andpodcast_name
for podcast episodes the guest has appeared on
page_not_found
The 404 page. No page-specific data is exposed to this template, as the 404 message can be read from strings.page_not_found
.
Lists of objects
All list pages - except for one - use pagination (see the section below). In this instance page_obj.object_list
can be used to loop through objects. In the case of the host list page, this is simply object_list
, as pagination is not necessary.
An example of an episode loop:
{{# each page_obj.object_list }}
<article>
<a href="{{ share_url }}">
<img src="{{ image }}" />
</a>
<h3>
<a href="{{ share_url }}">{{ title }}</a>
</h3>
{{{ summary }}}
<a href="{{ share_url }}">{{ ../strings.read_more }}</a>
</article>
{{/ each }}
Note that inside an #each
loop, the variable names are scoped to the individual object, so title
will refer to the title of the episode in that iteration of the loop. You can access variables outside the current scope by prefixing them with ../
.
AJAX
Theme pages use AJAX and similar means to render content to a browser, which means things happen that don't require the entire web page to reload The client-side Mustache.js templating engine takes care of the display elements, while the server fetches data that is used to populate the templates.
When links to internal pages are clicked, only the server-side data is fetched, and the templates - already present in the HTML of the page - are used to render the content.
When customizing your theme, you can flip to virtually any page within a Podiant site, then click the "Customize" button and you'll find the template ready to edit.
Pagination
Pages using episode_list
, blogpost_list
, guest_list
and search_list
supply a page_obj
object, which gives information about the pagination state, and the list of objects in the current page. The object contains the following properties:
-
has_next_or_previous
: set totrue
if there is either a previous or a next page to navigate to. Use this to determine whether to show pagination or not -
has_previous
: set totrue
if the user is viewing page 2 or more -
has_next
: set totrue
if there is another page of results to display -
previous_page_number
: the number for the previous page of results -
next_page_number
: the number for the next page of results -
number
: the current page number -
object_list
: the list of items in the current page -
count
: the number of items in the current page -
first_object
: set to the first item in the page, if there are more than 0 items -
subsequent_objects
: a list of all but the first item in the page -
last_object
: the final item in the page, if there are more than 0 items -
all_but_last_object
: a list of all but the final item in the page
Pagination example:
{{# if page_obj.has_next_or_previous }}
<ul>
{{# if page_obj.has_previous }}
<li>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{{/ if }}
{{# each paginator.page_range }}
<li>
<a href="?page={{ number }}">{{ number }}</a>
</li>
{{/ each }}
{{# if page_obj.has_next }}
<li>
<a href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
{{/ if }}
</ul>
{{/ if }}
Infinite scroll
Instead of using traditional pagination links, themes can employ the infinite scroll technique. Any list of paginated objects should have an infinite-container
class added (this should be added to the list of items). Inside that list of items, each item should have an infinite-item
class. And at the end of the list, there should be a link like this:
{{# if page_obj.has_next }}
<a href="?page={{ page_obj.next_page_number }}" class="infinite-more-link">Next page</a>
{{/ if }}
The infinite-more-link
class is important, as that signals the infinite scroll system to trigger that link when the last item has entered the viewport. When the link is triggered, the next list of items is gathered from the server, rendered by Mustache and appended to the .infinite-container
list.
A simple example works like this:
<section class="infinite-container">
{{# each page_obj.object_list }}
<article class="infinite-item">
<h2><a href="{{ share_url }}">{{ title }}</a></h2>
{{{ summary }}}
</article>
{{/ each }}
{{# if page_obj.has_next }}
<a href="?page={{ page_obj.next_page_number }}" aria-label="Older posts" class="infinite-more-link">
<span aria-hidden="true">»</span> Older posts
</a>
{{/ if }}
</section>
Working with single object pages
Single object pages like episode_detail
, page_detail
and blogpost_detail
expose an object
variable which contains all the pertinent information for the particular item being viewed.
A simple example:
{{# with object }}
<article>
<header>
<h1>
<a href="{{ share_url }}">{{ title }}</a>
<small>{{ subtitle }}</small>
</h1>
</header>
{{{ iframe }}}
{{{ ../review_form }}}
{{ yield comments }}
{{# if similar_episodes.length }}
<h2>You might also like</h2>
<ul>
{{# each similar_episodes }}
<li>
<a href="{{ episode_url }}">{{ episode_title }}</a>
</li>
{{/ each }}
</ul>
{{/ if }}
<footer>
Published {{ date published 'MMMM D, YYYY' }}
</footer>
</article>
{{/ with }}
In this case, the #with
block can be used to act like an #each
block, but instead of looping through objects, it sets the current variable context to that of object
, so any variables referenced inside the #with
block are scoped to object
.
In this example of a single episode page, note the {{ ../review_form }}
notation. The variable name is prefixed with a ../
because it doesn’t belong as part of object
, but is in fact a top-level variable in its own right.
Date formatting
The Podiant theming system uses the Moment.js library for managing dates and times. Templates can make use of date-based fields like published, by using the date
helper. For example: {{ date published 'MMMM D, YYYY' }}
.
In this instance, date
is the name of the helper, published
is the name of the field we want to format, and the string enclosed in quotation marks is the format we want to use.
Here are some examples, directly from the Moment.js homepage:
-
dddd
: Sunday -
LT
: 5:04 PM -
MMM Do YY
: Dec 31st 17 -
MMMM Do YYYY, h:mm:ss a
: December 31st 2017, 5:03:14 pm
Previewing templates and debugging errors
Whenever you've made a change, click the "Preview" button at the top of the edtir, and the page should refresh to show your changes. If you see an error message in the bottom-left hand corner of your screen, you've made a syntax error, and you should carefully read through your changes and correct any mistakes. You can always hit the "Reset" button next to "Preview", to revert your changes back to the last saved version of the template.
Saving your changes
Once you're finished, make sure to click the "Save changes" button at the bottom of the page. Your browser should warn you if you navigate away from a page that the theme editor can help you with and you haven't yet saved your changes, but do remember to save often.
Using JavaScript
Use of external JavaScript resources is not possible, to best safeguard users against attacks. <script>
and <iframe>
tags are removed, and HTML attributes starting with on
(as in onclick
) are also stripped out.
Interactivity
Limited interactivity is enabled without the use of any custom JavaScript. You can toggle the active
or hidden
class of a targeted element by adding a data-toggle="active"
or data-toggle="hidden"
attribute to an <a>
tag. For example:
<a href="#showme" data-toggle="active">Show yourself</a>
<div id="showme">I am shown</div>
In this instance, any time the “Show yourself” link is clicked, the #showme
element will have a class of active
applied and then unapplied (or toggled). The same can be done with the hidden
class.
The theming system doesn’t do anything else, so it’s up to the theme designer to determine what to do with elements that have an active
or hidden
class added to them.
Right-to-left text
When the right-to-left text setting has been enabled for a podcast, the class
attribute of the <body>
tag will contain an rtl
class, which can be used within CSS. As mentioned above, an rtl
variable is also set to true
so template HTML can adapt accordingly.