Twitter Email
subspace (/ˈsʌbspɛɪs/)
A Jekyll playground site

Examples for custom Liquid template tags for Jekyll

Last modified: , 1347 Words
17:51 on Friday, 08. October 2021 | by Admin in Jekyll

This page contains a couple of examples how to implement custom Liquid template tags as Jekyll plugins. Such plugins are of varying complexity, depending on the desired functionality, but normally not to difficult

Jekyll Logo
Jekyll Logo
Jekyll Logo

Some Ruby and general programming knowledge is excepted for understanding this article. The basic idea is that a tag does something and outputs valid HTML, which replaces the tag in the source document. A tag can also take parameters and has access to all of the document, including its meta data. It also can access site data which is usually located in the data directory. Each tag is technically a Jekyll plugin written in Ruby plugin and must reside as single .rb file in the _plugins directory of your site’s source code folder. There is no need to list them in the _config.yaml since _plugins is always parsed when building the site.

While it is technically possible to have multiple tags in the same source file, I would advise against it as it makes things messier and harder to control. One file per tag, name the file like the tag to make clear what it does.

For the first example, we write a simple tag and name it sep (which shall stand for „content SEParator”). It’s purpose will be very simple: Output a horizontal line, using the <hr> HTML tag. Styling is done via CSS. The code is simple enough to be understood with even basic Ruby knowledge.

Here is the code:

This is the first and most simple version. It renders a <hr> tag, using a fixed (hard coded) CSS class named content-separator.

Code: (click to select all)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# all tags must be part of the Jekyll module and inherit from Liquid::Tag
module Jekyll
  class SepTag < Liquid::Tag

    # constructor, init the superclass and remember text (the parameter string)
    def initialize(tag_name, text, tokens)
      super
      @text = text      # we do not really need this in our example.
    end

    # the render() method must return the generated HTML 
    # output the result. That's all, really :)
    def render(context)
      output = "<hr class=\"content-separator\">"
      return output
    end
  end
end

Liquid::Template.register_tag('sep', Jekyll::SepTag)

This works, but it is fairly limited. Obviously, it would be nice to pass the name of the CSS class as parameter, wouldn’t it? That way, you could use the tag to render different type of content separators. Fortunately, this is easy to implement with only a couple more lines as shown in the next version of the plugin. All Liquid Tags can take an arbitrary number of parameters and all parameters are passed to the tag’s constructor as a single string in the second parameter name text. What you do with these argument is completely up to you, for this simple plugin, we interpret the argument string as a CSS class (or more than a single class when the parameter contains spaces).

For more advanced tags, having multiple arguments are likely something desirable, so using a common format like JSON would certainly be a good idea. More on that later.

Now, here is a version of the the separator tag that can take a parameter and use it as a CSS class.

Code: (click to select all)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Tags are part of the Jekyll module and must inherit from Liquid::Tag
module Jekyll
  class SepTag < Liquid::Tag

    # constructor, remember text which holds the parameters passed
    # to the tag as a single string
    # also, set a default in case no parameter was passed.
    def initialize(tag_name, text, tokens)
      super
      @defaultclass = "default-content-separator"
      @text = text.blank? ? @defaultclass : text
    end

    # render the ouput. Make sure, we have a valid cssclass
    def render(context)
      params = @text.split("|")
      cssclass = params[0].strip
      cssclass.blank? ? @defaultclass : cssclass

      output = "<hr class=\"" + cssclass + "\">"
      return output
    end
  end
end
Liquid::Template.register_tag('sep', Jekyll::SepTag)

Well, that’s it. We have our tag and can use it with or without a parameter.

1
2
3
4
  {% sep %}              renders a HR with the default CSS class default-content-separator
  {% sep foobar %}       renders a HR of class foobar.
  {% sep foobar thin %}  renders a HR of class "foobar thin".
  
Here is an example of 2 separators using different CSS classes


The CSS:

Code: (click to select all)
1
2
3
4
5
6
7
8
9
hr.content-separator {
    height: 5px;
    border: none;
    margin: 1em 30px;
    background-color: $accent_color;
}
hr.content-separator.thin {
    height: 3px;
}


Another example of a tag, this time a bit more complex

Here is a slightly more complex tag. It embeds videos from either YouTube or other sites and offers a couple of options. This tag accepts various arguments which must be formatted as valid JSON. You can either pass a full URL to a compatible video (mp4 or webm) or a YouTube video ID.

Why JSON for the parameters?

Because it works and is straightforward for most web developers and tech folk nowadays. It has become one of the most popular formats for passing and storing data on the web and unless your data sets become very complex, it’s fairly easy to use. JSON knowledge is also considered a basic skill requirement for many developers.

The following example embeds a video directly by its URL.

{% vid { "url": "https://i.imgur.com/PTRPi4I.mp4", "width": "95%" } %}

This will embed a YouTube video

{% vid { "id": "wB8jCwkO-sw", "width": "95%" } %}

The width parameter is optional and sets the container width. It must be in valid CSS format including the unit. Other valid parameters are wrapper to set the CSS class for the wrapper element, while style can be used to set CSS attributes for the wrapper element directly. The style and width parameters are mutually exclusive.

Code: (click to select all)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Tags are part of the Jekyll module and must inherit from Liquid::Tag
require 'json'

module Jekyll
  # implements a simple video tag
  class VidTag < Liquid::Tag

    # constructor, remember text which holds the parameters passed
    # to the tag as a single string (JSON format expected)
    def initialize(tag_name, text, tokens)
      super
      @text = text
    end

    # render the ouput.
    def render(context)
      # paramter string must exist
      if ( !@text.nil? && !@text.empty? )
        jdata = JSON.parse(@text)
        if( jdata.key?('url') || jdata.key?("id") )
          output = ""

          #allow a CSS class for the wrapper element
          wrapperclass = jdata.key?("wrapper") ? jdata["wrapper"] : ""

          # and a custom width
          width = jdata.key?("width") ? jdata["width"] : ""

          # reasonable defaults for the <video> tag
          options = jdata.key?("options") ? jdata["options"] : "controls muted"
          yt = jdata.key?("id") ? 1 : 0

          if ( width != "" )
            output = '<div style="width: ' + width + '; margin:auto;" class="' + wrapperclass + '">'
          else
            output = '<div class="' + wrapperclass + '">'
          end
          
          output += '<div class="ytcontainer">'
          if (yt == 1)
            id = jdata.key?("id") ? jdata["id"] : ""
            if( id == "" )
              return ""
            end
            output += '<iframe class="yt" allowfullscreen src="https://www.youtube.com/embed/' + id + '"></iframe>'
          else
            output += '<video class="yt" ' + options + ' poster="' + jdata["url"].strip + '">'
            output += '<source src="' + jdata["url"].strip + '" type="video/mp4">'
            output += '<source src="' + jdata["url"].strip + '" type="video/webm">'
            output += '</video><'
          end
          output += '</div></div>'
          return output
        end
        return ""
      end
      return ""
    end
  end
end
Liquid::Template.register_tag('vid', Jekyll::VidTag)
This CSS fragment is required for the code to work
Code: (click to select all)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
div.ytcontainer {
    position: relative;
    width: 100%;
    height: 0;
    padding-bottom: 56.25%;
}
iframe.yt, video.yt {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

Example with a YouTube Video.