In Jekyll you can use liquid template and I am trying to write a nav that includes all links in the website.
sitemap:
home: "/"
demo:
right: "/right"
left: "/left"
What I am trying to achieve is to create a sidebar that icludes all those links. But certains links are under a section. The output should be the following
<nav>
<ul>
<li>
<a href="/">home</a>
</li>
</ul>
<ul>
<li>demo</li>
<li>
<a href="/right">right</a>
</li>
<li>
<a href="/left">left</a>
</li
</ul>
</nav>
Not all sections must have a title. The home link is a standalone link.
The demo links are all in the demo section.
In liquid I can loop through the sitemap in this way:
{% for nav in site.sitemap %}
<ul>
<li>{{ nav[0] }}</li>
</ul>
{% endfor %}
In this way, liquid will print home
and demo
.
What I need is to check if the value is a string or an array in order to print the array or a single link!
Is there a way to check if the liquid variable is a string or an array?
I can't find it in the documentation i linked before!
Is there a way to check if the liquid variable is a string or an array?
Short answer: Yes
To check if a liquid variable is a string or a list of elements (array or hash), check if it has a first descendant, using the array filter first: {% var.first %}
as follow :
{% if var.first %}
// var is not a string
{% else %}
// var is a string
{% endif %}
Explanation:
the filter first
return the first element of an array or a hash if it has one, if the variable is empty or not a list of element it return nothing, and since nil
is a falsy value
in liquid, it works the same as false
in the if condition
above.
e.g :
# var1 = "a" // string
{% var1.first %} // return: // nil -> falsy
# var2 = [a,b,c] // array
{% var2.first %} // return: a
# var3 = {k1: a, k2: b, k3: c} // hash
{% var1.first %} // return: k1a
# var4 = {k1, k2, k3: c} // hash, first element is a key without associated value
{% var1.first %} // return: k1
Solution to the OP issue: loop through the sitemap
Now that we can determine if an element is a string or not, we can do that:
{% for nav in site.sitemap %}
<ul>
<li>
{{ nav[0] }} :
{% if nav[1].first %}
// loop through: nav[1]
{% else %}
{{ nav[1] }}
{% endif %}
</li>
</ul>
{% endfor %}
but I found that's more convenient* to work the sitemap
as list of elements (a), instead a single big hash (as it is actually) (b) (cf. YAML syntax)
1. Edit the sitemap data structure
so I modified it's structure, by adding a "- "
(a dash and a space) before each element:
- home: "/"
- demo:
- right: "/right"
- left: "/left"
which give us a:
{"home"=>"/"}{"demo"=>[{"right"=>"/right"}, {"left"=>"/left"}]}
instead of b:
{"home"=>"/", "demo"=>{"right"=>"/right", "left"=>"/left"}}
Side Note: you can put the sitemap in it's own file in: _data/sitmap.yml
and access it by site.data.sitemap
, in lieu of defined it as an attribute in the _config.yml
, to keep it cleaner, thereby it'll look exactly as the above.
2. Create a sitemap template
since we'll going to include the sitemap
in itself recursively (for each node with children ) we'll put this template in _includes
folder and not _layouts
Notice also that Jekyll allows passing parameters to includes. But there is a catch, the param
should be a string
not an array, or hash. So, we have to make a string out of the links array.
here we go:
<ul>
<!-- links is a param -->
{%for link in include.links %}
<li>
{% for part in link %}
{{part[0]}} : <!-- part[0] : link name -->
{% if part[1].first %} <!-- the element has children -->
<!-- concatenate jekyll array into a string -->
{% assign _links = "" | split: "" %}
{% for _link in part[1] %}
{% assign _links = _links | push: _link %}
{% endfor %}
<!-- pass the string as a param to sitemap, then do the recursive dance -->
{% include sitemap.html links = _links %}
{% else %} <!-- no children -->
{{part[1]}} <!-- part[1] : link url -->
{% endif %}
{% endfor%}
</li>
{%endfor%}
</ul>
3. Use the template :)
<!-- include and init the param with this ↓, or `site.sitemap` if it's defined in `_config.yml`, or ... -->
{% include sitemap.html links= site.data.sitemap %}
4. Output
for: /_data/sitemap.yml
:
- home : "/"
- about: "/about"
- archive:
- left : "/left"
- right: "/right"
- other:
- up: '/other/up'
- down: '/other/down'
the template produce:
- home : /
- about : /about
- archive :
- left : /left
- right : /right
- other :
- up : /other/up
- down : /other/down
so you have to put part[0]
and part[1]
of each link in an <a> tag
like:
<a href="{{part[1]}}"> {{part[0]}} </a>
I'm using Jekyll 2.8.5 and
var[0]
is falsy for string values, but truthy for an array (whose first element is truthy).
(On the other hand var.first
, as recommended by the previously accepted answer, now returns the first character of the string, and so it doesn't seem to be a good way to determine if a value is an array anymore.)
You can modify your structure as follows:
sitemap:
home:
link: "/"
demo:
children:
right:
link: "/right"
left:
link: "/left"
Now all your objects follow the same pattern: instead of testing types, you can just test if an object exists. You can also use recursion by inclusion to parse the sitemap:
{% for nav in site.sitemap %}
<ul>
{% include 'print-li' %}
</ul>
{% endfor %}
With 'print-li':
<li>
{% if nav.link %}
<a href="{{ nav.link }}">{{ nav[0] }}</a>
{% else %}
{{ nav[0] }}
{% endif %}
{% if nav.children %}
{% for nav in nav.children %}
{% include 'print-li' %}
{% endfor %}
{% endif %}
</li>