Docs » Common patterns

The following are some common templating problems that people often run into when building a Webhook powered site.

RSS

Webhook recognizes .xml files in the /pages/ directory. That means you can create a feed.xml file or something in there and then just copy/paste out a forloop from your articles/list.html page and adding in some xml markup. Often, people will create a single item form for "Feed details" or something to pull in the titles or other variables needed in more complex feeds like iTunes feeds. Below we'll create one just using some Settings from our general settings form in Webhook itself.

Here's a basic feed.xml page pulling content from an articles content-type.

<?xml version="1.0"?>

<rss version="2.0">
  <channel>
    <title>{{ getSetting('site_name') }}</title>
    <link>{{ getSetting('site_url') }}</link>
    <description>{{ getSetting('site_description') }}</description>
      {# Limit the RSS to 10 entries, and sort by most recently published. #}
      {% for object in cms.blog|sort('publish_date', true)|slice(0,10) %}
      <item>
        <title>{{ object.name }}</title>
        {# Web need Webhook to build a FULL url, not a relative one. #}
        <link>{{ getSetting('site_url') }}{{ url(object) }}</link>
        <description>
          {# The escape filter escapes our HTML so readers can read it. #}
          {{ object.body|safe|escape }}
        </description>
        <pubDate>{{ object.publish_date }}</pubDate>
      </item>
    {% endfor %}
  </channel>
</rss>

Quicky JSON / JSONP API

Webhook stores your CMS data inside a Firebase which you can access using the secret key in your .firebase.conf file. However, most people don't have the time to learn how Firebase works and just want to generate some Quick JSON endpoints for their data. This is useful if you want to call data from your CMS into a separate service. Using the |json or |jsonp filters we can build simple JSON endpoints accessible from a URL structure. For example, let's say we have a Content Type called articles that we want accessible through json. We'd just add a /pages/articles-json.html page with the following.

{{ cms.articles|json }}

{# Alternatively, you could use JSONP to load it through a script tag. #}
{{ cms.articles|jsonp }}

The above will give you a full data dump of all our articles which can now be accessed at http://www.example.com/articles-json/. If we wanted to separate this data into smaller chunks, we could do something similar, but on the articles themselves. We're going to add the below example to /templates/articles/json.html.

{{ item|json }

Now we have separate json for each article that lives at http://www.example.com/articles/article-name/json/.

Template based menus

When creating menus in your templates it's a good rule of thumb to use the {{ url() }} function to build out your URLs rather than hard coding them into the template. Here's a super simple menu in a couple different ways assuming multi-item forms for Articles, Podcasts and Videos and an single-itme About Us page.

{# Without using true here, we wouldn't get the About Us page. #}
{% set types = getTypes(true) %}

<ul>
  {% for type in types|sort('name') %}
    <li><a href="{{ url(type) }}">{{ type.name }}</a></li>
  {% endfor %}
</ul>

We could also manually build some of this stuff, while still protecting the URLs. Webhook allows you to pass the content type id as a string to the URL parameter to build our links to that content type's list.html page.

<ul>
  <li><a href="{{ url('articles') }}">Articles</a></li>
  <li><a href="{{ url('videos') }}">Videos</a></li>
  <li><a href="{{ url('podcasts') }}">Podcasts</a></li>
  <li><a href="{{ url('aboutus') }}">About us</a></li>
</ul>

CMS defined menus

Webhook does not include a Menu manager for your site. But there's no reason you can't create your own. Here is a simple way to create a two-level menu that is sortable and changeable through your CMS. The only disadvantage to this method is that you are hard coding the relative urls of your menu somewhere. If your urls change, you'll need to do that in the CMS as well.

  1. Create a multi-item form in your CMS called "Menu". Add the following fields.
    • Name
    • Sort (number field)
    • Level (multiple choice field, values of "First", "Second")
    • Relative URL (Text field)
    • Related items (After you've created the form, edit it and add a relationship field that links to the "Menu" form.)
  2. Add some menu items. Relate your "Second" level menus to your "First" level menus as appropriate.
  3. Put the following code somewhere.
<ul>
  {# List out our first level items, and sort them by the sort number we define. #}
  {% for first in menu|where('level', 'First')|sort('sort') %}
    <li>
      <a href="{{ first.relative_url }}">{{ first.name }}</a>
      <ul>
        {% for second in first.related_items %}
          <li><a href="{{ second.relative_url }}">{{ second.name }}</a></li>
        {% endfor %}
      </ul>
    </li>
  {% enfor %}
</ul>

You may wonder why we don't sort the second live of menu items as we did the first? Well. That's because Webhook lets us drag and drop the order of related items in the CMS itself, so using the sort field on secondary items isn't needed. You certainly could use the sort field if you wanted, but it might be a little tedious to keep up.

The best way to handle breadcrumbs of any kind are likely by using blocks in your templates. Blocks allow you to extend and overwrite pieces of parent templates in the child templates below them. Here's a short example of a simple breadcrumb system.

In your base.html or some other parent template you'd want to establish the part of the breadcrumb you'd want on all pages.

<ul>
   <li><a href="/">Home</a></li>
   {% block breadcrumb %}{% endblock %}
</ul>

Then in your list page you'd extend your base and overwrite that block with the portion you need.

{# This bit is likely there already #}
{% extends "templates/partials/base.html" %}

{# This bit will fill into the block you have in base of the same name #}
{% block breadcrumb %}
   {# substitute movies with the name of your type #}
  <li><a href="{{ url('movies') }}">Movies</a></li>
{% endblock %}

Now for your individual page...

{# Again, make sure you're extending your parent. #}
{% extends "templates/partials/base.html" %}

{% block breadcrumb %}
    {# You could manually write movies here, but why not just grab the type from the item itself. #}
    <li><a href="{{ url(item._type) }}">{{ item._type }}</a></li>
    <li><a href="{{ url(item) }}">{{ item.name }}</a></li>
{% endblock %}

Powerful SEO title control

Webhook by default will use the {{ item.name }} field for the page title used on your site. Sometimes though your editorial titles aren't optimized for Google searches. The solution? Make separate fields in your CMS content-type to let people manually set the page titles to be more SEO friendly. Here's how we'd set it up in the CMS.

With the above fields added, our users can now optionally add more search friendly titles and descriptions. We just need to adjust the template code now to use them if set. Here we assume you've kept the default blocks for your Webhook site.

{% extends "templates/base.html" %}
{% block title %}
  {% if item.seo_title %}
    {{ item.seo_title }}
  {% else %}
    {{ item.name}}
  {% endif %}
{% endblock %}

Create lists built from multiple content-types

Webhook allows you to merge data from separate content types into a single mashed array. Here's a common example for an editorial feed on your homepage.

{% set editorial = merge(cms.articles, cms.reviews, cms.videos) %}

This allows us to loop through a merged list of our articles, reviews and videos. But to get the most benefit from it you'll also want to make sure those content types all use same-named fields (like "image" and "deck") so that you can output that content easily in your forloop like below.

{% set editorial = merge(cms.articles, cms.reviews, cms.videos) %}

{% for item in editorial %}
  {# We used the same names for our fields in all three content types, so we can call them directly #}
  {{ item.name }}
  {{ item.deck }}
  <img src="{{ item.image|imageCrop(50, 50) }}"/>

  {# But if we need, we can still check against the content-type _type to output something just for videos #}
  {% if item._type == "video" %}
    {{ item.field_specific_to_video_only }}
  {% endif %}
{% endfor %}

Note that you can use the _type property to figure out what content-type a specific object is. It returns the content id of the content type itself, in the above case, it tells us it's a "video" content-type.

Group content by categories using groupBy

Often times you want to group a list of content into sub-sections of related, shared characteristics. For example, the documentation menu to the left is grouped by a category field. To better layout our that menu, we group by category and then sort within that category by another field created in the CMS called "sort". Here's how it looks in the CMS.

To actually group them this way on the frontend layer like the menu on the left we use the |groupBy filter from Swig JS.

{# Grab the docs from the CMS #}
{% set docs = get('documentation')%}
<dl class="docs-menu">
  {# Group the docs into subsets by category #}
  {% for cat in docs|groupBy('category') %}
    {# The loop.key outputs the category field value. "Learn" #}
    <dt>{{loop.key}}</dt>
    {# Sort within that category by a field called "sort". #}
    {% for object in cat|sort("sort") %}
      {# Set some special classes based on if we're on that page, and if these docs need a submenu (which is just a CMS checkbox) #}
      <dd class="{% if object.submenu == "Yes" %}submenu{% endif %} {% if getCurrentUrl() == url(object) %}active{% endif %}">
        <a href="{{ url(object) }}">{{ object.name }}</a>
      </dd>
    {% endfor %}
  {% endfor %}
</dl>