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  and pinterest_share_url ), which point to pages on those sites for sharing a specific episode
  • links : a list of objects containing link_title  and link_url  for each link in an episode
  • chapters : a list of objects containing chapter_time  (the start point, in seconds), chapter_title  and chapter_url  (the episode URL with an added timecode argument) and optionally a chapter_link  (an external URL) for each chapter of the episode
  • guests : a list of objects containing a guest_url , guest_image  and guest_name  for each of the guests in the episode
  • similar_episodes : a list of objects containing episode_url , episode_image , episode_title , episode_summary , podcast_url  and podcast_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  and pinterest_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 to  target=“_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 the link_url  value points to an internal URL, the link_target  variable will be an empty string.
  • is_poll : set to true  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  and pinterest_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 to  target=“_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 the link_url  value points to an internal URL, the link_target  variable will be an empty string.
  • is_poll : set to true  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  and pinterest_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 containing link_url , link_title  and link_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 containing link_url , link_title  and link_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 with episode_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  and podcast_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 to true  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 to true  if the user is viewing page 2 or more
  • has_next : set to true  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">&raquo;</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.

Did this answer your question?