ナビゲーション

2017年1月24日 tomjoht tomjoht

Jekyllサイトに多くのページがある場合、ページのナビゲーションを作成することを検討する必要があります。ナビゲーションリンクをハードコーディングする代わりに、プログラムでページのリストを取得して、サイトのナビゲーションを構築できます。

他のJekyllドキュメントにはデータファイルとの連携に関する情報が既にありますが、このチュートリアルでは、サイトのより堅牢なナビゲーションの構築について詳しく説明します。

Jekyllサイトでページを取得する主な方法は2つあります。

  • YAMLデータソースにリストされているページを取得する。ページデータを`_data`フォルダ内のYAML(またはJSON、CSV)ファイルに保存し、YAMLプロパティをループ処理して、その値をテーマに挿入します。
  • ページのフロントマターをループ処理してページを取得する。ページのフロントマターを調べて特定のプロパティを特定し、それらのページを返し、ページのフロントマターの値をテーマに挿入します。

以降の例では、基本的なナビゲーションシナリオから開始し、ページを返すさまざまな方法を示すために、より高度な要素を追加します。どのシナリオでも、3つの要素が表示されます。

  • YAML
  • Liquid
  • 結果

`_data`ディレクトリのYAMLファイルは`samplelist.yml`と呼ばれています。

シナリオは以下のとおりです。

シナリオ1:基本リスト

基本的なページリストを返す必要があります。

YAML

docs_list_title: ACME Documentation
docs:

- title: Introduction
  url: introduction.html

- title: Configuration
  url: configuration.html

- title: Deployment
  url: deployment.html

Liquid

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

結果

ACMEドキュメント

これらの架空のサンプルの結果では、`#`は実際のリンク値の代わりに手動で置換されています(404エラーを回避するため)。

`for`ループを使用する場合、ループ処理する項目の参照方法を選択します。選択した変数(この場合は`item`)は、リスト内の各項目のプロパティにアクセスする方法になります。ドット表記を使用して、項目のプロパティを取得します(例:`item.url`)。

YAMLコンテンツには、ここで関連する2つの主要な形式の種類があります。

  • マッピング
  • リスト

`docs_list_title: ACME Documentation`はマッピングです。`site.data.samplelist.docs_list_title`を使用して値にアクセスします。

`docs:`はリストです。リストは各項目をハイフンで開始します。マッピングとは異なり、通常はマッピングのようにリストのプロパティに直接アクセスしません。リスト内の特定の項目にアクセスする場合は、通常の配列表記に従って、必要なリスト内の位置を特定する必要があります。たとえば、`site.data.samplelist.docs[0]`はリストの最初の項目にアクセスします。ただし、これはめったに行われません。

リストでは、通常`for`ループを使用してリスト内の項目を循環処理し、各項目に対して何かを行います。ナビゲーションメニューでは、通常、使用しているHTMLテーマのナビゲーション構造に基づいて、各リスト項目を`li`タグに挿入します。

各ハイフン(`-`)は、リスト内の別の項目を示します。この例では、各リスト項目に`title`と`url`の2つのプロパティしかありません。項目ごとに必要な数のプロパティを含めることができます。リスト内の各位置のプロパティの順序は関係ありません。

シナリオ2:ソートされたリスト

`title`でリストをソートしたいとします。これを行うには、`docs`コレクションへの参照を変換し、Liquidの`sort`フィルターをその変数に適用します。

Liquid

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

結果

項目はアルファベット順に表示されるようになりました。Liquidフィルターの`sort`プロパティは、リスト内の実際のプロパティである`title`に適用されます。`title`がプロパティでない場合は、別のプロパティでソートする必要があります。

詳細なフィルターオプションについては、Liquid配列フィルターを参照してください。この構文を単純に使用することはできません。

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

`site.data.samplelist.docs`を`assign`タグまたは`capture`タグを使用して最初に変数に変換する必要があります。

シナリオ3:2レベルのナビゲーションリスト

見出しのタイトルとサブ項目の複数のセクションを含む、より堅牢なリストが必要なとします。これを行うには、この情報を格納するために、各リスト項目にさらにレベルを追加します。

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

{% 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 %}

結果

この例では、`グループ1`は最初のリスト項目です。そのリスト項目内では、そのサブページはそれ自体がリスト(`subfolderitems`)を含むプロパティとして含まれています。

Liquidコードは`site.data.samplelist.toc`の`item`で最初のレベルを調べ、次に`item.subfolderitems`の`entry`で2番目のレベルのプロパティを調べます。`item`がループ処理する項目の任意の名前であるのと同じように、`entry`も任意の名前です。

シナリオ4:3レベルのナビゲーションリスト

前のセクションに基づいて、リストにさらに1レベルの深さ(`subsubfolderitems`)を追加してみましょう。ここではフォーマットが複雑になりますが、原則は同じです。

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

<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>

結果

この例では、`if site.data.samplelist.toc2[0]`を使用して、YAMLレベルに実際に項目が含まれていることを確認しています。`[0]`の位置に何もない場合は、このレベルを調べることをスキップできます。

プロヒント:`for`ループと`if`ステートメントを揃える

コードを明確にするために、`for`ループや`if`ステートメントなど、Liquidタグの先頭と末尾を揃えます。これにより、開始タグがいつ閉じられたかがわかります。コードがMarkdownページに表示される場合は、Markdownフィルターがコンテンツをコードサンプルとして扱わないように、開始HTMLタグと終了HTMLタグを左端に揃えます。必要に応じて、コード全体を`div`タグで囲んで、コードにコードを囲むHTMLタグがあるようにすることができます。

シナリオ5:ページ変数を使用してYAMLリストを選択する

サイドバーがさまざまなドキュメントセットによって異なる場合があるとします。サイトに3つの異なる製品があり、それぞれに固有の3つの異なるサイドバーが必要な場合があります。

ページのフロントマターにサイドバーリストの名前を保存し、その値を動的にリストに渡すことができます。

ページフロントマター

---
title: My page
sidebar: toc
---

Liquid

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

結果

このシナリオでは、ページのフロントマターの値を変数を含む`for`ループに渡す必要があります。割り当てられた変数が文字列ではなくデータ参照である場合、フロントマターの値を参照するには、(波括弧ではなく)角括弧を使用する必要があります。

詳細については、Liquidのドキュメントの式と変数を参照してください。角括弧は、ドット表記を使用できない場合に使用されます。このStack Overflowの回答でも詳細を読むことができます。

シナリオ6:現在のページにアクティブクラスを適用する

YAMLデータファイルからリストに項目を挿入することに加えて、ユーザーがそのページを表示している場合は、通常、現在のリンクを強調表示することも必要です。これは、現在のページのURLと一致する項目に`active`クラスを挿入することによって行います。

CSS

.result li.active a {
    color: lightgray;
    cursor: default;
}

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 %}

結果

ここでは、`デプロイ`が現在のページであると仮定します。

`item.url`(YAMLファイルに保存されている)が`page.url`と一致することを確認するために、`{{ page.url }}`をページに出力することが役立つ場合があります。

シナリオ7:条件付きで項目を含める

リストに条件付きで項目を含める場合があります。たとえば、複数のサイト出力があり、特定の出力のサイドバー項目のみを含めたい場合があります。各リスト項目にプロパティを追加し、それらのプロパティを使用してコンテンツを条件付きで含めることができます。

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

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

結果

`デプロイ`ページは、その`バージョン`が`2`であるため除外されます。

シナリオ8:フロントマターのプロパティに基づいて項目を取得する

`_data`フォルダにYAMLファイルにナビゲーション項目を保存したくない場合は、`for`ループを使用して各ページまたはコレクションのフロントマターを調べ、フロントマターのプロパティに基づいてコンテンツを取得できます。

このシナリオでは、_docsというコレクションがあると仮定します。コレクションはページよりも多くの場合優れており、ループ処理する項目のリストを絞り込むことができます。(大量の項目をループ処理するシナリオは避けてください。ビルド時間が長くなります。コレクションを使用すると、範囲を絞り込むことができます。)

このシナリオでは、docsコレクションには、Sample 1、Sample 2、Topic 1、Topic 2、Widget 1、Widget 2の6つのドキュメントがあります。

コレクション内の各ドキュメントには、フロントマターに少なくとも3つのプロパティが含まれています。

  • title
  • category
  • order

各ページのフロントマターは以下のとおりです(簡潔にするためにまとめています)。

---
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
---

categoryはドキュメントのフロントマターで使用されていますが、投稿のようにcategoryはビルトイン変数ではありません。つまり、site.docs.categorycategoryの中身を直接参照することはできません。

特定のカテゴリのコレクション内のすべてのドキュメントを取得したい場合、if条件付きのforループを使用して、特定のカテゴリを確認することができます。

<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>

結果は次のようになります。

はじめに

これは、ナレッジベースを構築していて、各カテゴリに数十個のトピックがあり、各カテゴリが独自のページに表示される場合に役立つ可能性があります。

しかし、カテゴリ別に項目をソートし、カテゴリ名の下にグループ化したい場合(カテゴリ名をハードコーディングせずに)は、2つのフィルタを使用できます。

  • group_by
  • sort

対応するカテゴリの見出しの下にグループ化されたページのリストを取得するためのコードを次に示します。

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 %}

結果

はじめに

設定

デプロイ

コードを見ていきましょう。まず、変数(mydocs)にコレクションの内容(site.docs)を代入します。

group_byフィルタは、コレクションの内容をcategoryでグループ化します。group_byフィルタは、より具体的には、mydocsnameitemssizeプロパティを持つ配列に変換します。おおよそ次のようになります。

[
  {"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}
]

for cat in mydocsを使用して、mydocs配列内の各項目をループ処理し、カテゴリnameを出力します。

カテゴリ名を取得した後、ドキュメントの変数itemsを代入し、sortフィルタを使用して、ドキュメントをorderプロパティで並べ替えます。cat.itemsというドット記法は、items配列の内容にアクセスするためです。sortフィルタは、項目を昇順に並べ替えます。

for item in itemsループは、各itemをループ処理し、titleurlを取得してリスト項目のリンクを作成します。

group_byフィルタの詳細については、JekyllのテンプレートドキュメントこのSiteleafチュートリアルを参照してください。sortフィルタの詳細については、Liquidのドキュメントにあるsortを参照してください。

ドキュメントのフロントマターのプロパティを使用してページを取得する場合でも、YAMLデータファイルを使用する場合でも、どちらの場合も、サイトのより堅牢なナビゲーションをプログラムで構築できます。

シナリオ9:再帰によるネストされたツリーナビゲーション

任意の深さのネストされたツリーナビゲーションが必要だとします。これは、ナビゲーションリンクのツリーを再帰的にループ処理することで実現できます。

YAML

nav:
  - title: Deployment
    url: deployment.html
    subnav:
      - title: Heroku
        url: heroku.html
        subnav:
          - title: Jekyll on Heroku
            url: jekyll-on-heroku.html
  - title: Help
    url: help.html

Liquid

まず、ナビゲーションツリーのレンダリングに使用できるインクルードを作成します。このファイルは_includes/nav.htmlになります。

<ul>
  {% for item in include.nav %}
    <li><a href="{{ item.url }}">{{ item.title }}</a>
      {% if item.subnav %}
        {% include nav.html nav=item.subnav %}
      {% endif %}
    </li>
  {% endfor %}
</ul>

レイアウトまたはページでこれをレンダリングするには、単にテンプレートを含めてnavパラメータを渡します。この場合、page.navを使用してYAMLフロントマターから取得します。

{% include nav.html nav=page.nav %}

このインクルードは最初にこれを使用し、次に各項目のsubnavプロパティを調べて、ネストされたリストを再帰的にレンダリングします。

結果