Alexander D Huang
Alexander D Huang

Categories

How-tos

Tags

Jekyll uses Liquid, which was created by Shopify and written in Ruby, as its template language. For making a tag archive page, we have to do a little bit of dirty work because of the lack of power of Liquid. But first, let me describe what a tag page we want to make here.

On the top of the tag archive page, there is a list of labels, for example,

Ignoring the style, we can see that each label has a tag name and the count of tags. Besides, each label has a link to the section of the related posts list. These labels are first sorted by the counts and then by the alphabetic.

Talk is cheap. Show me the code.

OK, I show you the code now:


{% assign tags_max = 0 %}
{% for tag in site.tags %}
  {% if tag[1].size > tags_max %}
    {% assign tags_max = tag[1].size %}
  {% endif %}
{% endfor %}

{% assign tag_names_array = "" %}
{% assign tag_counts = "" %}
{% assign first_array_element = true %}
{% for i in (1..tags_max) reversed %}
  {% assign tag_names = "" %}
  {% assign first_tag = true %}

  {% for tag in site.tags %}
    {% if tag[1].size == i %}
      {% if first_tag %}
        {% assign first_tag = false %}
      {% else %}
        {% assign tag_names = tag_names | append: "," %}
      {% endif %}
      {% assign tag_names = tag_names | append: tag[0] %}
    {% endif %}
  {% endfor %}

  {% if tag_names != "" %}
    {% assign tag_names = tag_names | split: "," | sort | join: "," %}

    {% if first_array_element %}
      {% assign first_array_element = false %}
    {% else %}
      {% assign tag_names_array = tag_names_array | append: "|" %}
      {% assign tag_counts = tag_counts | append: "|" %}
    {% endif %}
    {% assign tag_names_array = tag_names_array | append: tag_names %}
    {% assign tag_counts = tag_counts | append: i %}
  {% endif %}
{% endfor %}

{% assign tag_names_array = tag_names_array | split: "|" %}
{% assign tag_counts = tag_counts | split: "|" %}


<ul class="taxonomy-index">
  {% for tag_names in tag_names_array %}
    {% assign tag_names_list = tag_names | split: "," %}
    {% assign tag_count = tag_counts[forloop.index0] %}
    {% for tag_name in tag_names_list %}
      <li>
        <a href="#{{ tag_name | slugify }}">
          <strong>{{ tag_name }}</strong> <span class="taxonomy-count">{{ tag_count }}</span>
        </a>
      </li>
    {% endfor %}
  {% endfor %}
</ul>

It looks dirty, so let us walk through the code for better understanding.


{% assign tags_max = 0 %}
{% for tag in site.tags %}
  {% if tag[1].size > tags_max %}
    {% assign tags_max = tag[1].size %}
  {% endif %}
{% endfor %}

This segment is for calculating the maximum counts of tags. {% site.tags %} is a hash of posts indexed by the tag, for example,

{ 
    'tech' => [<Post A>, <Post B>],
    'ruby' => [<Post B>] 
}

Then we define two strings tag_names_array and tag_counts. What we want to have are two arrays, but by the lack of syntax for directly creating arrays in Liquid, we play a trick here. We use a long string to collect tag names; each element is delimited by a vertical line |, and each tag name in each element is delimited by a comma ,. For example,

"tech,ruby|jekyll|html,css,javascript"

Similarly, we also use a string to collect tag counts; each count is delimited by a vertical line |.

Next, we define an auxiliary Boolean value first_array_element. If the first element is appended to the array, it will be set to false. It is used to check whether a delimiter | should be appended to the array.

Next, we iterate from tags_max to 1, and inside this loop, we define two variables tag_names and first_tag. Their roles are similar with tag_names_array and tag_counts. Then we create an inner loop to find all tags whose count is matched with i:


{% for tag in site.tags %}
  {% if tag[1].size == i %}
    {% if first_tag %}
      {% assign first_tag = false %}
    {% else %}
      {% assign tag_names = tag_names | append: "," %}
    {% endif %}
    {% assign tag_names = tag_names | append: tag[0] %}
  {% endif %}
{% endfor %}

After escaping this loop, if tag_names is not an empty string, that means we have collected tags whose counts are equal to i. So we append tag_names to tag_names_array, and at the same time, append i to tag_counts.


{% if tag_names != "" %}
  {% assign tag_names = tag_names | split: "," | sort | join: "," %}
  {% if first_array_element %}
    {% assign first_array_element = false %}
  {% else %}
    {% assign tag_names_array = tag_names_array | append: "|" %}
    {% assign tag_counts = tag_counts | append: "|" %}
  {% endif %}
  {% assign tag_names_array = tag_names_array | append: tag_names %}
  {% assign tag_counts = tag_counts | append: i %}
{% endif %}

Now we can make two real arrays by calling split:


{% assign tag_names_array = tag_names_array | split: "|" %}
{% assign tag_counts = tag_counts | split: "|" %}

Until now, all the things we do are prepare works. Let’s do a real job: showing the list of labels.


<ul class="taxonomy-index">
  {% for tag_names in tag_names_array %}
    {% assign tag_names_list = tag_names | split: "," %}
    {% assign tag_count = tag_counts[forloop.index0] %}
    {% for tag_name in tag_names_list %}
      <li>
        <a href="#{{ tag_name | slugify }}">
          <strong>{{ tag_name }}</strong> <span class="taxonomy-count">{{ tag_count }}</span>
        </a>
      </li>
    {% endfor %}
  {% endfor %}
</ul>

At last, we need to show the post entries for each tag:


{% for tag_names in tag_names_array %}
  {% assign tag_names_list = tag_names | split: "," %}
  {% for tag_name in tag_names_list %}
    <section id="{{ tag_name | slugify | downcase }}" class="taxonomy-section">
      <h2 class="taxonomy-title">{{ tag_name }}</h2>
      {% for tag in site.tags %}
        {% if tag[0] == tag_name %}
          <div>
            {% for entry in tag.last %}
              {% comment %} Show the entry of each post in the style you like. {% endcomment %} 
            {% endfor %}
          </div>
        {% endif %}
      {% endfor %}
    </section>
  {% endfor %}
{% endfor %}

Since we have finished a tag archive page, I think a category archive page is also easy to make by little modifications.