API機能を使って、画面遷移なしで、エントリーのフィルタリングをしよう

この記事は公開日より2年以上経過しているため、現在の内容と異なる可能性があります。

※この記事の内容は2021年9月に開催された a-blog cms Training Camp 2021 Autumn の中で行われた「API機能を使って、画面遷移なしで、エントリーのフィルタリングをしよう」というカスタマイズ講座の内容です。

a-blog cms ver.2.12で実装予定(2021年9月11日現在)のAPI機能をつかって画面遷移のないエントリーのフィルタリング機能を実装します。

当日のカスタマイズ講座を行った動画もありますので、よければこちらもご覧ください。


近年、Web技術の進化により、ユーザーはよりリッチな体験を求めるようになりました。  
JavaScriptを使用して非同期でデータ通信を行うことで、不要な画面遷移をさせることなくページを更新することができるからです。  
それに伴い、ヘッドレスCMSやSPAなどJavaScriptでデータ通信を行い、画面を表示させる実装が増えました。

API機能について

最初にa-blog cmsでAPI機能について以下のことを説明します。

  • API機能でできること
  • API機能を使うための初期設定
  • エンドポイント

API機能でできること

API機能ができることによって以下のような事ができるようになります。

  • ヘッドレスCMSとしてa-blog cmsを利用する
  • 画面遷移のないコンテンツのフィルタリングやソート機能の開発
  • 画面遷移のないページネーションやもっと見るボタンの実装
  • Nuxt.jsやNext.jsといった静的サイトジェネレータとの連携

他にも柔軟な使用方法が考えられます。そのため、様々なニーズに答えることができる様になるといえるでしょう。

API機能を使うための初期設定

コンフィグ > 一般設定 >API設定からAPI機能についての設定を行うことができます。


API設定をクリックしていただき、APIの有効化のチェックボックスにチェックを入れていただくとa-blog cmsのAPIを使用することができるようになります。
また、この設定画面で、APIをリクエストする側のドメインの設定や、HTTPリファラー、IPアドレスによるAPI制限の設定をすることができます。
※ 同一オリジンでAPIを使用する際には Allow-Origin の設定は不要です。


また、API-KEYも記載されていますので、メモしておきましょう。APIを使用してHTTP通信をする際に必要になります。API-KEYはコンフィグセット毎に生成されるので、異なるコンフィグセットを設定しているブログが存在する場合は注意しましょう。


エンドポイント

エンドポイントとはAPIにアクセスするためのURIのことを言います。a-blog cmsでは URLコンテキストの末尾に /api/:module_id/:module_id は任意のモジュールID)を追加したURIがエンドポイントになります。

例えば、a-blog cmsを https://acms.com というURLで使用していて、summary_index というモジュールIDを設定したモジュールの情報を取得する場合のエンドポイントは以下になります。

https://acms.com/api/summary_index/

また、a-blog cmsの特徴であるURLコンテキストを利用することができます。(ここは嬉しいポイントですよね。)

例えば、 summary_index のモジュールIDを設定したモジュールの情報のうち、カスタムフィールド「price」の値を「60000」で登録しているかつ2ページ目の情報だけを取得したい場合は以下のようなエンドポイントになります。

https://acms.com/field/price/60000/page/2/api/summary_index/

また、API機能はモジュールIDを作成することができるすべてのモジュールに対応しています。

フィルタリング機能の実装

この記事では、API機能を使った実装の1例として、エントリーのフィルタリング機能を実装していきます。フィルタリング機能を作成するにあたって、サンプルとなるデータを用意する必要があります。この記事では、下記のスーパーヒーローの特徴というデータセットを用いて実装していきます。


heroes_info.csv

heroes_info.csv

事前準備

a-blog cms でAPI機能を使用する事前準備として以下の5点の作業をご自分の a-blog cms の環境で行います。

  1. field-hero.html でエントリーのカスタムフィールドを作成する
  2. 上記の heroes_info.csvをcsvインポートの機能を用いてインポートする
  3. コンフィグ > 一般設定 > API設定からAPI機能を有効にする
  4. 異なるドメインからHTTPリクエストを送信する場合は、Allow-Origin に HTTPリクエストを送信するサイトのドメインを追加して保存する
  5. summary_hero_index というIDで Entry_Summary のモジュールIDを作成する。その際、URLコンテキストのフィールドとページ、表示設定の「リクエストに404 Not Foundとして応答する」にチェックを付ける

field-hero.html のサンプルはこちらになります。

<h3 class="acms-admin-admin-title2">ヒーロー情報設定</h3>
<table class="acms-admin-table-admin-edit">
  <tr>
    <th>身長</th>
    <td>
      <input type="number" name="height" value="{height}" class="acms-admin-form-width-mini" />
      <input type="hidden" name="field[]" value="height" />
    </td>
  </tr>
  <tr>
    <th>体重</th>
    <td>
      <input type="number" name="weight" value="{weight}" class="acms-admin-form-width-mini" />
      <input type="hidden" name="field[]" value="weight" />
    </td>
  </tr>
  <tr>
    <th>性別</th>
    <td>
      <div class="acms-admin-form-radio">
        <input type="radio" name="gender" value="Male" {gender:checked#Male} id="input-radio-gender-Male" />
        <label for="input-radio-gender-Male">
          <i class="acms-admin-ico-radio"></i>男性</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="gender" value="Female" {gender:checked#Female} id="input-radio-gender-Female" />
        <label for="input-radio-gender-Female">
          <i class="acms-admin-ico-radio"></i>女性</label>
      </div>
      <input type="hidden" name="field[]" value="gender" />
    </td>
  </tr>
  <tr>
    <th>瞳の色</th>
    <td>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="yellow" {eye_color:checked#yellow} id="input-radio-eye_color-yellow" />
        <label for="input-radio-eye_color-yellow">
          <i class="acms-admin-ico-radio"></i>yellow</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="blue" {eye_color:checked#blue} id="input-radio-eye_color-blue" />
        <label for="input-radio-eye_color-blue">
          <i class="acms-admin-ico-radio"></i>blue</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="green" {eye_color:checked#green} id="input-radio-eye_color-green" />
        <label for="input-radio-eye_color-green">
          <i class="acms-admin-ico-radio"></i>green</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="brown" {eye_color:checked#brown} id="input-radio-eye_color-brown" />
        <label for="input-radio-eye_color-brown">
          <i class="acms-admin-ico-radio"></i>brown</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="red" {eye_color:checked#red} id="input-radio-eye_color-red" />
        <label for="input-radio-eye_color-red">
          <i class="acms-admin-ico-radio"></i>red</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="violet" {eye_color:checked#violet} id="input-radio-eye_color-violet" />
        <label for="input-radio-eye_color-violet">
          <i class="acms-admin-ico-radio"></i>violet</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="white" {eye_color:checked#white} id="input-radio-eye_color-white" />
        <label for="input-radio-eye_color-white">
          <i class="acms-admin-ico-radio"></i>white</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="purple" {eye_color:checked#purple} id="input-radio-eye_color-purple" />
        <label for="input-radio-eye_color-purple">
          <i class="acms-admin-ico-radio"></i>purple</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="black" {eye_color:checked#black} id="input-radio-eye_color-black" />
        <label for="input-radio-eye_color-black">
          <i class="acms-admin-ico-radio"></i>black</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="grey" {eye_color:checked#grey} id="input-radio-eye_color-grey" />
        <label for="input-radio-eye_color-grey">
          <i class="acms-admin-ico-radio"></i>grey</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="silver" {eye_color:checked#silver} id="input-radio-eye_color-silver" />
        <label for="input-radio-eye_color-silver">
          <i class="acms-admin-ico-radio"></i>silver</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="gold" {eye_color:checked#gold} id="input-radio-eye_color-gold" />
        <label for="input-radio-eye_color-gold">
          <i class="acms-admin-ico-radio"></i>gold</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="hazel" {eye_color:checked#hazel} id="input-radio-eye_color-hazel" />
        <label for="input-radio-eye_color-hazel">
          <i class="acms-admin-ico-radio"></i>hazel</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="indigo" {eye_color:checked#indigo} id="input-radio-eye_color-indigo" />
        <label for="input-radio-eye_color-indigo">
          <i class="acms-admin-ico-radio"></i>indigo</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="amber" {eye_color:checked#amber} id="input-radio-eye_color-amber" />
        <label for="input-radio-eye_color-amber">
          <i class="acms-admin-ico-radio"></i>amber</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="eye_color" value="bown" {eye_color:checked#bown} id="input-radio-eye_color-bown" />
        <label for="input-radio-eye_color-bown">
          <i class="acms-admin-ico-radio"></i>bown</label>
      </div>
      <input type="hidden" name="field[]" value="eye_color" />
    </td>
  </tr>
  <tr>
    <th>種族</th>
    <td>
      <select name="race">
        <option value=""></option>
        <option value="Alien" {race:selected#Alien}>Alien</option>
        <option value="Alpha" {race:selected#Alpha}>Alpha</option>
        <option value="Amazon" {race:selected#Amazon}>Amazon</option>
        <option value="Android" {race:selected#Android}>Android</option>
        <option value="Animal" {race:selected#Animal}>Animal</option>
        <option value="Asgardian" {race:selected#Asgardian}>Asgardian</option>
        <option value="Atlantean" {race:selected#Atlantean}>Atlantean</option>
        <option value="Bizarro" {race:selected#Bizarro}>Bizarro</option>
        <option value="Bolovaxian" {race:selected#Bolovaxian}>Bolovaxian</option>
        <option value="Clone" {race:selected#Clone}>Clone</option>
        <option value="Cosmic Entity" {race:selected#Cosmic Entity}>Cosmic Entity</option>
        <option value="Cyborg" {race:selected#Cyborg}>Cyborg</option>
        <option value="Czarnian" {race:selected#Czarnian}>Czarnian</option>
        <option value="Dathomirian Zabrak" {race:selected#Dathomirian Zabrak}>Dathomirian Zabrak</option>
        <option value="Demon" {race:selected#Demon}>Demon</option>
        <option value="Eternal" {race:selected#Eternal}>Eternal</option>
        <option value="Flora Colossus" {race:selected#Flora Colossus}>Flora Colossus</option>
        <option value="Frost Giant" {race:selected#Frost Giant}>Frost Giant</option>
        <option value="God" {race:selected#God }>God</option>
        <option value="Gorilla" {race:selected#Gorilla}>Gorilla</option>
        <option value="Gungan" {race:selected#Gungan}>Gungan</option>
        <option value="Human" {race:selected#Human}>Human</option>
        <option value="HumanKree" {race:selected#HumanKree}>HumanKree</option>
        <option value="HumanSpattoi" {race:selected#HumanSpattoi}>HumanSpattoi</option>
        <option value="HumanVulcan" {race:selected#HumanVulcan}>HumanVulcan</option>
        <option value="HumanVuldarian" {race:selected#HumanVuldarian}>HumanVuldarian</option>
        <option value="Icthyo Sapien" {race:selected#Icthyo Sapien}>Icthyo Sapien</option>
        <option value="inhuman" {race:selected#inhuman}>inhuman</option>
        <option value="Kaiju" {race:selected#Kaiju}>Kaiju</option>
        <option value="Kakarantharaian" {race:selected#Kakarantharaian}>Kakarantharaian</option>
        <option value="Korugaran" {race:selected#Korugaran}>Korugaran</option>
        <option value="Kryptonian" {race:selected#Korugaran}>Kryptonian</option>
        <option value="Luphomoid" {race:selected#Luphomoid}>Luphomoid</option>
        <option value="Maiar" {race:selected#Maiar}>Maiar</option>
        <option value="Martian" {race:selected#Martian}>Martian</option>
        <option value="Metahuman" {race:selected#Metahuman}>Metahuman</option>
        <option value="Mutant" {race:selected#Mutant}>Mutant</option>
        <option value="New Got" {race:selected#New Got}>New Got</option>
        <option value="Neyaphem" {race:selected#Neyaphem}>Neyaphem</option>
        <option value="Parademon" {race:selected#Parademon}>Parademon</option>
        <option value="Planet" {race:selected#Planet}>Planet</option>
        <option value="Rodian" {race:selected#Rodian}>Rodian</option>
        <option value="Saiyan" {race:selected#Saiyan}>Saiyan</option>
        <option value="Spartoi" {race:selected#Spartoi}>Spartoi</option>
        <option value="Strontian" {race:selected#Strontian}>Strontian</option>
        <option value="Symbiote" {race:selected#Symbiote}>Symbiote</option>
        <option value="Talokite" {race:selected#Talokite}>Talokite</option>
        <option value="Tamaranean" {race:selected#Tamaranean}>Tamaranean</option>
        <option value="Tamaranean" {race:selected#Tamaranean}>Ungaran</option>
        <option value="Vampire" {race:selected#Vampire}>Vampire</option>
        <option value="Xenomorph XX121" {race:selected#Xenomorph XX121}>Xenomorph XX121</option>
        <option value="Yautja" {race:selected#Yautja}>Yautja</option>
        <option value="Yoda&#x27;s species" {race:selected#Yoda&#x27;s species}>Yoda&#x27;s species</option>
        <option value="ZenWhoberian" {race:selected#ZenWhoberian}>ZenWhoberian</option>
        <option value="Zombie" {race:selected#Zombie}>Zombie</option>
      </select>
      <input type="hidden" name="field[]" value="race" />
    </td>
  </tr>
  <tr>
    <th>髪の毛の色</th>
    <td>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="No Hair" {hair_color:checked#No Hair} id="input-checkbox-hair_color-No Hair" />
        <label for="input-checkbox-hair_color-No Hair">
          <i class="acms-admin-ico-checkbox"></i>No Hair</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Black" {hair_color:checked#Black} id="input-checkbox-hair_color-Black" />
        <label for="input-checkbox-hair_color-Black">
          <i class="acms-admin-ico-checkbox"></i>Black</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Blond" {hair_color:checked#Blond} id="input-checkbox-hair_color-Blond" />
        <label for="input-checkbox-hair_color-Blond">
          <i class="acms-admin-ico-checkbox"></i>Blond</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Brown" {hair_color:checked#Brown} id="input-checkbox-hair_color-Brown" />
        <label for="input-checkbox-hair_color-Brown">
          <i class="acms-admin-ico-checkbox"></i>Brown</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="White" {hair_color:checked#White} id="input-checkbox-hair_color-White" />
        <label for="input-checkbox-hair_color-White">
          <i class="acms-admin-ico-checkbox"></i>White</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Purple" {hair_color:checked#Purple} id="input-checkbox-hair_color-Purple" />
        <label for="input-checkbox-hair_color-Purple">
          <i class="acms-admin-ico-checkbox"></i>Purple</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Orange" {hair_color:checked#Orange} id="input-checkbox-hair_color-Orange" />
        <label for="input-checkbox-hair_color-Orange">
          <i class="acms-admin-ico-checkbox"></i>Orange</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Pink" {hair_color:checked#Pink} id="input-checkbox-hair_color-Pink" />
        <label for="input-checkbox-hair_color-Pink">
          <i class="acms-admin-ico-checkbox"></i>Pink</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Red" {hair_color:checked#Red} id="input-checkbox-hair_color-Red" />
        <label for="input-checkbox-hair_color-Red">
          <i class="acms-admin-ico-checkbox"></i>Red</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Auburn" {hair_color:checked#Auburn} id="input-checkbox-hair_color-Auburn" />
        <label for="input-checkbox-hair_color-Auburn">
          <i class="acms-admin-ico-checkbox"></i>Auburn</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Strawberry Blond" {hair_color:checked#Strawberry Blond} id="input-checkbox-hair_color-Strawberry Blond" />
        <label for="input-checkbox-hair_color-Strawberry Blond">
          <i class="acms-admin-ico-checkbox"></i>Strawberry Blond</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Blue" {hair_color:checked#Blue} id="input-checkbox-hair_color-Blue" />
        <label for="input-checkbox-hair_color-Blue">
          <i class="acms-admin-ico-checkbox"></i>Blue</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Green" {hair_color:checked#Green} id="input-checkbox-hair_color-Green" />
        <label for="input-checkbox-hair_color-Green">
          <i class="acms-admin-ico-checkbox"></i>Green</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Magenta" {hair_color:checked#Magenta} id="input-checkbox-hair_color-Magenta" />
        <label for="input-checkbox-hair_color-Magenta">
          <i class="acms-admin-ico-checkbox"></i>Magenta</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Silver" {hair_color:checked#Silver} id="input-checkbox-hair_color-Silver" />
        <label for="input-checkbox-hair_color-Silver">
          <i class="acms-admin-ico-checkbox"></i>Silver</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Grey" {hair_color:checked#Grey} id="input-checkbox-hair_color-Grey" />
        <label for="input-checkbox-hair_color-Grey">
          <i class="acms-admin-ico-checkbox"></i>Grey</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Yellow" {hair_color:checked#Yellow} id="input-checkbox-hair_color-Yellow" />
        <label for="input-checkbox-hair_color-Yellow">
          <i class="acms-admin-ico-checkbox"></i>Yellow</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Gold" {hair_color:checked#Gold} id="input-checkbox-hair_color-Gold" />
        <label for="input-checkbox-hair_color-Gold">
          <i class="acms-admin-ico-checkbox"></i>Gold</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="hair_color[]" value="Indigo" {hair_color:checked#Indigo} id="input-checkbox-hair_color-Indigo" />
        <label for="input-checkbox-hair_color-Indigo">
          <i class="acms-admin-ico-checkbox"></i>Indigo</label>
      </div>
      <input type="hidden" name="field[]" value="hair_color" />
    </td>
  </tr>
  <tr>
    <th>制作会社</th>
    <td>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Marvel Comics" {publisher:checked#Marvel Comics} id="input-radio-publisher-Marvel Comics" />
        <label for="input-radio-publisher-Marvel Comics">
          <i class="acms-admin-ico-radio"></i>Marvel Comics</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Dark Horse Comics" {publisher:checked#Dark Horse Comics} id="input-radio-publisher-Dark Horse Comics" />
        <label for="input-radio-publisher-Dark Horse Comics">
          <i class="acms-admin-ico-radio"></i>Dark Horse Comics</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="DC Comics" {publisher:checked#DC Comics} id="input-radio-publisher-DC Comics" />
        <label for="input-radio-publisher-DC Comics">
          <i class="acms-admin-ico-radio"></i>DC Comics</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="NBC  Heroes" {publisher:checked#NBC Heroes} id="input-radio-publisher-NBC  Heroes" />
        <label for="input-radio-publisher-NBC  Heroes">
          <i class="acms-admin-ico-radio"></i>NBC Heroes</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Wildstorm" {publisher:checked#Wildstorm} id="input-radio-publisher-Wildstorm" />
        <label for="input-radio-publisher-Wildstorm">
          <i class="acms-admin-ico-radio"></i>Wildstorm</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Image Comics" {publisher:checked#Image Comics} id="input-radio-publisher-Image Comics" />
        <label for="input-radio-publisher-Image Comics">
          <i class="acms-admin-ico-radio"></i>Image Comics</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Icon Comics" {publisher:checked#Icon Comics} id="input-radio-publisher-Icon Comics" />
        <label for="input-radio-publisher-Icon Comics">
          <i class="acms-admin-ico-radio"></i>Icon Comics</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="SyFy" {publisher:checked#SyFy} id="input-radio-publisher-SyFy" />
        <label for="input-radio-publisher-SyFy">
          <i class="acms-admin-ico-radio"></i>SyFy</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="HannaBarbera" {publisher:checked#HannaBarbera} id="input-radio-publisher-HannaBarbera" />
        <label for="input-radio-publisher-HannaBarbera">
          <i class="acms-admin-ico-radio"></i>HannaBarbera</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="George Lucas" {publisher:checked#George Lucas} id="input-radio-publisher-George Lucas" />
        <label for="input-radio-publisher-George Lucas">
          <i class="acms-admin-ico-radio"></i>George Lucas</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Team Epic TV" {publisher:checked#Team Epic TV} id="input-radio-publisher-Team Epic TV" />
        <label for="input-radio-publisher-Team Epic TV">
          <i class="acms-admin-ico-radio"></i>Team Epic TV</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="South Park" {publisher:checked#South Park} id="input-radio-publisher-South Park" />
        <label for="input-radio-publisher-South Park">
          <i class="acms-admin-ico-radio"></i>South Park</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="HarperCollins" {publisher:checked#HarperCollins} id="input-radio-publisher-HarperCollins" />
        <label for="input-radio-publisher-HarperCollins">
          <i class="acms-admin-ico-radio"></i>HarperCollins</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="ABC Studios" {publisher:checked#ABC Studios} id="input-radio-publisher-ABC Studios" />
        <label for="input-radio-publisher-ABC Studios">
          <i class="acms-admin-ico-radio"></i>ABC Studios</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Universal Studios" {publisher:checked#Universal Studios} id="input-radio-publisher-Universal Studios" />
        <label for="input-radio-publisher-Universal Studios">
          <i class="acms-admin-ico-radio"></i>Universal Studios</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="IDW Publishing" {publisher:checked#IDW Publishing} id="input-radio-publisher-IDW Publishing" />
        <label for="input-radio-publisher-IDW Publishing">
          <i class="acms-admin-ico-radio"></i>IDW Publishing</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Shueisha" {publisher:checked#Shueisha} id="input-radio-publisher-Shueisha" />
        <label for="input-radio-publisher-Shueisha">
          <i class="acms-admin-ico-radio"></i>Shueisha</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Sony Pictures" {publisher:checked#Sony Pictures} id="input-radio-publisher-Sony Pictures" />
        <label for="input-radio-publisher-Sony Pictures">
          <i class="acms-admin-ico-radio"></i>Sony Pictures</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="J. K. Rowling" {publisher:checked#J. K. Rowling} id="input-radio-publisher-J. K. Rowling" />
        <label for="input-radio-publisher-J. K. Rowling">
          <i class="acms-admin-ico-radio"></i>J. K. Rowling</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Titan Books" {publisher:checked#Titan Books} id="input-radio-publisher-Titan Books" />
        <label for="input-radio-publisher-Titan Books">
          <i class="acms-admin-ico-radio"></i>Titan Books</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Rebellion" {publisher:checked#Rebellion} id="input-radio-publisher-Rebellion" />
        <label for="input-radio-publisher-Rebellion">
          <i class="acms-admin-ico-radio"></i>Rebellion</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="Microsoft" {publisher:checked#Microsoft} id="input-radio-publisher-Microsoft" />
        <label for="input-radio-publisher-Microsoft">
          <i class="acms-admin-ico-radio"></i>Microsoft</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="publisher" value="J. R. R. Tolkien" {publisher:checked#J. R. R. Tolkien} id="input-radio-publisher-J. R. R. Tolkien" />
        <label for="input-radio-publisher-J. R. R. Tolkien">
          <i class="acms-admin-ico-radio"></i>J. R. R. Tolkien</label>
      </div>
      <input type="hidden" name="field[]" value="publisher" />
    </td>
  </tr>
  <tr>
    <th>肌の色</th>
    <td>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="blue" {skin_color:checked#blue} id="input-checkbox-skin_color-blue" />
        <label for="input-checkbox-skin_color-blue">
          <i class="acms-admin-ico-checkbox"></i>blue</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="red" {skin_color:checked#red} id="input-checkbox-skin_color-red" />
        <label for="input-checkbox-skin_color-red">
          <i class="acms-admin-ico-checkbox"></i>red</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="black" {skin_color:checked#black} id="input-checkbox-skin_color-black" />
        <label for="input-checkbox-skin_color-black">
          <i class="acms-admin-ico-checkbox"></i>black</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="grey" {skin_color:checked#grey} id="input-checkbox-skin_color-grey" />
        <label for="input-checkbox-skin_color-grey">
          <i class="acms-admin-ico-checkbox"></i>grey</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="gold" {skin_color:checked#gold} id="input-checkbox-skin_color-gold" />
        <label for="input-checkbox-skin_color-gold">
          <i class="acms-admin-ico-checkbox"></i>gold</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="green" {skin_color:checked#green} id="input-checkbox-skin_color-green" />
        <label for="input-checkbox-skin_color-green">
          <i class="acms-admin-ico-checkbox"></i>green</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="white" {skin_color:checked#white} id="input-checkbox-skin_color-white" />
        <label for="input-checkbox-skin_color-white">
          <i class="acms-admin-ico-checkbox"></i>white</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="pink" {skin_color:checked#pink} id="input-checkbox-skin_color-pink" />
        <label for="input-checkbox-skin_color-pink">
          <i class="acms-admin-ico-checkbox"></i>pink</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="silver" {skin_color:checked#silver} id="input-checkbox-skin_color-silver" />
        <label for="input-checkbox-skin_color-silver">
          <i class="acms-admin-ico-checkbox"></i>silver</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="yellow" {skin_color:checked#yellow} id="input-checkbox-skin_color-yellow" />
        <label for="input-checkbox-skin_color-yellow">
          <i class="acms-admin-ico-checkbox"></i>yellow</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="purple" {skin_color:checked#purple} id="input-checkbox-skin_color-purple" />
        <label for="input-checkbox-skin_color-purple">
          <i class="acms-admin-ico-checkbox"></i>purple</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="Orange" {skin_color:checked#Orange} id="input-checkbox-skin_color-Orange" />
        <label for="input-checkbox-skin_color-Orange">
          <i class="acms-admin-ico-checkbox"></i>Orange</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="gray" {skin_color:checked#gray} id="input-checkbox-skin_color-gray" />
        <label for="input-checkbox-skin_color-gray">
          <i class="acms-admin-ico-checkbox"></i>gray</label>
      </div>
      <div class="acms-admin-form-checkbox">
        <input type="checkbox" name="skin_color[]" value="bluewhite" {skin_color:checked#bluewhite} id="input-checkbox-skin_color-bluewhite" />
        <label for="input-checkbox-skin_color-bluewhite">
          <i class="acms-admin-ico-checkbox"></i>bluewhite</label>
      </div>
      <input type="hidden" name="field[]" value="skin_color" />
    </td>
  </tr>
  <tr>
    <th>全体的な位置付け</th>
    <td>
      <div class="acms-admin-form-radio">
        <input type="radio" name="alignment" value="good" {alignment:checked#good} id="input-radio-alignment-good" />
        <label for="input-radio-alignment-good">
          <i class="acms-admin-ico-radio"></i>good</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="alignment" value="bad" {alignment:checked#bad} id="input-radio-alignment-bad" />
        <label for="input-radio-alignment-bad">
          <i class="acms-admin-ico-radio"></i>bad</label>
      </div>
      <div class="acms-admin-form-radio">
        <input type="radio" name="alignment" value="neutral" {alignment:checked#neutral} id="input-radio-alignment-neutral" />
        <label for="input-radio-alignment-neutral">
          <i class="acms-admin-ico-radio"></i>neutral</label>
      </div>
      <input type="hidden" name="field[]" value="alignment" />
    </td>
  </tr>
</table>

処理の流れ

実際に、JavaScriptを書いて実装していく前に、処理の流れを確認します。
この記事ではReactというJavaScriptライブラリを使用して実装していきますが、他のJavaScriptフレームワークやライブラリ、例えば、Vue.js などを利用しても、処理の流れは共通しているためとても大切な部分になってくるからです。


1. conditions というglobal state を用意します。(global stateとは異なるコンポーネント間で共有できるデータのことになります)

conditions には以下のようにデータをもたせます。初期値はからのオブジェクトとします。 カスタムフィールドの field の値を key にして、valuefieldkeywordskey を持つオブジェクトを持つようにします。

    {
      gender: {field: 'gender', keywords: ['Male']},
      eye_color: {field: 'eye_color', keywords: ['yellow', 'green']},
    }

2. AccordionItemがクリックされると、クリックされたAccordionItemに紐付いている fieldkeywordconditions に追加されます。(conditions にすでに 同じfieldkey にもつオブジェクトが追加されている場合は keyword のみ追加)

例えば、AccordionItemに紐付いている `field` が `hair_color` 、 `keyword` が `Brown` だった場合、 `conditions` は下記のようになります。
    {
      gender: {field: 'gender', keywords: ['Male']},
      eye_color: {field: 'eye_color', keywords: ['yellow', 'green']},
      hair_color: {field: 'hair_color', keywords: ['Brown']},
    }

3. conditions が変更されたら、conditions の情報を元に エンドポイントを作成し、HTTPリクエストを行い、取得したデータを元に、HeroCard を描画します。

エンドポイントは上記の例の場合、 `https://example.com/field/gender/Male/_``and``_/eye_color/yellow/green/_``and``_/hair_color/Brown/api/:module_id/` になります。このURIに対してHTTPリクエストを行い、返ってきたレスポンスデータを画面に反映します。

4. また、クリックされたAccordionItemに紐付いている fieldkeyword がフィルタリングされている値であった場合には、その fieldkey にもつオブジェクトの keywords から keyword を削除します。

↓ `field` が `eye_color`、 `keyword` が `yellow` の `AccordionItem` をクリックした場合の `conditions`
    {
      gender: {field: 'gender', keywords: ['Male']},
      eye_color: {field: 'eye_color', keywords: ['green']},
      hair_color: {field: 'hair_color', keywords: ['Brown']},
    }

   削除する fieldkeywords が1つであった場合には オブジェクトの key 毎削除します。
   ↓fieldgenderkeywordMaleAccordionItem をクリックした場合の conditions

   {
      eye_color: {field: 'eye_color', keywords: ['yellow', 'green']},
      hair_color: {field: 'hair_color', keywords: ['Brown']},
   }

実装

処理の流れは理解できましたでしょうか?処理の流れが理解できたら、実際にソースコードを書いて実装していきます。 また今回はソースコードが長くなってしまうので、githubにリポジトリを作成しました。詳細はこちらのリポジトリをみていただけると幸いです。

またこちらのリポジトリの README.md には簡単にこの講座用のテスト環境を試すことができる手順が記載してありますので、お時間ある方はぜひお試しください。

以下の手順で再現可能です。

git clone https://github.com/appleple/api-lesson-in-a-blogcms-training-camp-2021-autumn.git
npm ci
touch .env

.env ファイルに下記をコピペ

API_KEY = 任意のAPI-KEY
npm run build
npm start

今回の講座ではいくつかの外部ライブラリを使用しています。

  • @material-ui/core (スタイリング簡易化のため)
  • @material-ui/icons (スタイリング簡易化のため)
  • axios (HTTPリクエストを送信するため)
  • react react-dom (実装のため)

以下のコマンドでインストールできます。

npm i @material-ui/core @material-ui/icons axios react react-dom

HTTPリクエストを送信する

それでは、ソースコードをポイントに絞って解説します。 まず、a-blog cms に対してHTTPリクエストを送信するソースコードをみてみましょう。

今回は axios というライブラリを使用しています。URLの末尾に /api/:module_id/ をつけることでa-blog cmsのAPIに通信できます。

// src/common.js

import axios from 'axios';

// eslint-disable-next-line import/prefer-default-export
export const get = (endpoint) => axios.get(`http://6zgkhz8h.ablogcms.io/field${endpoint}/api/summary_hero_index/`, {
  headers: { 'X-API-KEY': process.env.API_KEY },
});
ハードコーディングを回避する

上記のような API-KEY を使用するようなソースコードではAPI-KEYを直接ソースコードに書くことは望ましくありません。本番環境と開発環境では API-KEY が異なることがあったり、Githubなどでソースコードを公開する時に、API-KEYは知られたくない情報だからです。

こういった本来プログラムの中に記述すべきでない情報を、直接ソースコードに書くことをハードコーディング と呼びます。

このようなハードコーディングを解決したい場合に有効なのが環境変数です。環境変数を利用することで、API-KEY等を直接ソースコードに書かなくて済みます。

今回は、環境変数を手軽に使用するための npm ライブラリ webpack-dotenv というライブラリを利用して 環境変数を使用します。

まず、 webpack-dotenv を npm でインストールします。

npm i -D webpack-dotenv

使い方は簡単で webpackの設定ファイルに下記のコードを追加します。

// webpack.prod.js

const Dotenv = require('dotenv-webpack'); // ←ここを追加

module.exports = {
  // other settings...
  plugins: [
      // other plugins...
      new Dotenv(), // ←ここを追加
  ],
};

webpackの設定ができたら環境変数を定義するファイルをルートディレクトリ(package.json がある場所)に作成します。コマンドで作成する方はこちら↓

touch .env

作成できたら .env ファイルに環境変数を定義します。

// .env

API_KEY = 任意のAPI-KEY

これで設定は完了です。 プログラム内では process.env..envファイルで定義した環境変数の左辺 と記述することで環境変数を呼び出すことができます。

この場合だと、下記のようになります。

// env-test.js

console.log(process.env.API_KEY); // output: 任意のAPI-KEY

これで、API-KEYをソースコードに直接書かなくても良くなりました!! webpack-dotenv を使用する際の注意点として、必ず .env ファイルを .gitignore するのを忘れないことです。これを忘れてしまうと、Githubにプッシュした際にリポジトリに登録されてしまい、環境変数の中身がネット上に公開されてしまい、せっかく環境変数を利用した意味がなくなってしまいます。

ただこれだと、CI/CDなどを使用して、リモートリポジトリ上でプログラムを動作させたい場合に困ってしまう。そういった場合はCI/CDには環境変数を設定できる機能が備わっている場合が多いので、リモートリポジトリ上ではそちらを使用してください。

global state の定義

次にReactの標準機能である Context という機能を使用して global state を定義します。ここで conditionsconditions を変更する関数 setConditions を定義しています。

// src/store.js

import React, { useState, createContext, useContext } from 'react';

const ConditionsContext = createContext({
  conditions: {},
  setConditions: () => {},
});

const ConditionsProvider = ({ children }) => {
  const [conditions, setConditions] = useState({});

  return (
    <ConditionsContext.Provider value={{ conditions, setConditions }}>
      {children}
    </ConditionsContext.Provider>
  );
};

const useConditions = () => useContext(ConditionsContext);

export { ConditionsProvider, useConditions };

これで、ConditionsProvider コンポーネントでラップしたすべてのコンポーネントで conditionssetConditions を使用することができるようになりました。

フィルタリング条件の入力

global state が定義できたら、実際にフィルタリング条件の入力をおこなう src/components/AccordionItem.js をみていきましょう。

// src/components/AccordionItem.js

// ...省略

const AccordionItem = ({ field, keyword }) => {
  const classes = useStyles();
  const { conditions, setConditions } = useConditions();

  // フィルターされたかどうか
  const [filtered, setFiltered] = useState(false);

  const handleClick = () => {
    const keywords = conditions[field] ? conditions[field].keywords : [];

    if (!filtered) {
      const added = { [field]: { field, keywords: [...keywords, keyword] } };
      setConditions({ ...conditions, ...added });
    } else if (conditions[field].keywords.length === 1) {
      const { [field]: _, ...rest } = conditions;
      // delete conditions[field];
      setConditions(rest);
    } else {
      const deleted = {
        [field]: { field, keywords: keywords.filter((k) => k !== keyword) },
      };
      setConditions({ ...conditions, ...deleted });
    }
    setFiltered(!filtered);
  };

  return (
    <ListItem
      button
      className={`${classes.nested} ${filtered ? classes.isFillterd : ''}`}
      onClick={handleClick}
    >
      <ListItemText primary={keyword} />
    </ListItem>
  );
};

export default AccordionItem;

簡単に解説すると、このコンポーネントは fieldkeyword という情報を持っています。例えば、field'gender'keyword'Male' だとします。

そして、10行目の const [filtered, setFiltered] = useState(false); というところで、このコンポーネントが持っている fieldkeyword はフィルター済みかどうかという状態を定義しています。

そして、下記の handleClick という関数内で AccordionItem コンポーネントがクリックされたときの動作を定義しています。

const handleClick = () => {
  const keywords = conditions[field] ? conditions[field].keywords : [];

  if (!filtered) {
    const added = { [field]: { field, keywords: [...keywords, keyword] } };
    setConditions({ ...conditions, ...added });
  } else if (conditions[field].keywords.length === 1) {
    const { [field]: _, ...rest } = conditions;
    // delete conditions[field];
    setConditions(rest);
  } else {
    const deleted = {
      [field]: { field, keywords: keywords.filter((k) => k !== keyword) },
    };
    setConditions({ ...conditions, ...deleted });
  }
  setFiltered(!filtered);
};

フィルター済みでなければ、AccordionItem が持つ fieldkey に指定し、value{ field: field, keywords: [ keyword ] } で指定したプロパティを conditions に追加しています。

フィルター済みである、かつ keywords の配列の要素の数が1つであれば、 オブジェクトの key 毎削除しています。

フィルター済みである、かつ keywords の配列の要素の数が1つでなければ、AccordionItem が持つ fieldkey にもつオブジェクトの keywords から keyword を削除しています。

ヒーローの特徴の情報を表示する

次に、実際にヒーローの特徴の情報を表示するコンポーネントをみていきます。src/components/HeroesIndex.js です。

// src/components/HeroesIndex.js

const HeroesIndex = () => {
  const { conditions } = useConditions();
  const [heroes, setHeroes] = useState([]);
  const [isShowMore, setIsShowMore] = useState(true);
  const [isError, setIsError] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const endpoint = Object.values(conditions)
      .map((obj) => `/${obj.field}/${obj.keywords.join('/')}`)
      .join('/_and_');

    setIsLoaded(false);
    get(endpoint).then(
      ({ data: { 'entry:loop': result, lastPage } }) => {
        setIsError(false);
        setIsLoaded(true);
        setHeroes(Array.isArray(result) ? result : [result]);
        setIsShowMore(!!lastPage);
      },
      () => {
        setIsLoaded(true);
        setIsError(true);
      },
    );
  }, [conditions]);

  if (isError) {
    return (
      <div style={style}>
        <div>該当するヒーローは存在しません。</div>
      </div>
    );
  }
  if (!isLoaded) {
    return (
      <div style={progress}>
        <CircularProgress size="80px" />
      </div>
    );
  }
  return (
    <div style={style}>
      {heroes.length > 0
        && heroes.map((hero) => <HeroCard hero={hero} key={hero.eid} />)}
      {isShowMore ? (
        <ShowMoreButton
          setHeroes={setHeroes}
          setIsShowMore={setIsShowMore}
          text="もっと見る"
        />
      ) : (
        ''
      )}
    </div>
  );
};

export default HeroesIndex;

簡単に解説します。 まずは、エンドポイントの作成です。↓の部分でエンドポイントを作成しています。

const endpoint = Object.values(conditions)
      .map((obj) => `/${obj.field}/${obj.keywords.join('/')}`)
      .join('/_and_');

次に、実際にAPIに対してHTTPリクエストを送信するところです。Reactの標準機能で useEffect の第2引数に配列の形で、値を書いておくと、その値が更新されるたびに useEffect 関数を実行してくれます。

この場合は conditions が更新されるごとにHTTPリクエストを送信したいので、[conditions] と記述しています。

useEffect(() => {
    // 省略...

    get(endpoint).then(
      ({ data: { 'entry:loop': result, lastPage } }) => {
        setIsError(false);
        setIsLoaded(true);
        setHeroes(Array.isArray(result) ? result : [result]);
        setIsShowMore(!!lastPage);
      },
      () => {
        setIsLoaded(true);
        setIsError(true);
      },
    );
  }, [conditions]);

a-blog cms の APIにHTTPリクエストを送ると↓のようなレスポンスが返ってきますが、今回必要なのは entry:looplastPage だけなので、そのデータだけ JavaScript の機能である分割代入を使って抜き出しています。

// Entry_SummaryのAPIをGETしたときのレスポンスの例

moduleField":[],
    "noimage":[
    {
    "noImgX": 100,
    "noImgY": 100
    },
    省略...
    "entry:loop":[
    {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-751.html", "title": "Wonder Woman",…},
    {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-726.html", "title": "Violator",…},
    {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-681.html", "title": "T800",…},
    省略...
    ],
    "unit:loop":[
    [],
    [],
    []
    ],
    "page:loop":[
    {
    "page": 1,
    "pageCurAttr": " class=\"cur\"",
    "glue":[]
    },
    {"page": 2, "glue":[], "link#front":{"url": "http://6zgkhz8h.ablogcms.io/page/2/"…},
    {"page": 3, "glue":[], "link#front":{"url": "http://6zgkhz8h.ablogcms.io/page/3/"…},
    {
    "page": 4,
    "link#front":{
    "url": "http://6zgkhz8h.ablogcms.io/page/4/"
    },
    "link#rear":[]
    }
    ],
    "forwardLink":{
    "url": "http://6zgkhz8h.ablogcms.io/page/2/",
    "forwardNum": 10,
    "forwardPage": 2
    },
    "indexUrl": "http://6zgkhz8h.ablogcms.io/hero/",
    "indexBlogName": "a-blog cms",
    "blogName": "a-blog cms",
    "blogCode": "",
    "blogUrl": "http://6zgkhz8h.ablogcms.io/",
    "indexCategoryName": "ヒーロー",
    "categoryName": "ヒーロー",
    "categoryCode": "hero",
    "categoryUrl": "http://6zgkhz8h.ablogcms.io/hero/",
    "itemsAmount": 734,
    "itemsFrom": 1,
    "itemsTo": 10,
    "lastPageUrl": "http://6zgkhz8h.ablogcms.io/page/74/",
    "lastPage": 74
}

上記の JSON のデータから、entry:loop のデータを result という変数に入れて、HeroesIndex コンポーネントで定義している heroes という stateresult で更新します。

このとき、heroes の中身は↓のようになっています。

[
  {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-751.html", "title": "Wonder Woman",…},
  {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-726.html", "title": "Violator",…},
  {"permalink": "http://6zgkhz8h.ablogcms.io/hero/entry-681.html", "title": "T800",…},
  省略...
]

そして↓で下記のコードで heroes を元に HeroCard をレンダリングしています。

{heroes.length > 0
        && heroes.map((hero) => <HeroCard hero={hero} key={hero.eid} />)}

また、ここが a-blog cms の便利なところなのですが、管理画面 > モジュールIDの設定画面 > 表示設定で “エントリーがない場合のHTTP応答” の “リクエストに404 Not Foundとして応答する” にチェックをつけると、対象となるエントリー、つまり entry:loop の値がない場合には404のステータスコードを返してくれるところです。


この仕様があることで JavaScriptでエラーハンドリングをすることができます。実際に講座のサンプルコード内でも以下のようにエラーハンドリングを行っています。

get(endpoint).then(
      ({ data: { 'entry:loop': result, lastPage } }) => {
        setIsError(false);
        setIsLoaded(true);
        setHeroes(Array.isArray(result) ? result : [result]);
        setIsShowMore(!!lastPage);
      },
      // ここからエラー処理
      () => {
        setIsLoaded(true);
        setIsError(true);
      },
    );

エラーだった場合は以下のように ”該当するヒーローは存在しません。” という文を表示するようにしています。

if (isError) {
  return (
    <div style={style}>
      <div>該当するヒーローは存在しません。</div>
    </div>
  );
}

お疲れさまでした。以上で今回のカスタマイズは終了となります。今回はポイントに絞って解説させていただきました。ソースコードの中身を詳しく知りたいという方は、繰り返しにはなりますが、↓のリポジトリをみていただけると、今回使用したソースコードがすべてアップロードされていますので、そちらを御覧ください。


終わりに

実は今回の講座のデモにはAPI機能を利用した実装がもう1機能実装されています。デモを見ていただいたらわかるかと思いますが、「もっと見るボタン」の実装です。講座では解説できませんでしたが、ソースコード自体は src/components/ShowMoreButton.js にありますので、興味のある方はGithubを確認していただけると幸いです。また、自分でもっとAPI機能を使って何かを実装してみたい!という方は ヒーローの身長(height)や体重(weight)、名前のアルファベットによるソート機能、キーワード検索機能を追加で実装してみると勉強になると思います。ぜひご自分の手で実装してみてください!

同じタグ付けがされている記事