I'm finishing up my site redesign and just need to complete my portfolio page. Rather than using posts for portfolio entries, I want to use subdirectories/child pages:
...
work
project
index.html
project-2
index.html
index.html
...
I want to loop through those subpages in a list to show on work/index.html.
Something similar to:
<ul>
{% for page in site.work.pages %}
<li>
<figure>
<img src="/img/foo.jpg" alt="foo">
</figure>
</li>
{% endfor %}
</ul>
How can this be done?
Jekyll does not support this as simply as in your example yet, it is coming in 2.0 however.
You could add a key/value pair to the YAML header of the child pages to signify that it should appear on the main index page. I have a similar setup that I use to define what pages should appear in the main navigation for the site.
project/index.html etc
---
group: work
---
work/index.html
<ul>
{% for node in site.pages %}
{% if 'work' == node.group %}
<li>{{node.title}}</li>
{% endif %}
{% endfor %}
</ul>
You may be able to avoid requiring the group attribute if you changed the if condition to do substring matching of the URL, but this solution is easier to understand.
If you do your own page build with jekyll build you can simply create a file called _plugins/page_filters.rb with the following contents:
module Jekyll
module PageFilters
def children_of(all_pages, parent)
all_pages.select { |p| child_of?(p, parent) }
end
private
def child_of?(child, parent)
parent_path = parent["path"]
child_path = child.path
# Exclude 'index.md' from becoming a child of itself
return false if parent_path == child_path
# Remove 'index.md' from the parent path
parent_path = parent_path.split("index.md", 2).first
child_path.start_with? parent_path
end
end
end
Liquid::Template.register_filter(Jekyll::PageFilters)
And then invoke the children_of filter as such:
{% assign children = site.pages | children_of: page %}
<ul>
{% for child in children %}
<li>{{ child.title }}</li>
{% endfor %}
</ul>
Related
I'm trying to write a static site with Jekyll that has a few layers to it. What's the best way to generate links to all subpages within a section?
For example, if I have a site structure like this:
landing
- Topic A
- Content 1
- Content 2
- Content 3
- Topic B
- Content 1
- Content 2
- Content 3
What would be the best way to create links to each of the Content pages from its Topic page? And, is there a simple way to link to all the Topic pages from the landing?
These are not posts, just static pages. It would be really great if I could just do {% for topic.each %} ...etc. and print the links out.
I would not use posts for this purpose (as yaitloutou suggests). I would read the hierarchy from the directory structure (solution 1) or create two seperate collections (solution 2). You can let the collections from solution 2 share the same layout if you want that.
1. Using pages
Create a directory structure with index.md pages and loop over the Jekyll veriable called 'site.pages' to create the menu.
index.md
topic-a/index.md
content-1/index.md
content-2/index.md
content-3/index.md
topic-b/index.md
content-1/index.md
content-2/index.md
content-3/index.md
And loop over all pages like this:
<ul>
{% assign sitepages = site.pages | sort: 'order' %}
{% for sitepage in sitepages %}
<li {% if page.url == sitepage.url %} class="active"{% endif %}>
{{ sitepage.title }}
</li>
{% endfor %}
</ul>
If you want the nested structure, you can do something like this. Or if you want only the results for Topic A, you can do this:
<ul>
{% assign sitepages = site.pages | sort: 'order' %}
{% for sitepage in sitepages %}
{% if sitepage.url contains 'topic-a' %}
<li {% if page.url == sitepage.url %} class="active"{% endif %}>
{{ sitepage.title }}
</li>
{% endif %}
{% endfor %}
</ul>
2. Using collections (simplest solution and quickest build)
Create a collection Topic A and create another collection Topic B. Your config file should look like this:
collections:
topic-a:
output: true
permalink: /topic-a/:path/
topic-b:
output: true
permalink: /topic-b/:path/
Outputting the items of one topic goes like this:
{% assign atopics = site.topic-a | sort: 'order' %}
{% for atopic in atopics %}
<li {% if page.url == atopic.url %} class="active"{% endif %}>
{{ atopic.title }}
</li>
{% endfor %}
</ul>
You should create a _topic-a and a _topic-b directory with your content-1.md, content-2.md, etc. files.
Note that both solutions have YML variables called 'order', to determine the order of appearance of the items/pages. This looks like this:
---
title: mytitle
layout: mylayout
order: 50
---
mycontent
I'll propose here 2 ways, you can determine the "best" according to your specific needs/situation, and which one sound more adapted to them.
first of all, "posts" and "pages" are basically just collections of md/html files. with some variables associated to each one.
to generate files with this structure, you can:
1. Using _posts and page.categories
put all the sub-files in _posts (the 2017-01-01- is just a place holder)
_posts/
- 2017-01-01-content-a-1.md
- 2017-01-01-content-a-2.md
- 2017-01-01-content-a-3.md
- 2017-01-01-content-b-1.md
- 2017-01-01-content-b-2.md
- 2017-01-01-content-b-3.md
add appropriate categories to each file:
2.1. for posts caontent-a-* add category: topic-a (in this order) by adding this line in the yaml front matter at top of each of them:
---
layout: page # or any appropriate layout
category: topic-a
---
2.2. for posts caontent-b-* add category: topic-b
set a premalink to ignore the date, and create the desired structure, by adding the following line to _config.yml:
defaults:
-
scope:
path: "_posts" # to all the file in posts
values:
permalink: /landing/:categories/:title.html # set this as default permalink value
you still can specify a permalinks per post in its front matter, or just add the permalink line to each md folder front matter.
the above will generate the desired structure.
loop through all the
{% for entry in site.posts %}
{% if entry.category == type-a %}
<!-- do A stuff -->
{% elsif entry.category == type-b %}
<!-- do B stuff -->
{% endif %}
{% endfor %}
2. Using collections:
it's similar to the above, but instead of using the already existent _postscollection you'll start by creating a new collection (one advantage is that you'll not need to add a date )
any of the approaches above will generate this structure inside _site
landing/
type-a/
content-a-1/
index.html
content-a-2/
index.html
...
type-b/
...
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>
home
</li>
</ul>
<ul>
<li>demo</li>
<li>
right
</li>
<li>
left
</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:
{{part[0]}}
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 %}
{{ nav[0] }}
{% else %}
{{ nav[0] }}
{% endif %}
{% if nav.children %}
{% for nav in nav.children %}
{% include 'print-li' %}
{% endfor %}
{% endif %}
</li>
I'm building a static website on gh-pages using jekyll liquid. I'm properly generating a simple, two-level navigation from a data file. My problem is that I am stuck trying to do two things:
Apply a "selected" class to the current page link item.
Apply an "open" class to the parent list item when a sublink menu is present.
Here's the format I'm using in my _data/nav.yml file
- title: Top Level Nav Item
url: level-1/
sublinks:
- title: Child Nav Item 1
url: child-1/
- title: Child Nav Item 2
url: child-2/
Here's how I'm building my navigation:
{% assign current_page = page.url | remove: 'index.html' %}
<ul class="-nav">
{% for nav in include.nav %}
{% assign current = null %}
{% if nav.url == current_page %}
{% assign current = ' _selected' %}
{% endif %}
{% if nav.url contains current_page %}
{% assign open = ' _open' %}
{% endif %}
<li class="-item{{ current }}{{ open }}">
{{ nav.title }}
{% if nav.sublinks and current_page contains nav.url %}
{% include navigation.html nav=nav.sublinks%}
{% endif %}
</li>
{% endfor %}
</ul>
Again this builds my navigation correctly, but it doesn't apply either the selected or open class.
Here's what I'm would like it to look like in the end:
What am I doing wrong?
Finally got this working. I solved it by adding an item to the front matter of my page called permalink where I specified the desired page permalink.
---
layout: default
title: Example
permalink: url/example/
---
I use this permalink to check against the current page.url to add the desired _selected class on the <li> element.
I made one modification to my example data file, adding in the parent url_part into the child's url. I'm not sure why, but I had trouble printing the entire URL correctly otherwise.
- title: Top Level Nav Item
url: level-1/
sublinks:
- title: Child Nav Item 1
url: level-1/child-1/
- title: Child Nav Item 2
url: level-1/child-2/
Lastly for my navigation.html include, here's how I'm creating my main menu, rendering sub-menus if they exist and should be shown, and properly selecting the active link:
{% assign current_page = page.url | remove: 'index.html' %}
<ul class="-nav">
{% for nav in include.nav %}
{% assign current = null %}
{% if nav.url == page.permalink %}
{% assign current = ' _selected' %}
{% endif %}
<li class="-item{{ current }}">
{{ nav.title }}
{% if nav.sublinks and current_page contains nav.url %}
{% include navigation.html nav=nav.sublinks%}
{% endif %}
</li>
{% endfor %}
</ul>
The big difference between this and the snippet I originally posted is I dropped the {{ open }} stuff for now. One problem at a time. The other thing is that I'm checking to see if nav.url equals page.permalink. Before I was checking against page.url and this always failed for me.
It's probably not the prettiest, but I finally got a jekyll liquid menu to generate (semi-)dynamically and properly select the active link.
I want to show a single content entry (from one content type) on a page and I want to access this page directly with a link.
So far I've created the content type "posts" (with wagon generate ...). It contains the fields "title","date" and "body". On the page "posts", all title's of the posts are listed and when you click on one of the title, you should be redirected to the subpage "post" which contains the rest of the content (depending on which post you selected).
posts.liquid:
{% extends parent %}
{% block main %}
{% for post in contents.posts%}
<li>{{ post.date }} - {{ post.titel }} </li>
{% endfor %}
{% endblock %}
This lists all posts.
post.liquid:
{% extends parent %}
{% block main %}
<h2>{{post.title}}</h2>
<h3>{{post.date}}</h3>
<p>{{post.body}}</p>
{% endblock %}
And this shoul be the template for the rest of the content on a single page.
How can I link the list elemnts to the correct post?
I'm using wagon to develop the site local.
I found a solution to my problem.
To access only one specific entry in the content type posts, you need to create a template which holds the content with the correct layout.
This means you need a file named "content-type-template.liquid" and this file has to be placed in a folder (in my case named "post") to define the parent:
/posts.liquid # Holds a list of all Posts
/post/content-type-template.liquid # Holds the layout for only one post
Also in the top of content-type-template.liquid you need to define the content-type and the slug:
---
title: Post Template
content_type: post
slug: content_type_template
---
The fields of the content type are now reachable with the following syntax:
{% extends parent %}
{% block main %}
<h2>{{post.title}}</h2>
<h3>{{post.date}}</h3>
<p>{{post.body}}</p>
{% endblock %}
If the content type is for example named "product", you need to rename everything above called "post" with "product".
Finally you can reach a single entry with it's slug.
{% extends parent %}
{% block main %}
{% for post in contents.posts%}
<li>{{ post.date }} - {{ post.titel }} </li>
{% endfor %}
{% endblock %}
Here are some links that helped me:
http://www.tommyblue.it/2011/02/28/how-to-build-a-website-with-locomotive-cms-from-scratch/
http://doc.locomotivecms.com/references/api/objects/content-entry
My blog is built with Jekyll on Github. In the navigation bar, the default order is Pages, Messages, About, Archives. I want to change the list to Pages, Archives, About, Messages. What should I do?
I think it is related to the code below
{% assign pages_list = site.pages %}
I think site.pages is what I should change, but I don't know how.
You can create custom order of your menu items like this:
In your pages front matter add the order field (you can name it as you prefer)
---
layout: default
published: true
title: Page title
order: 1
---
When getting pages, apply the 'sort' filter
{% assign sorted_pages = site.pages | sort:"order" %}
{% for node in sorted_pages %}
<li>{{node.title}}</li>
{% endfor %}
You'll end up with an ordered (ASC) list of pages, based on the 'order' field value you add to each page.
Update: Some ordering functionality seems to have been added to Jekyll: https://github.com/plusjade/jekyll-bootstrap/commit/4eebb4462c24de612612d6f4794b1aaaa08dfad4
Update: check out comment by Andy Jackson below – "name" might need to be changed to "path".
This seems to work for me:
{% assign sorted_pages = site.pages | sort:"name" %}
{% for node in sorted_pages %}
<li>{{node.title}}</li>
{% endfor %}
name is file name. I renamed pages to 00-index.md, 01-about.md etc. Sorting worked, but pages were generated with those prefixes, which looked ugly especially for 00-index.html.
To fix that I override permalinks:
---
layout: default
title: News
permalink: "index.html"
---
Sadly, this won't work with custom attributes, because they are not accessible as methods on Page class:
{% assign sorted_pages = site.pages | sort:"weight" %} #bummer
The order of your navbar menu is determined by the HTML template in _layout (which may be pulling in HTML fragments from _includes.
It sounds like your navbar is being programatically generated from the list of pages provided in site.pages using the liquid code
{% assign pages_list = site.pages %}
If you have only a small number of pages, you may prefer to just write the list out manually. site.pages is Jekyll's alphabetical list of all pages. Nothing stops you from just hardcoding this instead:
<div class="navbar" id="page-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="/">EverCoding.net</a>
<ul class="nav">
<li>Pages</li>
<li>Archive</li>
<li>About</li>
<li>Messages</li>
Whereas I'm guessing at the moment you have that list generated programmatically, perhaps by following the way Jekyll-bootstrap does with liquid code:
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="{{ HOME_PATH }}">{{ site.title }}</a>
<ul class="nav">
{% assign pages_list = site.pages %}
{% assign group = 'navigation' %}
{% include JB/pages_list %}
</ul>
</div>
</div>
</div>
The liquid code in this second example is handy if you really want to determine the menu each time, but if you have a static menu in a static order you are probably best coding it by hand as in my first example, rather than modifying the liquid code to sort.
If you could link to the Jekyll source, rather than the published blog, we could be more specific.
I'm using Jekyll v2.5.3 and you can also number your actual markdown files (order them that way) and since you're using the Front Matter block you can still keep the titles and permalinks as you want them.
The parser will order your page links that way.
I.e.:
01_about.md
02_photos.md
03_projects.md
99_contact.md
I made pages.yml file in the _data directory it is look like similar:
- url: pages/test.html
title: Pages
group: navigation
- url: pages/front.html
title: Front
group: navigation
And I changed the default.html (from site.pages to site.data.pages):
<ul class="nav">
{% assign pages_list = site.data.pages %}
{% assign group = 'navigation' %}
{% include JB/pages_list %}
</ul>
And now I can use this yml file for the menu.
You could see the documentation: http://jekyll.tips/jekyll-casts/navigation/
There are good examples and explanations with navigation_weight.
---
layout: page
title: About
permalink: /about/
navigation_weight: 10
---
For minima:
<div>
{% assign navigation_pages = site.pages | sort: 'navigation_weight' %}
{% for p in navigation_pages %}
{% if p.navigation_weight %}
{% if p.title %}
<a class="page-link" href="{{ p.url | relative_url }}">{{ p.title | escape }}</a>
{% endif %}
{% endif %}
{% endfor %}
</div>
For minima theme
Put:
header_pages:
- pages.md
- archive.md
- about.md
- messages.md
in _config.yml to override default order. That's all.
Minima README:
Customize navigation links
This allows you to set which pages you want to appear in the
navigation area and configure order of the links.
For instance, to only link to the about and the portfolio page,
add the following to you _config.yml:
- about.md
- portfolio.md
You can see how it works in header.html file from minima _includes.
You were on the right path. You could sort by a custom variable named, say, 'order'.
In header.html insert and extra row:
{% assign pages_list = (site.pages | sort: 'order') %}
Then replace site.pages with pages_list in the for statement:
{% for my_page in pages_list %}
{% if my_page.title %}
<a class="page-link" href="{{ my_page.url | relative_url }}">{{ my_page.title | escape }}</a>
{% endif %}
{% endfor %}
Then add 'order' into the YAML front matter for each page, and set it a suitable value:
---
layout: page
title: About
permalink: /about/
order: 0
---
The Jekyll Bootstrap 3 template requires that you include group navigation in the Jekyll header. Building on #Wojtek's answer, you can modify JB3's pages_list to use this group field to both filter, and sort.
Before calling pages_list, sort by group:
{% assign sorted_pages = site.pages | sort:"group" %}
Then, simply change one line in pages_list:
{% if group == null or group == node.group %} -> {% if group == null or node.group contains group %}
Now you can specify the group to be navigation-00, navigation-01, without having to rename your files or set up any permalinks, and you get sorting for free.
I made a simple plugin some time ago to sort pages according to a page_order array you can define your _config.yml:
pages_order: ['index', 'summary', 'overview', 'part1', 'part2', 'conclusion', 'notes']
It exposes page.prev and page.next in templates to allow navigation:
{% if page.prev %}
<a id="previous-page" href="{{page.prev}}.html">Previous</a>
{% endif %}
{% if page.next %}
<a id="next-page" href="{{page.next}}.html">Next</a>
{% endif %}
Note: Does not work on Github Pages.