I rebuilt my blog recently using Jekyll. In the process of migrating from blogspot.com I came across a few issues and limitations in the Liquid templating system that Jekyll is built on.

Two of these limitations were inserting Google Ads code and formatting inline image captions and description text. I didn't want to be constantly copy pasting a large chunk of HTML and needed a solution that did all that for me.

The Liquid templating language that Jekyll relies on has a few built in tags but they are both surprisingly few and generic.

Tags are the code that goes between the {% and %} tags. Example:

{% if %}

{% else %}

{% endif %}



Luckily you can create your own tags. It is easy and the cleanest way to inject and manipulate your static site code.

All custom tags should be saved with the extension .rb into a folder called /_plugins/ in the root of your site. You need to restart the Jekyll serve each time you make changes to these files to reload your plugins.

Custom Google Ads Tag

I wanted a simple instruction that would inject the ads code into my page everywhere I put it.

Given the tag code below I can now write {% ads %} directly into my blog posts as many times as I want and have Jekyll inject the requested Google ads code.

class AdsInlineTag < Liquid :: Tag def initialize ( tag_name , input , tokens ) super end def render ( context ) # Write the output HTML string output = "<div style= \" margin: 0 auto; padding: .8em 0; \" ><script async " output += "src= \" //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js \" >" output += "</script><ins class= \" adsbygoogle \" style= \" display:block \" data-ad-client= \" xxxxx \" " output += "data-ad-slot= \" yyyyyy \" data-ad-format= \" auto \" ></ins><script>(adsbygoogle =" output += "window.adsbygoogle || []).push({});</script></div>" # Render it on the page by returning it return output ; end end Liquid :: Template . register_tag ( 'ads' , AdsInlineTag )

But what if we want to sometimes display ads from a different slot or client?

You could create a tag for each variation or...

Accepting Tag Parameters

A better approach than copy/pasting your code for every combination of values would be to extend the tag code to accept parameters through the input variable.

One slight problem is that there is only one input parameter permitted in tags. A simple way around this is to split the input on a known separator.

In the example below the tag accepts both the ad-client number and the ad-slot number as a pipe ( | ) separated input value:

Allowing us to do this:

{% ads ca-pub-00000000000000|555555555 %}

class AdsInlineTag < Liquid :: Tag def initialize ( tag_name , input , tokens ) super @input = input end def render ( context ) # Split the input variable (omitting error checking) input_split = split_params ( @input ) adclient = input_split [ 0 ]. strip adslot = input_split [ 1 ]. strip # Write the output HTML string output = "<div style= \" margin: 0 auto; padding: .8em 0; \" ><script async " output += "src= \" //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js \" >" output += "</script><ins class= \" adsbygoogle \" style= \" display:block \" data-ad-client= \" #{ adclient } \" " output += "data-ad-slot= \" #{ adslot } \" data-ad-format= \" auto \" ></ins><script>(adsbygoogle =" output += "window.adsbygoogle || []).push({});</script></div>" # Render it on the page by returning it return output ; end def split_params ( params ) params . split ( "|" ) end end Liquid :: Template . register_tag ( 'ads' , AdsInlineTag )

The pipe separator trick quickly becomes hard to maintain. Not only are the parameter values cryptic but their order also matters. This makes it difficult to only change the adslot variable as you must pass in an empty or the default adclient value every time as the first value.

By supporting a JSON formatted parameter data we can pass in a more complicated and flexible configuration.

Customizing both ad-client and ad-slot:

{% ads {"adclient":"ca-pub-00000000000000", "adslot":"555555555"} %}

only modifying ad-slot

{% ads {"adslot":"8888888"} %}

still support the default with no parameters:

{% ads %}

require 'json' class AdsInlineTag < Liquid :: Tag def initialize ( tag_name , input , tokens ) super @input = input end def render ( context ) # Set defaults first, replace with your values! adclient = "xxxxxx" adslot = "yyyyy" # Attempt to parse the JSON if any is passed in begin if ( ! @input . nil? && ! @input . empty? ) jdata = JSON . parse ( @input ) if ( jdata . key? ( "adclient" ) ) adclient = jdata [ "adclient" ]. strip end adslot = jdata [ "adslot" ]. strip end rescue end # Write the output HTML string output = "<div style= \" margin: 0 auto; padding: .8em 0; \" ><script async " output += "src= \" //pagead2.googlesyndication.com/pagead/js/adsbygoogle.js \" >" output += "</script><ins class= \" adsbygoogle \" style= \" display:block \" data-ad-client= \" #{ adclient } \" " output += "data-ad-slot= \" #{ adslot } \" data-ad-format= \" auto \" ></ins><script>(adsbygoogle =" output += "window.adsbygoogle || []).push({});</script></div>" # Render it on the page by returning it return output ; end end Liquid :: Template . register_tag ( 'ads' , AdsInlineTag )

The inline image tag was a little bit more tricky as the tag class needed to be able to pull data out of the post page it was being rendered in (for the site baseurl parameter).

Luckily there is a clever way to do that:

class ImageWithCaptionTag < Liquid :: Tag def initialize ( tag_name , input , tokens ) super @input = input end # Lookup allows access to the page/post variables through the tag context def lookup ( context , name ) lookup = context name . split ( "." ). each { | value | lookup = lookup [ value ] } lookup end def render ( context ) # Accessing the page/site variable for the base url baseurl = " #{ lookup ( context , 'site.baseurl' ) } " # Reading the tag parameter (using the pipe-split technique) input_split = split_params ( @input ) img_path = input_split [ 0 ]. strip . downcase # Caption is an optional second parameter if ( input_split . length > 1 ) caption = input_split [ 1 ]. strip end # Create the HTML output for the image container with an optional caption # the 'captioned-image' css class controls the look and feel of the image # in my case the class centeres the image and poses maximum size restrictions output = "<div class= \" captioned-image \" ><div>" output += "<img src= \" #{ baseurl } / #{ img_path } \" alt= \" #{ caption } \" >" if ( ! caption . nil? && ! caption . empty? ) output += "<p> #{ caption } </p>" end output += "</div></div>" return output end def split_params ( params ) params . split ( "|" ) end end Liquid :: Template . register_tag ( 'imgc' , ImageWithCaptionTag )

This allows me to write code such as:

{% imgc img/picture.jpg|This is a image caption text. %}

The inline image tag still uses our simple pipe character trick to separate different input values. However this tag can be augmented with the JSON technique discussed above to achieve a more flexible configuration.

Using the tag techniques discussed in this post will allow you to create your own tags for almost every use. Tags can be very powerful and allow you great flexibility in your site and rendered output.

If you find yourself pasting HTML code between posts it might be time to create a tag to do that work for you.

Please enable JavaScript to view the comments powered by Disqus.