コンテンツにスキップ

ルーティング: データバインディング

データバインディングは、CMSoD のデータストアに格納されているコンテンツ(ニュース記事・商品データ・コレクションのエントリなど)を取得し、HTML テンプレートに差し込む仕組みです。これにより、HTML を 1 つ用意するだけで複数の動的ページを生成できます。

このページは ルート設定 の続きです。先にルートの基本概念を確認しておいてください。

バインドの基本設定

1 つのルートに対して、1 つ以上の「バインド」を設定できます。それぞれのバインドが「どのデータを、どのような条件で取得するか」を定義します。

項目 説明 必須
バインド名 このバインドの識別名(最大 64 文字、英数字) はい
バインドドメイン データ取得元のコレクション はい
クエリ 取得条件(JSON 形式) はい
ソート 取得結果の並び順 いいえ
件数制限 最大取得件数 いいえ
1 件のみ取得 チェックすると 1 件のみ取得 いいえ
結果なし時に 404 取得結果が 0 件のときに 404 を返す いいえ

ルート編集ダイアログのバインド設定セクション

「バインド名」はテンプレートからデータを参照するときの名前として使われます。後から変更すると、参照箇所もまとめて修正する必要があるため、最初に決めた名前を維持することを推奨します。

クエリの記述

クエリは、データ取得条件を JSON 形式で記述します。比較演算子($eq $gt 等)と論理演算子($and $or)を組み合わせた JSON ベースのクエリ DSL です。

単純な条件

{
  "status": "published"
}

→ status フィールドが published のレコードのみ取得。

URL パラメータの参照

URL パターンに :id のようなパラメータがある場合、クエリ内から参照できます。

{
  "_id": ":id"
}

→ パスパラメータ :id の値が _id に一致するレコードを取得。

よく使う比較演算子

演算子 意味 例
$eq 等しい {"status": {"$eq": "active"}}
$ne 等しくない {"status": {"$ne": "draft"}}
$gt より大きい {"count": {"$gt": 10}}
$gte 以上 {"count": {"$gte": 10}}
$lt より小さい {"count": {"$lt": 100}}
$lte 以下 {"count": {"$lte": 100}}
$in いずれかに一致 {"category": {"$in": ["news", "blog"]}}
$nin いずれにも一致しない {"category": {"$nin": ["draft"]}}
$exists フィールドの有無 {"thumbnail": {"$exists": true}}
$and AND 条件 {"$and": [{...}, {...}]}
$or OR 条件 {"$or": [{...}, {...}]}

よくあるエラー

エラー 原因 対処
クエリ構文エラー JSON として不正、引用符の閉じ忘れ エディタの構文チェックを確認
フィールド名のタイポ 存在しないフィールドを指定 コレクションのフィールド一覧で確認
結果が常に 0 件 値の型不一致(数値を文字列で比較等) データを開いて型を確認

バインドのパラメータルール

バインドごとに、受け付ける URL クエリパラメータのルールを定義できます。

項目 説明
キー パラメータ名(英数字)
必須 true / false
デフォルト値 パラメータ未指定時の既定値

たとえば検索結果ページで ?keyword=... を受け取り、未指定時は空文字をデフォルトにするといった用途で使います。

ページネーション

取得結果をページ単位で区切って表示できます。

項目 説明
有効/無効 ページネーションのオン/オフ
1 ページあたりの件数 表示件数
プレフィックス URL パラメータのプレフィックス(最大 64 文字)

プレフィックスを設定しておくと、複数のバインドを 1 ページに置いてもそれぞれ独立したページネーションが可能になります(例: news_page=2&blog_page=3)。

グルーピング

取得結果を特定のフィールドでグループ化できます。

項目 説明
有効/無効 グルーピングのオン/オフ
グループキー グループ化するフィールド名

「年月別のアーカイブ一覧」や「カテゴリ別の商品リスト」など、見出し付きの一覧表示で利用します。

カスタムフォーマット

バインドで取得したデータを、テンプレートに渡す前に加工する仕組みです。フィールド値の変換、参照解決(外部キー → 実データ)、フィルタリングなどに使います。

設定項目

項目 説明 必須
キー フォーマット対象のフィールド名(最大 64 文字、英数字・アンダースコア・ハイフン) はい
値 フォーマット定義(JSON 形式) はい
バインド名 適用対象のバインド名。省略時は全バインド いいえ

全バインドに適用 / 特定バインドに適用

カスタムフォーマットは内部的にバインド名でグループ化されます。

{
  "*": {                    ← 全バインドに適用
    "key1": value1
  },
  "bind-name": {            ← 特定バインドにのみ適用
    "key1": value2
  }
}

バインド名を省略すると *(全バインド対象)として扱われます。

値の記述形式

JSON で次の 3 形式が記述できます。

形式 説明
単純な値 固定値への変換
マップ(オブジェクト) クエリ形式でフィルタリング
配列形式 参照先データの取得とソート指定

参照+ソート指定の構文:

[[参照フィールド1, 参照フィールド2], {フィルタ条件}, [ソートフィールド, "asc"]]
  • ソート順は asc(昇順)または desc(降順)

セレクター構文

カスタムフォーマットの値では、ネストされたデータに簡潔にアクセスするためのセレクター構文を使えます。

:parent [:id="5"] :nth(1)
構文 説明
:field-name フィールドへのアクセス
[:attr="value"] 属性でフィルタリング
:nth(n) n 番目の要素
:length 要素数
:empty 空要素のみ
:not-empty 空でない要素のみ
:has(...) 条件に一致する要素

属性フィルターの演算子:

演算子 意味
= 等しい
!= 等しくない
~= 部分一致
^= 前方一致
$= 後方一致
<、<=、>、>= 数値の比較

よく使うパターン例

1. ステータスでの絞り込み

公開済みエントリのみを返す:

{
  "*": {
    "status": "published"
  }
}

2. 参照解決(外部キー → 実データ)

author_id が指している実際の author レコードを引き当てて差し込みます。

{
  "articles": {
    "author": [["author_id"], {}, ["display_name", "asc"]]
  }
}

→ articles.author_id を主キーに author コレクションを引き、表示用に display_name 昇順で並べ替えて articles.author に詰めます。

3. 配列要素の取り出し(セレクター)

tags の 1 番目のタグだけを取り出す:

{
  "articles": {
    "primary_tag": ":tags:nth(1)"
  }
}

4. 条件付きの絞り込み

comments のうち承認済みのみを取得:

{
  "articles": {
    "approved_comments": ":comments[:status=\"approved\"]"
  }
}

5. 全バインド共通の整形

すべてのバインドで日時フィールドを正規化したい場合は * キーを使います。

{
  "*": {
    "published_at": ":published_at"
  }
}

6. 逆参照(自分を参照しているレコードの取得)

「2. 参照解決」は自分の持つ外部キーから相手を引く向きですが、逆に自分を参照している側のレコード(以下、子レコード)を 集めたいことがあります。レシピに紐づく手順、記事に紐づくレビューなどです。 値形式の先頭に ":_referrers" を指定します。

[":_referrers", "参照元コレクション", ":参照フィールド", {静的条件}, {ソート}, 上限]
位置 項目 省略したとき
2 番目 参照元コレクション 必須
3 番目 参照フィールド 参照元がこのコレクションを 1 つのフィールドでしか参照していないときだけ省略できます。省略して 4 番目以降を書くときは、3 番目に null か "" を書きます
4 番目 静的条件 絞り込みなし
5 番目 ソート 登録順(_id の昇順)
6 番目 上限 100 件

recipe を参照している recipe_step を、公開中のものだけ手順番号順に 20 件まで取り出す例です。

{
  "recipe": {
    "steps": [":_referrers", "recipe_step", ":rs_recipe", {"rs_status": 1}, {"rs_order": 1}, 20]
  }
}

ソートは 1 が昇順、-1 が降順です(「2. 参照解決」の ["display_name", "asc"] とは書き方が違います)。 複数の項目で並べるときは [{"rs_order": 1}, {"_id": -1}] のように優先順に書きます。同じ項目を 2 回書いた場合は、先に書いた方だけが使われます。 取得した子レコードは、レコードの配列として recipe.steps に入ります。 子レコードを join や layout の起点にするときは、起点の位置に ":_referrers" を書きます。

{
  "recipe": {
    "steps_text": [{"join": ["%s", [":rs_text"], "、"]}, [":_referrers", "recipe_step", ":rs_recipe"]]
  }
}

→ 手順が「生地をよく混ぜる」「180℃で焼く」の 2 件のとき、recipe.steps_text は 生地をよく混ぜる、180℃で焼く になります。 静的条件とソートを省略しているので、並び順は登録順です。

注意点
  • 逆参照できるのは、参照元の参照型フィールド(1 件参照・複数参照)だけです。 リスト型のフィールドの中に参照が入っている場合は対象になりません。
  • 参照元が同じコレクションを複数のフィールドで参照している場合は、参照フィールドを省略できません (どのフィールドで辿るか決まらないため)。
  • 上限を超えた分は切り捨てられます。件数が増えるコレクションでは、静的条件とソートで 必要な分だけに絞ってください。
  • 静的条件では、バインドのクエリで使える ":パラメータ名" の置換(URL パラメータの参照)は行われません(文字列のまま条件になります)。
  • 静的条件に参照フィールド自身の条件を書いても、表示中のレコードの _id で上書きされます。
  • 子レコードの参照型フィールドは展開されません。join で [":rs_tool"] と書くと ObjectId が出力され、 参照先の項目は使えません。
  • 逆参照はレコードごとに問い合わせが発生します。静的条件に参照型フィールドの条件 ({"rs_tool": {"tl_name": "泡立て器"}} のように参照先の項目で絞り込む書き方)を書くと、 その条件 1 つにつき、逆参照 1 回あたり参照先を検索する問い合わせが 1 回増えます。 一覧を表示するバインドでは表示件数の分だけ問い合わせが増えるため、1 件を表示する詳細ページでの利用を基本とし、 一覧で使う場合は 1 ページあたりの表示件数を抑えてください。
  • 子レコードは、値そのものか join / layout などの起点としてだけ使えます。 書式に差し込むときは、join で文字列にしてから差し込みます。 JSON-LD では join を通して組み立てます(子レコードを配列にする)。
  • 逆参照は、ページのバインドで取得したレコードに対してだけ使えます。 fetch-external(外部レコードの取得)で取得したレコードに適用する書式で使っても、値は出力されません。
  • 次の場合は値が出力されません。ページの他の内容は表示されます。
    • 取得結果が 0 件のとき(閲覧制限で見えない場合を含みます)。join の起点にしていても、 空文字列や [] にはならず、値ごと出力されません。
    • 参照元コレクションが見つからない、または参照フィールドを特定できないとき
    • 静的条件に使えない演算子を書いた場合など、子レコードの取得に失敗したとき
  • 次の書き方は、ルート設定の保存時にエラーになります。
    • 「書式に誤りがあります。」
      • 子レコードを ":_json" / ":_nillable" / 書式の %s に直接差し込む
      • 書式に差し込む値の中で、子レコードを join 以外(layout など)の起点にする
      • 子レコードを split / join-format の対象にする
      • 参照フィールドを省いて、3 番目に静的条件などを書く([":_referrers", "recipe_step", {"rs_status": 1}] など。3 番目には null か "" を書きます)
      • ソートを { } または [{ }, …] 以外の形で書く、または向きに 1 / -1 以外の値を書く
      • 静的条件を { } の形で書かない
      • 上限に 1〜10000 の整数以外を書く、または上限の後ろに値を書く
    • 「逆参照で指定されたコレクションが見つかりません。」
      • 参照元コレクションが存在しない、または埋め込み型を指定した
    • 「逆参照の参照フィールドを特定できません。参照元コレクションで、このバインドのコレクションを参照しているフィールドを指定してください。」
      • 参照元に、このバインドのコレクションを参照する参照型フィールドが無い
      • 指定した参照フィールドが、このバインドのコレクションを参照していない
      • 参照フィールドを省略したが、参照元がこのコレクションを複数のフィールドで参照している

カスタムフォーマット適用後の値は、HTML テンプレート側で {{articles.author.display_name}} のように参照します。

JSON-LD の組み立て

JSON-LD 形式の構造化データをレコードの値から組み立てるときは、値形式の先頭に ":_json" を指定します。 指定すると、書式に差し込まれる値だけが JSON 文字列としてエスケープされます。書式そのものは 構造なのでエスケープされません。

{
  "articles": {
    "ld": [":_json", "{\"@context\":\"https://schema.org\",\"@type\":\"Article\",\"headline\":\"%s\",\"description\":\"%s\"}", ":title", ":summary"]
  }
}

テンプレート側では、そのまま <script> の中に出力します。

<script type="application/ld+json">{{articles.ld}}</script>

":_json" を付けると、値に含まれる次の文字が安全な形に変換されます。

元の文字 出力
" \ \" \\
改行・タブ・制御文字 \n \t \u00XX
<(</script> 対策) \u003C
U+2028 / U+2029 \u2028 / \u2029

日本語はそのままの文字で出力されるため、\uXXXX に展開されて出力が肥大することはありません。

複数の値をつなぐ

配列フィールドを区切り文字でつなぐ join / join-format でも、同じ階層に "_json": true を 書くとエスケープが有効になります。

{
  "recipes": {
    "ingredients": [{"join": ["\"%s\"", "、"], "_json": true}, ":ingredients"]
  }
}

JSON の配列として出力したいときは、区切り文字を , にして、外側の書式で [ ] を囲みます。 引用符のような構造は要素側の書式(上の例の "\"%s\"")に書いてください。

組み合わせる

":_json" の書式に差し込む値には、":_json" や join / join-format を入れ子で書けます。 入れ子で組み立てた部分は「組み立て済みの JSON」として扱われ、外側で二重にエスケープされません。 階層の深さに制限はありません。

{
  "recipes": {
    "ld": [":_json", "{\"@context\":\"https://schema.org\",\"@type\":\"Recipe\",\"name\":\"%s\",\"recipeIngredient\":[%s],\"author\":%s}",
      ":name",
      [{"join": ["\"%s\"", ","], "_json": true}, ":ingredients"],
      [":_json", "{\"@type\":\"Person\",\"name\":\"%s\"}", ":author"]]
  }
}

材料名に </script> が、名前に " が含まれていても、値だけが変換されて構造は保たれます。

{"@context":"https://schema.org","@type":"Recipe","name":"抹茶\"プリン\"","recipeIngredient":["抹茶\u003C/script>","牛乳"],"author":{"@type":"Person","name":"山田"}}

つなぐ対象のフィールドが未設定・空のときは、区切り文字だけが残ることはなく [] が出力されます。 プロパティ自体が消えることもありません。

{"@context":"https://schema.org","@type":"Recipe","name":"抹茶プリン","recipeIngredient":[],"author":{"@type":"Person","name":"山田"}}

配列全体の書式を持つ join-format も同じように入れ子にできます。

{
  "recipes": {
    "ld": [":_json", "{\"name\":\"%s\",\"keywords\":%s}",
      ":name",
      [{"join-format": ["[%s]", "\"%s\"", ":tags", ","], "_json": true}, ":tags"]]
  }
}
{"name":"抹茶プリン","keywords":["和菓子","デザート"]}

入れ子にできるのは ":_json" と、"_json": true を付けた join / join-format だけです。 ":_nillable" などそれ以外の値形式を入れ子に書くと、組み立て結果がただの文字列として エスケープされ、意図した構造になりません。

子レコードを配列にする

逆参照(":_referrers")で取得した子レコードを "_json": true の join の起点にすると、 recipeInstructions のような子要素の配列を組み立てられます。 子レコードが 0 件のときにそのプロパティだけを省くには、外側を ":_nillable" にして、 プロパティごとに ":_json" で組み立てます。

{
  "recipe": {
    "ld": [":_nillable", "{\"@context\":\"https://schema.org\",\"@type\":\"Recipe\",\"name\":\"%s\"%s}",
      [":_json", "%s", ":cf_r_title"],
      [":_json", ",\"recipeInstructions\":[%s]",
        [{"join": ["{\"@type\":\"HowToStep\",\"text\":\"%s\"}", [":rs_text"], ","], "_json": true},
         [":_referrers", "recipe_step", ":rs_recipe"]]]]
  }
}

レシピ名と手順の本文に " を含む場合の出力です(値の " がエスケープされます)。

{"@context":"https://schema.org","@type":"Recipe","name":"チョコ\"ケーキ\"","recipeInstructions":[{"@type":"HowToStep","text":"生地を\"よく\"混ぜる"},{"@type":"HowToStep","text":"180℃で焼く"}]}

手順が 0 件のときは recipeInstructions だけが省かれます。

{"@context":"https://schema.org","@type":"Recipe","name":"チョコ\"ケーキ\""}

join で組んだ配列を外側の ":_json" に差し込んだ場合は、0 件のとき <script> ごと出力されません。

注意点

  • 書式の中でパーセント記号そのものを出したいときは %% と書きます(%s が値の差し込み位置のため)。
  • ルート設定の「HTML ヘッダタグの追加」はルートごとの固定文字列なので、レコードの値を差し込めません。 値によって変わる JSON-LD はカスタムフォーマットで作り、テンプレート側で出力してください。
  • ":_json" を指定した書式では、%S / %C / %c / %n と、%.100s のように桁数(精度)を 指定した %s は使えません。エスケープ済みの値を壊して不正な JSON になるため、これらを含む書式は 値ごと出力されません(そのプロパティが JSON-LD から消えます)。文字数の制限には使えないので 注意してください。
  • 書式の中に改行やタブをそのまま書かないでください。JSON の文字列の中では使えない文字なので、 その <script type="application/ld+json"> はタグごと出力されません。読みやすさのための改行・ インデントは、文字列の外側({ や , の後など)なら問題ありません。
  • 日付のフィールドを %s で差し込むと、2014-01-01T18:30:00+09:00 のような ISO 8601 形式で出力されます。 %tF なら 2014-01-01 のような形式にできます。%s / %t のどちらで差し込んでも日本時間で出力されます。
  • ":_json" の付け忘れなどで出力内容が JSON として妥当でなくなった場合、その <script type="application/ld+json"> はタグごと出力されません。壊れた構造化データを 検索エンジンに配信しないための動作です。ページの他の内容はそのまま表示されます。 JSON-LD やその一部が出力されないときは、":_json" の指定漏れと、上記の使えない書式変換の 両方を確認してください。

パラメータルール

URL のクエリパラメータ全体に対する検証ルールです。バインド固有のルール(前述)と別に、ルート全体で受け付ける形式を定義します。

項目 説明
パラメータ名 対象のパラメータ名(最大 64 文字)
ルール 検証条件(正規表現)

条件テンプレート

同じルートでも、データの内容や URL パラメータに応じて異なるテンプレートを出し分けたい場合に使います。

タイプ 説明
クエリベース コレクションのクエリ結果でテンプレートを切替
バインドベース バインドのフィールド値でテンプレートを切替
パラメータベース URL パラメータの値でテンプレートを切替

条件に一致しない場合の既定テンプレートも指定できます。

次に読むページ