File: navigation.md

package info (click to toggle)
jekyll 3.9.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 4,604 kB
  • sloc: ruby: 15,325; javascript: 1,455; sh: 214; xml: 29; makefile: 7
file content (605 lines) | stat: -rw-r--r-- 19,652 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
---
layout: tutorials
permalink: /tutorials/navigation/
title: Navigation
---

If your Jekyll site has a lot of pages, you might want to create navigation for the pages. Instead of hard-coding navigation links, you can programmatically retrieve a list of pages to build the navigation for your site.

Although there's already information about [interacting with data files]({% link _docs/datafiles.md %}) in other Jekyll docs, this tutorial dives into building more robust navigation for your site.

There are two primary ways of retrieving pages on a Jekyll site:

* **Retrieve pages listed in a YAML data source**. Store the page data in a YAML (or JSON or CSV) file in the `_data` folder, loop through the YAML properties, and insert the values into your theme.
* **Retrieve pages by looping through the page front matter**. Look through the front matter of your pages to identify certain properties, return those pages, and insert the pages' front matter values into your theme.

The examples that follow start with a basic navigation scenario and add more sophisticated elements to demonstrate different ways of returning the pages. In every scenario, you'll see 3 elements:

* YAML
* Liquid
* Result

The YAML file in the `_data` directory is called `samplelist.yml`.

The scenarios are as follows:

* TOC
{:toc}

## Scenario 1: Basic List

You want to return a basic list of pages.

**YAML**

```yaml
docs_list_title: ACME Documentation
docs:

- title: Introduction
  url: introduction.html

- title: Configuration
  url: configuration.html

- title: Deployment
  url: deployment.html
```

**Liquid**

```liquid
{% raw %}<h2>{{ site.data.samplelist.docs_list_title }}</h2>
<ul>
   {% for item in site.data.samplelist.docs %}
      <li><a href="{{ item.url }}" alt="{{ item.title }}">{{ item.title }}</a></li>
   {% endfor %}
</ul>{% endraw %}
```

**Result**
<div class="highlight result">
   <h2>ACME Documentation</h2>
   <ul>
      <li><a href="#" alt="Introduction">Introduction</a></li>
      <li><a href="#" alt="Configuration">Configuration</a></li>
      <li><a href="#" alt="Deployment">Deployment</a></li>
   </ul>
</div>

{: .note .info }
For the results in these fictitious samples, `#` is manually substituted for the actual link value to avoid 404 errors.)

When you use a `for` loop, you choose how you want to refer to the items you're looping through. The variable you choose (in this case, `item`) becomes how you access the properties of each item in the list. Dot notation is used to get a property of the item (for example, `item.url`).

The YAML content has two main types of formats that are relevant here:

* mapping
* list

`docs_list_title: ACME Documentation` is a mapping. You access the value with `site.data.samplelist.docs_list_title`.

`docs:` is a list. The list begins each item with a hyphen. Unlike with mappings, you usually don't access list properties directly as you do with mappings. If you want to access a specific item in the list, you must identify the position in the list you want, following typical array notation. For example, `site.data.samplelist.docs[0]` would access the first item in the list. However, this is rarely done.

With lists, you usually use `for` loops to cycle through the list of items and do something with each item. With navigation menus, you usually insert each list item into `li` tags based on the navigation structure you're using in your HTML theme.

Each hyphen (`-`) indicates another item in the list. This example just has two properties with each list item: `title` and `url`. You can include as many properties as you want for each item. The order of properties at each position in the list doesn't matter.

## Scenario 2: Sorted list

Suppose you wanted to sort the list by the `title`. To do this, convert the reference to the `docs` collection to a variable, and then apply Liquid's `sort` filter to the variable:

**Liquid**

{% raw %}
```liquid
{% assign doclist = site.data.samplelist.docs | sort: 'title'  %}
<ol>
{% for item in doclist %}
    <li><a href="{{ item.url }}" alt="{{ item.title }}">{{ item.title }}</a></li>
{% endfor %}
</ol>
```
{% endraw %}

**Result**

<div class="highlight result">
   <ol>
      <li><a href="#" alt="Configuration">Configuration</a></li>
      <li><a href="#" alt="Deployment">Deployment</a></li>
      <li><a href="#" alt="Introduction">Introduction</a></li>
   </ol>
</div>

The items now appear in alphabetical order. The `sort` property in the Liquid filter applies to the `title`, which is an actual property in the list. If `title` weren't a property, we would need to sort by another property.

See the [Liquid array filter](https://help.shopify.com/themes/liquid/filters/array-filters) for more filter options. Note that you can't simply use this syntax:

{% raw %}
```liquid
{% for item in site.data.samplelist.docs | sort: "title" %}{% endfor %}
```
{% endraw %}

You have to convert `site.data.samplelist.docs` to a variable first using either `assign` or `capture` tags.

## Scenario 3: Two-level navigation list

Suppose you want a more robust list that incorporates multiple sections of heading titles and subitems. To do this, add an additional level to each list item to store this information:

**YAML**

```yaml
toc:
  - title: Group 1
    subfolderitems:
      - page: Thing 1
        url: /thing1.html
      - page: Thing 2
        url: /thing2.html
      - page: Thing 3
        url: /thing3.html
  - title: Group 2
    subfolderitems:
      - page: Piece 1
        url: /piece1.html
      - page: Piece 2
        url: /piece2.html
      - page: Piece 3
        url: /piece3.html
  - title: Group 3
    subfolderitems:
      - page: Widget 1
        url: /widget1.html
      - page: Widget 2
        url: /widget2.html
      - page: Widget 3
        url: /widget3.html
```

**Liquid**

{% raw %}
```liquid
{% for item in site.data.samplelist.toc %}
    <h3>{{ item.title }}</h3>
      <ul>
        {% for entry in item.subfolderitems %}
          <li><a href="{{ entry.url }}">{{ entry.page }}</a></li>
        {% endfor %}
      </ul>
  {% endfor %}
```
{% endraw %}

**Result**
<div class="highlight result">
    <h3>Group 1</h3>
      <ul>
          <li><a href="#">Thing 1</a></li>
          <li><a href="#">Thing 2</a></li>
          <li><a href="#">Thing 3</a></li>
      </ul>

    <h3>Group 2</h3>
      <ul>
          <li><a href="#">Piece 1</a></li>
          <li><a href="#">Piece 2</a></li>
          <li><a href="#">Piece 3</a></li>
      </ul>

    <h3>Group 3</h3>
      <ul>
          <li><a href="#">Widget 1</a></li>
          <li><a href="#">Widget 2</a></li>
          <li><a href="#">Widget 3</a></li>
      </ul>
</div>

In this example, `Group 1` is the first list item. Within that list item, its subpages are included as a property that itself contains a list (`subfolderitems`).

The Liquid code looks through the first level with `for item in site.data.samplelist.toc`, and then looks through the second-level property with `for entry in item.subfolderitems`. Just as `item` is an arbitrary name for the items we're looping through, so is `entry`.

## Scenario 4: Three-level navigation list

Building on the previous section, let's add one more level of depth (`subsubfolderitems`) to the list. The formatting will get more complex here, but the principles are the same.

**YAML**

```yaml
toc2:
  - title: Group 1
    subfolderitems:
      - page: Thing 1
        url: /thing1.html
      - page: Thing 2
        url: /thing2.html
        subsubfolderitems:
          - page: Subthing 1
            url: /subthing1.html
          - page: Subthing 2
            url: /subthing2.html
      - page: Thing 3
        url: /thing3.html
  - title: Group 2
    subfolderitems:
      - page: Piece 1
        url: /piece1.html
      - page: Piece 2
        url: /piece2.html
      - page: Piece 3
        url: /piece3.html
        subsubfolderitems:
          - page: Subpiece 1
            url: /subpiece1.html
          - page: Subpiece2
            url: /subpiece2.html
  - title: Group 3
    subfolderitems:
      - page: Widget 1
        url: /widget1.html
        subsubfolderitems:
          - page: Subwidget 1
            url: /subwidget1.html
          - page: Subwidget 2
            url: /subwidget2.html
      - page: Widget 2
        url: /widget2.html
      - page: Widget 3
        url: /widget3.html
```

**Liquid**

{% raw %}
```liquid
<div>
{% if site.data.samplelist.toc2[0] %}
  {% for item in site.data.samplelist.toc2 %}
    <h3>{{ item.title }}</h3>
      {% if item.subfolderitems[0] %}
        <ul>
          {% for entry in item.subfolderitems %}
              <li><a href="{{ entry.url }}">{{ entry.page }}</a>
                {% if entry.subsubfolderitems[0] %}
                  <ul>
                  {% for subentry in entry.subsubfolderitems %}
                      <li><a href="{{ subentry.url }}">{{ subentry.page }}</a></li>
                  {% endfor %}
                  </ul>
                {% endif %}
              </li>
          {% endfor %}
        </ul>
      {% endif %}
    {% endfor %}
{% endif %}
</div>
```
{% endraw %}

**Result**

<div class="highlight result">
   <div>
      <h3>Group 1</h3>
      <ul>
         <li><a href="#">Thing 1</a></li>
         <li><a href="#">Thing 2</a></li>
         <ul>
            <li><a href="#">Subthing 1</a></li>
            <li><a href="#">Subthing 2</a></li>
         </ul>
         <li><a href="#">Thing 3</a></li>
      </ul>
      <h3>Group 2</h3>
      <ul>
         <li><a href="#">Piece 1</a></li>
         <li><a href="#">Piece 2</a></li>
         <li><a href="#">Piece 3</a></li>
         <ul>
            <li><a href="#">Subpiece 1</a></li>
            <li><a href="#">Subpiece2</a></li>
         </ul>
      </ul>
      <h3>Group 3</h3>
      <ul>
         <li><a href="#">Widget 1</a></li>
         <ul>
            <li><a href="#">Subwidget 1</a></li>
            <li><a href="#">Subwidget 2</a></li>
         </ul>
         <li><a href="#">Widget 2</a></li>
         <li><a href="#">Widget 3</a></li>
      </ul>
   </div>
</div>

In this example, `if site.data.samplelist.toc2[0]` is used to ensure that the YAML level actually contains items. If there isn't anything at the `[0]` position, we can skip looking in this level.

<div class="note">
  <h5>ProTip: Line up <code>for</code> loops and <code>if</code> statements</h5>
  <p>To keep the code clear, line up the beginning and ending Liquid tags, such as the <code>for</code> loops and <code>if</code> statements. This way you know when the open tags have been closed. If the code will appear in a Markdown page, keep the opening and closing HTML tags flush against the left edge so that the Markdown filter won't treat the content as a code sample. If necessary, you can wrap the entire code sample in a <code>div</code> tag to ensure the code has HTML tags that bookend the code.</p>
</div>

## Scenario 5: Using a page variable to select the YAML list

Suppose your sidebar will differ based on various documentation sets. You might have 3 different products on your site, and so you want 3 different sidebars &mdash; each unique for that product.

You can store the name of the sidebar list in your page front matter and then pass that value into the list dynamically.

**Page front matter**

```yaml
---
title: My page
sidebar: toc
---
```

**Liquid**

{% raw %}
```liquid
<ul>
    {% for item in site.data.samplelist[page.sidebar] %}
      <li><a href="{{ item.url }}">{{ item.title }}</a></li>
    {% endfor %}
</ul>
```
{% endraw %}

**Result**

<div class="highlight result">
   <ul>
      <li><a href="#">Introduction</a></li>
      <li><a href="#">Configuration</a></li>
      <li><a href="#">Deployment</a></li>
   </ul>
</div>

In this scenario, we want to pass values from the page's front matter into a `for` loop that contains a variable. When the assigned variable isn't a string but rather a data reference, you must use brackets (instead of curly braces) to refer to the front matter's value.

For more information, see [Expressions and Variables](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers#expressions-and-variables) in Liquid's documentation. Brackets are used in places where dot notation can't be used. You can also read more details in this [Stack Overflow answer](http://stackoverflow.com/questions/4968406/javascript-property-access-dot-notation-vs-brackets/4968448#4968448).

## Scenario 6: Applying the active class for the current page

In addition to inserting items from the YAML data file into your list, you also usually want to highlight the current link if the user is viewing that page. You do this by inserting an `active` class for items that match the current page URL.

**CSS**
```css
.result li.active a {
    color: lightgray;
    cursor: default;
  }
```
**Liquid**

{% raw %}
```liquid
{% for item in site.data.samplelist.docs %}
    <li class="{% if item.url == page.url %}active{% endif %}">
      <a href="{{ item.url }}">{{ item.title }}</a>
    </li>
{% endfor %}
```
{% endraw %}

**Result**

<style>
.result li.active a {
    color: lightgray;
    cursor: default;
  }
</style>

<div class="highlight result">
   <ul>
      <li class=""><a href="#">Introduction</a></li>
      <li class=""><a href="#">Configuration</a></li>
      <li class="active"><a href="#">Deployment</a></li>
   </ul>
</div>

In this case, assume `Deployment` is the current page.

To make sure the `item.url` (stored in the YAML file) matches the `page.url`, it can be helpful to print the `{% raw %}{{ page.url }}{% endraw %}` to the page.

## Scenario 7: Including items conditionally

You might want to include items conditionally in your list. For example, maybe you have multiple site outputs and only want to include the sidebar item for certain outputs. You can add properties in each list item and then use those properties to conditionally include the content.

**YAML**
```yaml
docs2_list_title: ACME Documentation
docs2:

- title: Introduction
  url: introduction.html
  version: 1

- title: Configuration
  url: configuration.html
  version: 1

- title: Deployment
  url: deployment.html
  version: 2
```

**Liquid**

{% raw %}
```liquid
  <ul>
    {% for item in site.data.samplelist.docs2 %}
      {% if item.version == 1 %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
      {% endif %}
    {% endfor %}
</ul>
```
{% endraw %}

**Result**

<div class="highlight result">
   <ul>
      <li><a href="#">Introduction</a></li>
      <li><a href="#">Configuration</a></li>
   </ul>
</div>

The `Deployment` page is excluded because its `version` is `2`.

## Scenario 8: Retrieving items based on front matter properties

If you don't want to store your navigation items in a YAML file in your `_data` folder, you can use `for` loops to look through the YAML front matter of each page or collection and get the content based on properties in the front matter.

In this scenario, suppose we have a collection called `_docs`. Collections are often better than pages because they allow you to narrow the list of what you're looping through. (Try to avoid scenarios where you loop through large numbers of items, since it will increase your build time. [Collections]({% link _docs/collections.md %}) help you narrow the scope.)

In our scenario, there are 6 docs in the `docs` collection: Sample 1, Sample 2, Topic 1, Topic 2, Widget 1, and Widget 2.

Each doc in the collection contains at least 3 properties in the front matter:

* `title`
* `category`
* `order`

The front matter for each page is as follows (consolidated here for brevity):

```yaml
---
Title: Sample 1
category: getting-started
order: 1
---

---
Title: Sample 2
category: getting-started
order: 2
---

---
Title: Topic 1
category: configuration
order: 1
---

---
Title: Topic 2
category: configuration
order: 2
---

---
Title: Widget 1
category: deployment
order: 1
---

---
Title: Widget 2
category: deployment
order: 2
---
```

Note that even though `category` is used in the doc front matter, `category` is not a built-in variable like it is with posts. In other words, you cannot look directly inside `category` with `site.docs.category`.

If you wanted to simply get all docs in the collection for a specific category, you could use a `for` loop with an `if` condition to check for a specific category:

{% raw %}
```liquid
<h3>Getting Started</h3>
<ul>
    {% for doc in site.docs %}
      {% if doc.category == "getting-started" %}
        <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
      {% endif %}
    {% endfor %}
</ul>
```
{% endraw %}

The result would be as follows:

<div class="highlight result">
   <h3>Getting Started</h3>
   <ul>
      <li><a href="#">Sample1</a></li>
      <li><a href="#">Sample2</a></li>
   </ul>
</div>

This might be useful if you're setting up a knowledge base and have dozens of topics in each category, with each category displaying on its own page.

But let's say you want to sort the items by category and group them under the category name, without hard-coding the category names. To achieve this, you could use two filters:

* `group_by`
* `sort`

Here's the code for getting lists of pages grouped under their corresponding category headers:

**Liquid**

{% raw %}
```liquid
{% assign mydocs = site.docs | group_by: 'category' %}
{% for cat in mydocs %}
<h2>{{ cat.name | capitalize }}</h2>
    <ul>
      {% assign items = cat.items | sort: 'order' %}
      {% for item in items %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
      {% endfor %}
    </ul>
{% endfor %}
```
{% endraw %}

**Result**

<div class="highlight result">
   <h2>Getting-started</h2>
   <ul>
      <li><a href="#">Sample2</a></li>
      <li><a href="#">Sample1</a></li>
   </ul>
   <h2>Configuration</h2>
   <ul>
      <li><a href="#">Topic2</a></li>
      <li><a href="#">Topic1</a></li>
   </ul>
   <h2>Deployment</h2>
   <ul>
      <li><a href="#">Widget2</a></li>
      <li><a href="#">Widget1</a></li>
   </ul>
</div>

Let's walk through the code. First, we assign a variable (`mydocs`) to the collection content (`site.docs`).

The `group_by` filter groups the collection content by `category`. More specifically, the `group_by` filter converts `mydocs` into an array with `name`, `items`, and `size` properties, somewhat like this:

```yaml
[
  {"name": "getting-started", "items": [Sample 1, Sample 2],"size": 2},
  {"name": "configuration", "items": [Topic 1, Topic 2],  "size": 2},
  {"name": "deployment", "items": [Widget 1, Widget 2, "size": 2}
]
```

Using `for cat in mydocs`, we look through each item in the `mydocs` array and print the category `name`.

After getting the category name, we assign the variable `items` for the docs and use the `sort` filter to arrange the docs by their `order` property. The dot notation `cat.items` is used because we're accessing the content in the `items` array. The `sort` filter orders the items by their numbers in ascending order.

The `for item in items` loop looks through each `item` and gets the `title` and `url` to form the list item link.

For more details on the `group_by` filter, see [Jekyll's Templates documentation](https://jekyllrb.com/docs/templates/) as well as [this Siteleaf tutorial](https://www.siteleaf.com/blog/advanced-liquid-group-by/). For more details on the `sort` filter, see [sort](https://shopify.github.io/liquid/filters/sort/) in Liquid's documentation.

Whether you use properties in your doc's front matter to retrieve your pages or a YAML data file, in both cases you can programmatically build a more robust navigation for your site.