【Craft 3 サイト構築の基本】セクション詳細ページのテンプレート作成(実装編)

これは Craft CMS Advent Calendar 2018 22日目の記事です。
連載「サンプル制作で覚える Craft 3 サイト構築の基本」として、「準備編」に引き続き、セクション詳細ページのテンプレートを作成します。

ここでは、次の作業を行います。

  1. 対象テンプレートの確認
  2. インデックステンプレートの微調整
  3. 基本コードの定義
  4. 「会社案内」セクション詳細ページの作成
  5. 「サービス」セクション詳細ページの作成
  6. 「ワークス」セクション詳細ページの作成

工程ごとの差分データは「素材データ専用リポジトリ vol-15 ブランチ」にコミットしてあります。

作業にあたって

今回の作成対象である「会社案内」セクションのエントリは空の状態ですので、表示を確認する前にダミーの情報を登録するか、「Craft 3 のデモコンテンツをインストールしてみよう」で紹介している公式デモサイトを別途ローカル環境に用意し、当該ページのテキストをコピーするなどしてください。

また、Feed Me プラグインを利用したエントリのインポート後の 記事本文(行列フィールド) に含まれるブロックごとの「位置」フィールドのプルダウンが適切にセットされていない可能性があります。詳細ページの表示確認で見た目に差異がある(本来、右に配置されるはずの画像がテキストの下に出力されている、など)場合は、それぞれのエントリ編集画面で適宜調整してください。

対象テンプレートの確認

ここで修正対象となるテンプレートは次の通りです。

templates
├── _partials/
│   ├── article_body.html
│   └── content_header.html
├── about/
│   └── index.html
├── services/
│   ├── _entry.html
│   └── _index.html
└── work/
    ├── _entry.html
    └── _index.html

このうち templates/services/_index.htmltemplates/work/_index.html の2つは、既にある index.html_index.html にリネームしてください。

Architect プラグインによるインポートで作成した「サービス(インデックス)」および「ワークス(インデックス)」セクションの設定にあわせた微調整となります。

インデックステンプレートの微調整

「サービス(インデックス)」と「ワークス(インデックス)」の main ブロックに、対象セクションごとのエントリ一覧を出力します。【参考コミット】

{% block main %}
  <ul>
    {% for entry in craft.entries.section('services').all() %}
      <li><a href="{{ entry.url }}">{{ entry.title }}</a></li>
    {% endfor %}
  </ul>
{% endblock %}

動作確認の際、フロントエンドのグローバルナビゲーション経由で詳細ページへ遷移できるようになります。

基本コードの定義

タイトル用の変数と main ブロックの定義

「サービス」および「ワークス」セクションの詳細テンプレートに、ベースとなるコードを定義します。【参考コミット】

コーディングデータからメインコンテンツ部分のコピー

「会社案内」「サービス」「ワークス」それぞれの詳細テンプレート main ブロックに、メインコンテンツ部分のマークアップをコピーします。【参考コミット】

詳細テンプレート向け共通モジュールの読み込み

「会社案内」「サービス」「ワークス」それぞれの詳細テンプレートに {% include %} タグを加え、「ニュース」セクション詳細ページで作成したモジュールを読み込みます。【参考コミット】

セクションごとに必要なモジュールが異なりますので「準備編」も参考にしてください。

「会社案内」セクション詳細ページの作成

大見出し、本文の出力

「大見出し」と「本文」フィールドは {% if %} タグで入力値がある場合のみ出力します。【参考コミット】

「ロケーション」セクションに含まれるエントリの出力

別セクションの「ロケーション」に含まれるエントリを取得し、ループ処理します。【参考コミット】

{# 「ロケーション」セクションのエントリを取得 #}
{% set locationEntries = craft.entries.section('locations').all() %}

{# 「ロケーション」セクションのエントリ一覧 #}
{% for locationEntry in locationEntries %}
  (中略)
{% endfor %}

いくつかポイントを解説します。

{# 「住所」がセットされている場合のみ出力 #}
{% if locationEntry.address %}
  <p class="delta subfont caps address">{{ locationEntry.address | nl2br }}</p>
{% endif %}

「住所」フィールドは 改行を含むプレーンテキスト となるため、nl2br フィルタで改行コードを br タグに変換して出力します。

{# 「お問い合わせ方法」がセットされている場合のみ出力 #}
{% if locationEntry.contactMethods | length %}
  <dl class="inline">
    {# ブロックのループ処理 #}
    {% for block in locationEntry.contactMethods.all() %}
      <dt>{{ block.label }}</dt>
      <dd>{{ block.data }}</dd>
    {% endfor %}
  </dl>
{% endif %}

行列フィールドの「お問い合わせ方法」は {% if locationEntry.contactMethods | length %} による判別を加え、ブロックが1つ以上セットされている場合のみ dl タグを出力するようにしています。

{# 「メールアドレス」または「住所」がセットされている場合のみ出力 #}
{% if locationEntry.email or locationEntry.address %}
  <ul class="horz-list">
    (中略)
  </ul>
{% endif %}

EMAIL US および MAP ボタンを含む ul タグは、「メールアドレス」または「住所」のいずれかが入力されている場合のみ出力するようになっています。

{# 「住所」フィールドを利用した Google マップのリンク URL をセット #}
{% set googleMapsUrl = 'https://www.google.com/maps/search/' ~ locationEntry.address | replace("\n", ', ') | url_encode %}
<li>
  <a href="{{ googleMapsUrl }}" target="_blank" class="subfont caps map-btn">Map</a>
</li>

Google マップのリンクは、replace("\n", ', ') で「住所」フィールドのテキストに含まれる改行コードをカンマに置換し、url_encode で URL エンコードしたものを変数にセットしています。このとき、Twig タグ内で文字列として連結するため ~ を利用しています。

「サービス」セクション詳細ページの作成

「コンテンツヘッダー」モジュールの調整

「コンテンツヘッダー」モジュールに含まれる メイン画像 の出力箇所を「サービス」セクションのみ抑制するよう調整を加えます。【参考コミット】

{# 「コンテンツヘッダー」でメイン画像を出力しないためのフラグをセット #}
{% set excludeContentHeaderImage = 1 %}

詳細ページテンプレート(templates/services/_entry.html)に変数をセットし

{# 「メイン画像」がセットされていて、かつ、excludeContentHeaderImage が未定義の場合のみ出力 #}
{% if image and excludeContentHeaderImage is not defined %}
  <section>
    <img class="contain-media" src="{{ image.getUrl({ width: 1440, height: 360 }) }}" alt="{{ image.title }}">
  </section>
{% endif %}

モジュールの {% if %} タグに「変数が未定義である(excludeContentHeaderImage is not defined)」という条件文を加えます。

ローカルナビゲーションの出力

「サービス」セクションに含まれるエントリの取得・出力を行います。【参考コミット】

{# ループ中の「サービス」エントリ ID と現在のページのエントリ ID が同一なら true #}
{% set isCurrent = (serviceEntry.id == entry.id) %}

<li>
  <a href="{{ serviceEntry.url }}" class="subfont caps{% if isCurrent %} current{% endif %}">
    {{ serviceEntry.title }}
  </a>
</li>

アクティブなページの文字色を変更するため、ループ中のエントリ ID(serviceEntry.id)と詳細ページのエントリ ID(entry.id)が同一であれば a タグにクラス current を付加するよう、分岐処理を加えています。

「サービス本文」フィールドの出力

行列フィールドである「サービス本文」のマークアップは、ブロック一つ分のコードを残して削除します。【参考コミット】

{# 「サービス本文」がセットされている場合のみ出力 #}
{% if entry.serviceBody | length %}
  <section class="service-points">
    {# ブロックのループ処理 #}
    {% for block in entry.serviceBody.all() %}
      (中略)
    {% endfor %}
  </section><!-- /.service-points -->
{% endif %}

「会社案内」セクションの詳細ページテンプレートの「お問い合わせ方法」フィールドと同様に、length でブロックの有無を判別した上で、ループ処理を加えます。【参考コミット】

エントリに関連づけられている「ワークス」セクションのエントリを出力

表示中のエントリに関連づけられている別セクションのエントリは relatedTo(entry) で絞り込むことができます。ここでは最新1件のみを取得するため、one() も付加しています。【参考コミット】

{# このエントリに関連づけられている「ワークス」セクションのエントリ1件を取得 #}
{% set relatedWorksEntry = craft.entries.section('work').relatedTo(entry).one() %}

なお、relatedTo パラメータのオプションで、関連づけの種別(ソース、または、ターゲット)、対象フィールド、ロケールを指定できます。詳細については、公式ドキュメントの「リレーション > テンプレート記法」を参照してください。

「ワークス」セクション詳細ページの作成

「背景色」フィールドの反映

「コンテンツヘッダー」および「article 本文」モジュールに分岐処理を加え、「背景色」フィールドにセットされた値を反映できるようにします。【参考コミット】

{# 「引用」ブロック #}
{% case 'quote' %}
  {# 「位置」がフルで「背景色」が #000 でなければ、指定された HEX 値をセット #}
  {% set bgColor = (position == 'full' and entry.backgroundColor != '#000000') ? entry.backgroundColor %}

  <blockquote class="blockquote{% if bgColor %} reverse{% endif %}"{% if bgColor %} style="background-color: {{ bgColor }};"{% endif %}>
    (中略)
  </blockquote>

ここでは変数 bgColor に三項演算子を利用してセットした値によって、blockquote タグのクラスや style 属性を変化させています。なお、三項演算子部分の記述については、Twig の公式リファレンスも参照してください。

「サービスアイコン」フィールドの出力

「サービスアイコン」は他の行列フィールドと同様、{% for %} タグによるループ処理で出力します。【参考コミット】

{% for serviceEntry in entry.servicesPerformed.all() %}
  {# 「サービスアイコン」フィールドを取得 #}
  {% set image = serviceEntry.serviceIcon.one() %}

  <a href="{{ serviceEntry.url }}">
    {# 「サービスアイコン」がセットされている場合のみ出力 #}
    {% if image %}
      <img src="{{ image.url }}" alt="{{ image.title }}">
    {% endif %}
    <span class="services-title">{{ serviceEntry.title }}</span>
  </a>
{% endfor %}

前後ページのナビゲーションを出力

「ワークス」セクションに含まれる前後のエントリ詳細ページへのリンクを出力します。【参考コミット】

{# 前後ページを取得する際のパラメータをセット #}
{% set params = {
  section: 'work',
  orderBy: 'title'
} %}

{# 前のページを取得 #}
{% set prevEntry = entry.getPrev(params) %}

{# 次のページを取得 #}
{% set nextEntry = entry.getNext(params) %}

entry.getPrev() で前のエントリを取得できます。対象エントリを「ワークス」セクションに限定するため、パラメータをセットしています。

{# 前のページが存在する場合のみ出力 #}
{% if prevEntry %}
  <a href="{{ prevEntry.url }}" class="pag-prev-link">Prev</a>
{% endif %}
{# 次のページが存在する場合のみ出力 #}
{% if nextEntry %}
  <a href="{{ nextEntry.url }}" class="pag-next-link">Next</a>
{% endif %}

先頭、および、最終エントリでは前後いずれかが存在しないため、{% if prevEntry %} の形で出力対象が存在するかどうかチェックしています。

ここまでの作業で、セクションごとの詳細ページテンプレートを用意できました。

まとめ

ここでは「会社案内」「サービス」「ワークス」それぞれのセクション詳細ページテンプレートを作成してみました。

「条件分岐」「取得対象のセクション(または、フィールドなど)」「絞り込み方法」に違いはあるものの、「ニュース」セクションのテンプレート作成と大差がないことが判ると思います。Twig の基本を押さえられれば、ある程度自由にカスタマイズできそうですよね。

次回は、サイトトップや一覧ページなどの「インデックスページのテンプレート作成」を行います。