htmx と a-blog cms で作る「もっと見る」ボタンの実装方法

まず、実践的な実装方法の一つとして、標準的なページャー UI を Ajax を使用した動的コンテンツ読み込みに置き換える方法を紹介します。これを実現するために、htmx を利用します。



head に htmx のライブラリを読み込み

<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/ajax-header.js"></script>

a-blog cmshtmx のバックエンドに利用する場合、ajax-header.js の読み込みが必要ですので、ご注意ください。

リスト表示のモジュールのページャーをカスタマイズ

リスト表示用のモジュールのページャーをカスタマイズします。以下の例では、次のページへのリンクをカスタマイズしています。

<!-- BEGIN pager:veil -->
<!-- BEGIN forwardLink -->
<form hx-post="" hx-ext="ajax-header" hx-trigger="click" hx-target="this" hx-swap="outerHTML" class="acms-text-center">
 <input type="hidden" name="bid" value="1">
 <input type="hidden" name="cid" value="7">
 <input type="hidden" name="tpl" value="/include/htmx/headline.html">
 <input type="hidden" name="page" value="{forwardPage}">
 <p class="acms-text-center">
  <input type="submit" name="ACMS_POST_2GET" value="次の{forwardNum}件へ"
  class="acms-btn acms-btn-primary acms-btn-large">
 </p>
</form><!-- END forwardLink -->
<!-- END pager:veil -->

tpl を利用する際の private/cofig.system.yaml 設定

設定を全体を解除する(非推奨)

forbid_tpl_url_context: off

設定を部分的に解除する(推奨)

forbid_tpl_url_context: on
allow_tpl_path: [include/htmx/headline.html]

テンプレートファイルの html チェック

拡張子が .html のファイルが HTML フォーマットでない場合に 404 エラーを返す設定を無効にします。これは、a-blog cms が部分的な HTML を読み込めるようにするために必要です。

html_format_validate: off

続きを読み込む際の HTML で気を付けること

モジュールを <ul><li>〜</li></ul> で読み込む設定をすると、複数の <ul> タグが生成されることがあります。<li>〜</li> 部分だけを読み込み、全体として1つの <ul> タグで済むようなマークアップを心がけましょう。

_top.html

<h2 class="section-heading acms-text-center">お知らせ</h2>
<div class="module-section">
	<ul class="headline headline-1col acms-list-group clearfix" aria-labelledby="layout-top_headline">
	@include("/include/htmx/headline.html")
	</ul>
</div>

include/htmx/headline.html

<!-- BEGIN_MODULE Entry_Headline id="headline_news" -->
@include("/admin/module/setting.html")
<!-- BEGIN entry:loop -->
  <li class="headline-item {entry:loop.class}">
   <!-- BEGIN url#front --><a href="{url}" class="acms-list-group-item headline-link"><!-- END url#front -->
   <time class="headline-dat" datetime="{date#Y}-{date#m}-{date#d}">{date#Y}年{date#m}月{date#d}日( {date#week} )</time>
   <!-- BEGIN category:veil --><span class="acms-label">{categoryName}</span><!-- END category:veil -->
   <span class="headline-title">{title}</span><!-- BEGIN new --><span class="acms-label acms-label-danger">NEW</span><!-- END new -->
   <!-- BEGIN url#rear --></a><!-- END url#rear -->
  </li><!-- END entry:loop -->
  <!-- BEGIN pager:veil -->
  <li>
   <!-- BEGIN forwardLink -->
   <form hx-post="" hx-ext="ajax-header" hx-trigger="click" hx-target="this" hx-swap="outerHTML"
    class="acms-text-center">
    <input type="hidden" name="bid" value="1">
    <input type="hidden" name="cid" value="7">
    <input type="hidden" name="tpl" value="/include/htmx/headline.html">
    <input type="hidden" name="page" value="{forwardPage}">
    <p class="acms-text-center">
     <input type="submit" name="ACMS_POST_2GET" value="次の{forwardNum}件へ"
      class="acms-btn acms-btn-primary acms-btn-large">
    </p>
   </form><!-- END forwardLink -->
  </li>
  <!-- END pager:veil -->
<!-- END_MODULE Entry_Headline -->

波括弧を展開しない (@verbatim)

a-blog cms のテンプレートエンジンでは変数に波括弧を使用しているため、JavaScriptなどの波括弧とぶつかってしまい、そのままでは正常にJavaScriptが動作しません。 JavaScriptを正常に動作させるためには、波括弧をバックスラッシュでエスケープする必要があります。

<div id="app">
  \{\{ message \}\}
</div>

@verbatimブロック

テンプレートの広い箇所でJavaScript変数を表示する場合は、 HTMLを @verbatim ブロックで囲めば、波括弧を1つ1つをバックスラッシュでエスケープする必要がなくなります。

Ver. 2.10.8 で追加

@verbatim
<div id="app">
{{ message }}
</div>
@endverbatim

複数のエリアを同時に更新する hx-swap-oob

a-blog cmspost include で出来なかった事で htmx を採用する事でできるようになる大きなところとして「複数のエリアを同時に更新」が可能になったところではないでしょうか。


上記は htmx を活用したサンプルサイトです。 合わせてご覧ください。


hx-swap-oob 属性とは

要素を置換する場所を指定するには、hx-target属性を使用します。一方で、hx-swap-oob(Out-Of-Band Swapの略)属性を使うことで、hx-targetで指定された要素以外にも、同時に他の部分を置換することができます。

更新前のページで事前準備

更新をかけたい <div>要素について名前をつけておく必要がありますので、id="main-contents"id="topicpath" という属性を設定します。


一覧ページから詳細ページへのリンク

一覧ページから詳細ページへのリンクとしては以下のように記述します。複数箇所の更新の際にも呼び出し側の記述は特に変化はありません。通常の htmxhx-get などの記述になります。

<a href="{url}" 
  hx-get="{url}/tpl/include/htmx/realestate-body.html"
  hx-push-url="{url}"
  hx-target="#main-contents"
  hx-swap="innerHTML"
  hx-ext="ajax-header" 
class="card-link">

呼び出されるテンプレート側の記述

呼ばれるテンプレートには、以下のような記述をします。hx-swap-oob="true" と一緒に書かれている id属性 の部分が置き換わる事になります。

<div id="main-contents" hx-swap-oob="true">
<!-- BEGIN_MODULE Entry_Body -->
(略)
<!-- END_MODULE Entry_Body -->
</div>

<div id="topicpath" hx-swap-oob="true">
<!-- BEGIN_MODULE Topicpath -->
(略)
<!-- END_MODULE Topicpath -->
</div>

<!-- BEGIN_MODULE Ogp -->
<title>htmx:{title}</title>
<!-- END_MODULE Ogp -->

おまけ:タイトルタグの更新

上記では、id="main-contents"id="topicpath" の更新をするように書いていましたが、おまけで <title> の更新もできるようにします。 <title> については、今回の hx-swap-oob とも関係なく読み込んだファイルに <title>タグがあれば上書きをする仕様とのことです。

a-blog cms の場合 Ogp モジュールで <title>を組み立てる処理がありますので、そちらを利用します。 また、テストで更新できたことを分かりやすいようにテスト用のサイトではタイトルタグに htmx: を前につけて htmx でコンテンツを更新したことが分かりやすいようにしています。