第6回:一覧ページを整える


この章でやること

  • news/index.twig に V2_Entry_Summary モジュールを実装する

  • サムネイル画像・サマリー・タグを出力する

  • ページャー(「もっと見る」ボタン)を実装する

  • モジュールIDと画像フィールドの紐付けを確認する

この章で3つのメインテンプレート(トップ・詳細・一覧)がすべて揃い、サイトとしての基本的な動作が完成します。


1. テンプレートを準備する

現在、/news/ にアクセスすると、静的HTMLの news/index.html が表示されているか、または一覧ページ用のテンプレートが存在しないため404になるかページが表示されない場合があります。一覧ページをCMS化するには、テンプレートファイル news/index.twig が必要です。

既存の静的ページをひな形にしてテンプレートを作ります。

news/index.html をリネームして news/index.twig を作成してください。

/themes/sample/
├── _top.twig
├── news/
│   ├── index.twig   ← 追加
│   └── _entry.twig
├── service/
├── contact/
├── css/
└── images/

この状態で https://ablogcms-tutorial.ddev.site/news/ にアクセスすると、お知らせ一覧ページが表示されます。まだCMS化されていないため、静的HTMLの内容がそのまま表示されています。ここからモジュールIDの設定とテンプレートの実装を進めていきます。


2. 対象のファイルを確認する

news/index.twig を開くと、見出しの下にお知らせを繰り返し表示している部分があります。

<div class="border-b border-gray-200 pb-8 mb-12">
  <h1 class="text-3xl font-bold ...">News</h1>
  <p class="mt-4 text-lg text-gray-600">最新情報や季節限定のメニューなどをお届けします。</p>
</div>
<ul class="space-y-8">
  <li>
    ...(今回の対象)
  </li>
</ul>

この <ul><li> の繰り返し部分を V2_Entry_Summary モジュールに置き換えます。モジュールIDは news_index とします。


3. 管理画面でモジュールIDを先に作成する

今回はテンプレートの実装より先に管理画面の設定を行います。一覧ページで画像を表示するための「メインイメージ」設定は、管理画面側で先に指定しておく必要があるためです。

管理画面に移動し、モジュールID news_index を作成します。

条件設定

項目

設定値

モジュール

V2サマリー(V2_Entry_Summary)

モジュールID

news_index

名前

お知らせ一覧用

URLコンテキスト

チェックを入れる(/news/ のURL情報から自動的に「お知らせ」カテゴリーを取得)

ページ番号

チェックを入れる(2ページ目以降のURLを正しく処理するために必須)


URLコンテキストとは

トップページ用の news_top では「カテゴリーIDの参照」でお知らせカテゴリーを直接指定しました。 一覧ページでは「URLコンテキスト」を有効化します。「URLコンテキスト」有効化することで、/news/ というURLパスから a-blog cms が自動的に「お知らせ」カテゴリーを判定するため、カテゴリーIDを手動で指定する必要がありません。

ページ番号のチェックは忘れがちですが必須です。2ページ目以降のURL(/news/page/2/ など)の情報もモジュールIDに渡すことで、正しいページングが機能します。

URLコンテキスト


表示設定

項目

設定値

表示件数

3

表示順

日付(降順)

メインイメージ対象

カスタムフィールドにチェックを入れて entry_main_image を指定

「メインイメージ対象」を設定することで、カスタムフィールドで登録した画像を entry.mainImage.path で取得できるようになります。

表示件数を3、表示順を日付(降順)に設定している管理画面
メインイメージ対象をカスタムフィールドに設定している管理画面

4. 繰り返し表示される記事をTwig化する

管理画面の設定が完了したら、手順1で作成した news/index.twig のテンプレート実装に進みます。

まずモジュール呼び出しとループの骨格を書きます。

{% set entrySummary = module('V2_Entry_Summary', 'news_index') %}
<ul class="space-y-8">
  {{ include('/admin/module/setting.twig', { moduleInfo: entrySummary.moduleInfo }) }}
  {% for entry in entrySummary.items %}
    <li>
      ...(繰り返し表示される記事)
    </li>
  {% endfor %}
</ul>
...(もっと見るリンク)

{{ include('/admin/module/setting.twig', { moduleInfo: entrySummary.moduleInfo }) }} を追加することで、ログイン中の管理者が一覧表示部分にホバーすると「編集」リンクが表示され、閲覧画面から直接モジュールID(news_index)の設定を編集できるようになります。

次に <li> の中身を実装します。変換前の静的HTMLと変換後のTwig版を対比して見ていきましょう。

変換前(静的HTML)

<li>
  <a href="20250807.html" class="group block p-6 sm:p-8 rounded-lg ...">
    <article class="grid md:grid-cols-3 gap-8 items-start">
      <div class="md:col-span-1 overflow-hidden rounded-lg">
        <img src="/images/news_tomato_pasta.jpg" alt="季節のサラダ" class="...">
      </div>
      <div class="md:col-span-2">
        <p class="text-sm text-gray-500">
          <time datetime="2025-08-07">2025年8月7日</time>
        </p>
        <h2 class="...">新しい季節限定メニューが登場しました</h2>
        <p class="mt-3 text-gray-600 leading-relaxed">
          太陽の恵みをたっぷりと受けた新鮮な夏野菜と…
        </p>
        <div class="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2">
          <span class="bg-sky-100 ...">新商品</span>
          <span class="text-sm text-gray-500">#季節限定</span>
          <span class="text-sm text-gray-500">#夏メニュー</span>
        </div>
        <p class="...">続きを読む →</p>
      </div>
    </article>
  </a>
</li>

変換後(Twig版)

<li>
    <a href="{{ entry.url }}" class="group block p-6 sm:p-8 rounded-lg hover:bg-gray-50 transition-colors duration-300">
        <article class="grid md:grid-cols-3 gap-8 items-start">
            <div class="md:col-span-1 overflow-hidden rounded-lg">
                {% if entry.mainImage and entry.mainImage.path %}
                  <img
                    src="{{ entry.mainImage.path | resizeImg(600) }}"
                    alt="{{ entry.mainImage.alt }}"
                    class="w-full h-56 object-cover shadow-md ..."
                    width="{{ entry.mainImage.width }}"
                    height="{{ entry.mainImage.height }}"
                    loading="lazy"
                    decoding="async">
                {% endif %}
            </div>
            <div class="md:col-span-2">
                <p class="text-sm text-gray-500"><time datetime="{{ entry.date | date('Y-m-d') }}">{{ entry.date | date('Y年m月d日') }}</time></p>
                <h2 class="mt-2 text-xl font-bold text-gray-900 transition-colors group-hover:text-sky-600">
                    {{ entry.title }}
                </h2>
                <p class="mt-3 text-gray-600 leading-relaxed">
                    {{ entry.summary | mb_trim(200, '...') }}
                </p>
                <div class="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2">
                    {% if entry.category.items is not empty %}
                        <span class="bg-sky-100 text-sky-800 text-xs font-semibold px-2.5 py-0.5 rounded-full">{{ entry.category.items[0].name }}</span>
                    {% endif %}
                    {% for tag in entry.tags %}
                        <span class="text-sm text-gray-500">#{{ tag.name }}</span>
                    {% endfor %}
                </div>
                <p class="mt-4 inline-block font-semibold text-sky-600 transition-transform duration-300 group-hover:translate-x-1">
                    続きを読む &rarr;
                </p>
            </div>
        </article>
    </a>
</li>

置き換えポイント解説

画像の条件出力

{% if entry.mainImage and entry.mainImage.path %}
  <img
    src="{{ entry.mainImage.path | resizeImg(600) }}"
    alt="{{ entry.mainImage.alt }}"
    class="w-full h-56 object-cover shadow-md ..."
    width="{{ entry.mainImage.width }}"
    height="{{ entry.mainImage.height }}"
    loading="lazy"
    decoding="async">
{% endif %}

V2モジュールでは画像を entry.mainImage オブジェクトで受け取ります。pathaltwidthheight を個別に取得できるため、適切な alt テキストや実際の画像サイズも出力できます。

また、a-blog cms 独自の resizeImg フィルターを利用して画像をリサイズしています。

サマリーの文字数制限

{{ entry.summary | mb_trim(200, '...') }}

a-blog cms 独自の mb_trim フィルターでサマリーを200文字で切り詰め、末尾に「...」を付けます。

a-blog cms の Twig テンプレートでは「校正オプション」がそのままTwig フィルターとして利用できます。 他にも多くのフィルターがありますので、詳しくは公式ドキュメントの 「校正オプション」 をご覧ください。

ただし、Twig 標準のフィルターと名前が重複するものについてはプレフィックス acms_ を付与して指定する必要があります。


5. ページネーションを実装する

3件ごとにページが切り替わる「もっと見る」ボタンを実装します。V2モジュールではページネーション情報はモジュールデータの pagination プロパティに格納されています。

変換前(静的HTML)

<div class="mt-16 text-center">
  <a href="page2.html" class="...">もっと見る</a>
</div>

変換後(Twig版)

{% if entrySummary.pagination.nextPage %}
  <div class="mt-16 text-center">
    <a href="{{ entrySummary.pagination.nextPage.url }}" class="inline-flex items-center rounded-md border border-transparent bg-sky-600 px-6 py-3 text-base font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2">
      もっと見る
    </a>
  </div>
{% endif %}

entrySummary.pagination.nextPage が存在する(次のページがある)ときだけボタンを表示します。最終ページに達すると pagination.nextPage が空になり、ボタン全体が非表示になります。

https://ablogcms-tutorial.ddev.site/news/ にアクセスして、最新の3件が表示され、「もっと見る」をクリックすると2ページ目以降も正しく表示されることを確認してください。

ページネーションが実装できたら news/page2.html は不要なので削除してください。


6. カスタムフィールドと画像フィールドの紐付けを確認する

第4章で field.html に画像フィールド(entry_main_image)を定義しました。一覧ページでその画像を表示するには、モジュールID側でも「どのフィールドをメインイメージとして使うか」を指定する必要があります。

手順3で作成した news_index のモジュールID設定を開き、「表示設定」タブで以下を確認してください。

項目

設定値

メインイメージ対象

カスタムフィールドにチェックが入っているか確認

この設定があることで、テンプレート内で entry.mainImage.path として画像を取得できます。第4章で記事投稿時に画像をアップロード済みであれば、この時点で一覧ページに画像が表示されます。

7. 管理ボックスと管理用アセットを設置する

一覧ページにも管理ボックスを設置し、ログイン状態で「新規投稿」「管理画面へ移動」などの操作ができるようにします。また、管理UIが正しいデザインで表示されるよう、管理用アセットを読み込みます。

管理ボックスの設置

news/index.twig の見出しセクション(<h1>News</h1> を含む <div>)の直前に、以下を追加してください。

@include("/admin/action.html")

管理用アセットの読み込み

news/index.twig<head> タグ内の管理UI用リソースが含まれていない場合は、<head> タグ内に以下を追加してください。トップページ(_top.twig)や詳細ページ(_entry.twig)と同様の記述です。

<link rel="stylesheet" href="/css/acms-admin.min.css">
{% set js = module('V2_Js') %}
<script src="{{ JS_LIB_JQUERY_DIR }}jquery-{{ JS_LIB_JQUERY_DIR_VERSION }}.min.js" charset="UTF-8"></script>
<script src="{{ ROOT_DIR }}acms.js{{ js.arguments }}" charset="UTF-8" id="acms-js"></script>

https://ablogcms-tutorial.ddev.site/news/ にアクセスし、ログイン状態で管理ボックスが横並びで正しく表示されることを確認してください。


まとめ

この章でやったこと:

  • module('V2_Entry_Summary', 'news_index') 関数でモジュールデータを取得し、entrySummary.items でループした

  • {% if entry.mainImage and entry.mainImage.path %} で画像の条件出力を実装した

  • {{ entry.mainImage.path | resizeImg(600) }} で画像リサイズ、{{ entry.summary | mb_trim(200, '...') }} でサマリー切り詰めを行った

  • {% if entrySummary.pagination.nextPage %} でページャーを実装した

  • モジュールIDの「メインイメージ対象」に entry_main_image を指定して画像フィールドと紐付けた

次の最終章では、サンプルデータをインポートして完成を確認します。