Using liquid to sort posts alphabetically

18,013

Solution 1

It can be done without a plugin, which means that it works with Github Pages.

You have to use some ugly string manipulation tricks, though.
I used a similar approach to implement a tag page (that lists all posts for each tag).

Same approach, slightly modified:

{% capture posts %}
  {% for post in site.posts %}
    |{{ post.title }}#{{ post.url }}
  {% endfor %}
{% endcapture %}
{% assign sortedposts = posts | split: '|' | sort %}
{% for post in sortedposts %}
    {% assign postitems = post | split: '#' %}
    <a href={{ postitems[1] }}">{{ postitems[0] }}</a><br>
{% endfor %}

Beware:

You need two different separator characters inside the first loop (and of course again in the split calls later on).
In order for this to work, both characters must not occur in any of the post titles or URLs!!

I'm using | and # in this example, which works for me (I just tested it with my blog). But you might need to use different characters, depending on your post titles and how your URLs are constructed.


Bonus:

If you want to display only the posts in a certain tag/category (and not all posts), you can change the first for loop (the one inside the capture) to one of these:

{% for post in site.tags['whatever'] %}

{% for post in site.categories['whatever'] %}

Solution 2

According to the documentation, to filter an array by one of its field, you can use :

    {% assign sortedPosts = site.posts | sort: 'title' %}

Then the sortedPosts variable will contain the sorted array.

The documentation can be found here : https://docs.shopify.com/themes/liquid/filters/array-filters#sort

Solution 3

It's both clean and elegant to sort in Jekyll in GitHub pages without a plugin. Use your .yml data file in the _data directory. I use a data file here called team-members.yml:

{% assign sorted_team = site.data.team-members | sort:'title' %}
{% for member in sorted_team %}
    <span class="title">{{ member.title }}</span>
{% endfor %}

This pattern will handle what you need to do here.

Solution 4

I adapted a Jekyll plugin from https://gist.github.com/3812259 to accomplish this. I couldn't use the plugin as-is, because it failed in the presence of null values. I'm a beginning ruby programmer, and coded the null handling with help from https://stackoverflow.com/a/808721/1135052

sort_for example reversing the sort and performing case-sensitive string comparisons (ignored if the sorted property is not a string):

{% sorted_for node in site.pages reversed sort_by:title case_sensitive:true %}
  {{ node.title }}
{% endsorted_for %}

sorted_keys_for example:

{% sorted_keys_for tag in site.tags %}
  <a href="/tags/{{ tag | downcase | replace:" ","-"}}.html">{{ tag }}</a><br />
  Num posts: {{ site.tags[tag].size }}
{% endsorted_keys_for %}

For use in Jekyll, put this code in _plugins/sort_for.rb

module Jekyll
  module SortedForImpl
    def render(context)
      sorted_collection = collection_to_sort context
      return if sorted_collection.empty?
      sort_attr = @attributes['sort_by']
      case_sensitive = @attributes['case_sensitive'] == 'true'
      i = sorted_collection.first

      if sort_attr != nil
        if i.to_liquid[sort_attr].instance_of? String and not case_sensitive
          sorted_collection.sort_by! { |i|
            k = i.to_liquid[sort_attr]
            k ? k.downcase : ''
          }
        else
          sorted_collection.sort_by! { |i|
            k = i.to_liquid[sort_attr]
            [k ? 1 : 0,k || 1]
          }
        end
      else
        if i.instance_of? String and not case_sensitive
          sorted_collection.sort_by! { |i| i.downcase }
        else
          sorted_collection.sort!
        end
      end

      original_name = @collection_name
      result = nil
      context.stack do
        sorted_collection_name = "#{@collection_name}_sorted".sub('.', '_')
        context[sorted_collection_name] = sorted_collection
        @collection_name = sorted_collection_name
        result = super
        @collection_name = original_name
      end
      result
    end
  end

  class SortedForTag < Liquid::For
    include SortedForImpl

    def collection_to_sort(context)
      return context[@collection_name].dup
    end

    def end_tag
      'endsorted_for'
    end
  end

  class SortedKeysForTag < Liquid::For
    include SortedForImpl

    def collection_to_sort(context)
      return context[@collection_name].keys
    end

    def end_tag
      'endsorted_keys_for'
    end
  end
end

Liquid::Template.register_tag('sorted_for', Jekyll::SortedForTag)
Liquid::Template.register_tag('sorted_keys_for', Jekyll::SortedKeysForTag)

Solution 5

I wanted to add following for future reference.

To sort posts by title, you can use sort filter. See http://jekyllrb.com/docs/templates/#filters

So, this works:

{% assign sorted_threat_posts = site.categories.threat | sort: 'title', 'last' %}
{% for post in sorted_threat_posts %}
   <li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
Share:
18,013

Related videos on Youtube

Jacques Tardie
Author by

Jacques Tardie

Updated on June 04, 2022

Comments

  • Jacques Tardie
    Jacques Tardie about 2 years

    Is there a way to sort a number of posts alphabetically, using Jekyll?

    I have something like this now:

    {% for post in site.categories.threat %}
    <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
    

    It works, but the posts are jumbled up. Would look much nicer if they were sorted alphabetically I think.

    Thanks

  • user1242101
    user1242101 over 10 years
    This is exactly what I needed for my simple table of contents, cheers!
  • Tony Brasunas
    Tony Brasunas over 9 years
    Hmm. This doesn't work for me. I'm using {% assign sorted_team = site.data.team-members | sort: 'title', 'name' %} But I get compile errors: Liquid Exception: wrong number of arguments (3 for 2)
  • Miloš Miljković
    Miloš Miljković over 8 years
  • tomohulk
    tomohulk about 8 years
    this worked perfect for me, I did with my post titles.
  • SeanFromIT
    SeanFromIT almost 6 years
    Using the split on | I wound up with an empty first entry in the array, so I wrapped {% unless forloop.first %} {% endunless %} around the output to ignore it.