69

I'm constructing a static site (no blog) with Jekyll/Liquid. I want it to have an auto-generated navigation menu that lists all existing pages and highlight the current page. The items should be added to the menu in a particular order. Therefore, I define a weight property in the pages' YAML:

---
layout : default
title  : Some title
weight : 5
---

The navigation menu is constructed as follows:

<ul>
  {% for p in site.pages | sort:weight %}
    <li>
      <a {% if p.url == page.url %}class="active"{% endif %} href="{{ p.url }}">
        {{ p.title }}
      </a>
    </li>
  {% endfor %}
</ul>

This creates links to all existing pages, but they're unsorted, the sort filter seems to be ignored. Obviously, I'm doing something wrong, but I can't figure out what.

flyx
  • 35,506
  • 7
  • 89
  • 126
  • I just found out: The `sort` *does* something. If a site does not provide a `weight`, it gets written last. But if it does provide one, it is still not ordered according to it, but according to the file name. – flyx Jan 29 '12 at 21:53
  • 4
    I believe that the sort filter might only work with output markup (things wrapped in {{ }}, not {% %}). So, it might not work with as a filter on the for loop. My comment is based on this page: https://github.com/Shopify/liquid/wiki/Liquid-for-Designers and that it says the filters are for output markup. – Owen Jan 31 '12 at 14:46

10 Answers10

75

Since Jekyll 2.2.0 you can sort an array of objects by any object property. You can now do :

{% assign pages = site.pages | sort:"weight"  %}
<ul>
  {% for p in pages %}
    <li>
      <a {% if p.url == page.url %}class="active"{% endif %} href="{{ p.url }}">
        {{ p.title }}
      </a>
    </li>
  {% endfor %}
</ul>

And save a lot of build time compared to @kikito solution.

edit: You MUST assign your sorting property as an integer weight: 10 and not as a string weight: "10".

Assigning sorting properties as string will ends up in a a string sort like "1, 10, 11, 2, 20, ..."

David Jacquel
  • 51,670
  • 6
  • 121
  • 147
  • 1
    does not work for me (Jekyll 2.4.0). I defined the weight property in the pages as said above, but the sort seems to ignore it. – eyettea Oct 02 '14 at 13:51
  • 2
    @eyetea you are right. We need to make an assignment first. I've editer my code and it works on Jekyll 2.4.0. ;-) – David Jacquel Oct 02 '14 at 15:42
  • Thanks for the help. I also edited the code and removed the second sort filter, since it looks like it's not needed anymore. – eyettea Oct 02 '14 at 19:11
  • You're right. I've edited it myself because your suggested edit was rejected by 3 users ??? – David Jacquel Oct 02 '14 at 20:48
  • strange, I just deleted the "| sort: weight"... Why would it be rejected? Anyway, problem solved. – eyettea Oct 03 '14 at 01:20
  • I get an error in 2.4.0: `Error: comparison of Jekyll::Page with Jekyll::Page failed` .... ? – Dominik Mar 04 '15 at 05:05
  • mh turns out you can't use decimal numbers like `2.1` as weight... :) have to put them into quotes. Thanks though – Dominik Mar 04 '15 at 05:26
36

Your only option seems to be using a double loop.

<ul>
{% for weight in (1..10) %}
  {% for p in site.pages %}
    {% if p.weight == weight %}
      <li>
        <a {% if p.url == page.url %}class="active"{% endif %} href="{{ p.url }}">
          {{ p.title }}
        </a>
      </li>
    {% endif %}
  {% endfor %}
{% endfor %}
</ul>

Ugly as it is, it should work. If you also have pages without a weight, you will have to include an additional internal loop just doing {% unless p.weight %} before/after the current internal one.

kikito
  • 51,734
  • 32
  • 149
  • 189
  • 1
    lol. I guess you can trim that down by compressing everything into one single line of code if that is a concern. Unfortunately liquid doesn't have a `{%-` `%}` prefix to collapse empty lines like erb. – kikito Mar 06 '12 at 12:09
  • 3
    Just an addition: Replacing (1..10) with (1..site.pages.size) makes this loop as short as possible, and will work regardless of how many pages you have. Thanks for a stupid yet highly clever hack :) – Markus Amalthea Magnuson Aug 24 '13 at 16:47
  • @MarkusAmaltheaMagnuson the `(1..10)` on this code represent possible weights. It could be replaced by `(1..MAX_WEIGHT)` to make it a bit more clear (and have MAX_WEIGHT defined somewhere else, like in a constants file). – kikito Aug 29 '13 at 08:23
  • 1
    This worked for me, except that the "active" class needed to go on the
  • instead of the
  • – user1020853 Apr 05 '14 at 14:36
  • This is one of the best things about Jekyll/static-generation - "ugly" as it may be, it only runs once which doesn't affect the user experience or server load. **Nice solution!** – Paul Ferrett Jun 21 '14 at 01:05