Jekyll Inline Conditionals With Liquid Filters


Beyond The Ternary Operator

Posted on Tuesday, November 1, 2016 by Charles Beynon

Main Content

Often times when writing Jekyll templates, we need to do some conditional matching on certain blocks of text, returning one value in one instance, and a different value in another instance. Many times only a small portion needs to be changed, such as the name of a class, or inserting a default value when a variable is nil.

Unfortunately Jekyll’s Liquid syntax does not provide inline conditionals such as the ternary operator. However, we can implement one ourselves using custom Liquid filters. In this article, I discuss several such operators that can simplify your Liquid code, and have the benefit of being easy to chain.

Ternary Operators Testing for Existence

Take a block from a Jekyll template like so:

{% if page.feature %}
<header style="background-image: url('/images/{{ page.feature.image }}')">
{% else %}
<header style="background-image: url('/images/{{ site.feature.image }}')">
{% endif %}

There’s quite a bit of duplication here already, and if the header block included more variable attributes (such as a class name) things could get quickly out of hand.

I have many blocks like this in the theme I created for this site. In order to make things easier to read (and thus, easier for future me to maintain), I wrote a simple Jekyll filter.

_plugins/ternary.rb
module Jekyll
module TernaryFilters
def if(value, true_output, untrue_output = '')
value ? true_output : untrue_output
end
end
Liquid::Template.register_filter(Jekyll::TernaryFilters)

Using this filter I can simplify the template code to

<header style="background-image: url('/images/{{ page.feature | if: page.feature.image, site.feature.image }}')">

For readers unfamiliar with ternary operators, what this is doing is testing if page.feature exits and, if it is, returns page.feature.image; otherwise, it returns site.feature.image.

This is still a bit wordy in some cases; for example, if the tested value is the same as the first return value, we would duplicate that slice of code. Read ahead for an even DRYer solution.

Providing Defaults for Blank Values

We can add a second filter to this plugin module. Note that here we are testing explicity if the value passed to the filter is nil, which with Jekyll is true both when the YAML front-matter is missing or when the tag is present but left blank. When the value is missing, we return the default, otherwise we return the value itself.

_plugins/ternary.rb
module Jekyll
module TernaryFilters
...
def default(value, default)
value.nil? ? default : value
end
end
end
...

Using this filter we can rewrite this template snippet as

<header style="background-image: url('/images/{{ page.feature | default: site.feature.image }}')">

Now this is a nice, DRY way to write Jekyll.

Ternary Operator Matching on Values

The previous two scenarious dealt with missing values, but there are cases where we want to test equality instead, returning two different values. In my Jekyll theme this comes up often in the navigation bar, when determining the active tab. Without any custom filters, a single link in the navbar would look like

{% if page.slug == 'about' %}
<li class="active">
{% else %}
<li class="inactive">
{% end %}
<a href="/about/">About</a>
</li>

This block of code is repeated for each static page, along with once in a for loop for the category pages. Not only is this excessively wordy, it separates the opening and closing li tags in a way that could be confusing in a larger template.

We can repeate the pattern for the previous filters, this time taking three arguments: a matcher to match against, and the true_value and false_value to return depending on wheter the inpug value matches the matcher.

_plugins/ternary.rb
module Jekyll
module TernaryFilters
...
def matches(value, matcher, true_output, untrue_output = '')
value == matcher ? true_output : untrue_output
end
end
end
...

With this filter, we can easily have a list of links with only the appropriate one marked as active

<li class="{{ page.slug | matches: 'tags', 'active', 'inactive' }}">
<a href="/tags/">Tags</a>
</li>
<li class="{{ page.slug | matches: 'about', 'active', 'inactive' }}">
<a href="/about/">About</a>
</li>
<li class="{{ page.slug | matches: 'resume', 'active', 'inactive' }}">
<a href="/resume/">Résumé</a>
</li>

If I wanted to, I could created an even more specialized form of this filter named something like active_if, and return active or inactive directly.

Final Module and Conclusion

Combining all of these along with a reverse of the first if filter (called, appropriately enough, unless), the final plugin is below.

_plugins/ternary.rbdownload
module Jekyll
module TernaryFilters
def if(value, true_output, untrue_output = '')
value ? true_output : untrue_output
end
def unless(value, untrue_output, true_output = '')
value ? true_output : untrue_output
end
def matches(value, matcher, true_output, untrue_output = '')
value == matcher ? true_output : untrue_output
end
def default(value, default)
value.nil? ? default : value
end
end
end
Liquid::Template.register_filter(Jekyll::TernaryFilters)

I’ve found it much easier to develop complex Jekyll templates with the conditional logic inline, particularly as control structures get nested. I’ll be releasing full code of my theme in the coming days, so stay tuned.


Social Sharing Links

Comments