今回が「WP REST APIで投稿を非同期で全件取得した話」の最終話になります。

前回までの記事は下記からご覧ください。

【投稿全件取得編】WP REST APIで投稿を非同期で全件取得した話
【投稿全件取得編】WPRESTAPIで投稿を非同期で全件取得した話
WPRESTAPIは、全件取得しようと思ってもper_pageが100までしかきいてくれないので、何も考えなければ100件しか取得することができません。 そのため、全件取得しようと思ったら、下記...
【ページネーション編】WP REST APIで投稿を非同期で全件取得した話
【ページネーション編】WPRESTAPIで投稿を非同期で全件取得した話
前記事【投稿全件取得編】で取得してきた投稿に対して、スタンダードなページネーションを子コンポーネントとして追加する方法です。

最後はタクソノミーフィルターをついけていきます。

チェックボックスにチェックを入れたら、そのタームに属する記事だけ表示してくれるという機能になります。

今回も前回同様、子コンポーネントを作成して、最初に作成したarchive.vueと連携を取る形で進めます。

それではやっていきましょう。

# この記事で解決すること

  • WP REST APIで取得してきた一覧に対してタクソノミーフィルターをつけることができる

 

 

手順

  1. 事前準備
    • フォルダ・ファイル構成の確認
    • 構造とデータの確認
  2. 初期処理
    • archive.vue
    • taxFilter.vue
    • page-test.php
  3. タームの全件取得
    • タームの取得
    • タクソノミー初期情報の取得
    • タームの全件取得
    • templateに反映
  4. タクソノミーフィルター
    • タクソノミー名の取得
    • フィルター作成
    • 親コンポーネントでのデータ受取準備
    • ターム変更時の設定(発火設定)
    • フィルター表示時の挙動調整
  5. 挙動確認

フォルダ・ファイル構成の確認

今回追加するファイルはtaxFilter.vue、
変更するファイルはarchive.vueとpage-test.phpとなります。

  • theme folder
    • src
      • js
        • components
          • archive.vue(edit)
          • taxFilter.vue(add)
          • pagination.vue
        • app.js
    • page-test.php(edit)

 

構造とデータの確認

前回の続きとして作業を行いますので、データなども前回から引き続き使用するものがあります。

また、今回はタクソノミーとタームが少しややこしいので下記の画像を確認してください。

そちらを踏まえた上で確認していきます。

 

今回はCacooで作成しました!こちらの方がラクです。

やっていることとしては、
フロントのチェックボックスにチェックが入ったら、
その内容から全投稿をフィルターして、
チェックが入ったタクソノミーに属する投稿のみを表示させるというものになります。

新しく追加するものを青としており、連携させるデータ自体は割りと少ないです。

taxNamepropsに入れても良かったのですが、
taxFetchURlから正規表現で取得できそうだったので、
あえてcomputedで取得するようにしています。

では、前回から引き続き使用するデータと新たに追加するデータの一覧です。
(タクソノミーフィルターで使用するもののみ記載)

引き続き使用するデータ

name remark
pagenationShow ページネーションの表示
dataOrigin 投稿の全データ

新たに追加するデータ:props

name type remark
taxFetchUrl String REST APIで取得してくるタクソノミーのURL
default: ‘/wp-json/wp/v2/categories’
taxFilterSearch String 検索方法(or, and)
default: ‘or’
taxQuery Object REST APIに送るパラメータ
default: () => ({
‘exclude’: 1, // 未分類の非取得
})
taxFilterShow Boolean タクソノミーフィルターを表示するか否か
default: true

新たに追加するデータ:data

name type remark
terms Array チェックが入ったタームを格納
dataTerms Array ターム一覧(全件)
totalTaxPages Number タクソノミーのpage数
(タームの全件取得に必要)

初期処理

前項のデータを入れ込むと各ファイルの初期状態は下記の内容となります。
※説明と関係ない部分は省いています。

archive.vue

<template>
  <div class="my-post-archive">
    <taxFilter
      v-if="taxFilterShow"
      :taxFetchUrl="taxFetchUrl"
      :taxFilterSearch="taxFilterSearch"
      :taxQuery="taxQuery"
      :dataOrigin="dataOrigin"
      :paginationShow="paginationShow"
    />
    <article class="archive" v-if="datas">...</article>
    <pagination ... />
</div>
</template>

<style lang="scss" scoped>...</style>

<script>
  import axios from 'axios'
  import pagination from './pagination.vue'
  import taxFilter from './taxFilter.vue'

  export default {
  props: {
    postFetchUrl: {...},
    postQuery: {...},
    perPage: {...},
    /* pagination  */
    pagerVisible: {...},
    pagerScroll: {...},
    paginationShow: {...},
    /* taxFilter ------------------------------------------------- */
    taxFetchUrl: {
      type: String,
      default: '/wp-json/wp/v2/categories',
    },
    taxFilterShow: {
      type: Boolean,
      default: true,
    },
    taxFilterSearch: {
      type: String,
      default: 'or', // 'or' or 'and'
    },
    taxQuery: {
      type: Object,
      default: () => ({
      'exclude': 1, // 未分類の非取得
      })
    },
  },
  components: {
    pagination,
    taxFilter
  },
...

4行目

taxFilterShowでタクソノミーフィルターを表示させるか否か決めたいので、
v-ifで判定しています。

21行目, 36行目

taxFilter.vueの読み込み

33~50行目

propsの追加

 

taxFilter.vue

<template>
  <div class="filter">
    <div class="filter__label">
      <input type="checkbox" id="term_id_0">
      <label for="term_id_0" v-text="ターム0">
    </div>
    <div class="filter__label">
      <input type="checkbox" id="term_id_1">
      <label for="term_id_1" v-text="ターム1">
    </div>
  </div>
</template>

<style lang="scss" scoped>
  .filter {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .5rem 1rem;
    border-bottom: 2px solid #eee;
    margin-bottom: 1rem;
    padding-bottom: 1rem;
  }
</style>

<script>
  import axios from 'axios'
  
  export default {
    props: {
      taxFetchUrl: {
        type: String,
      },
      taxFilterSearch: {
        type: String,
      },
      taxQuery: {
        type: Object,
      },
      dataOrigin: {
        type: Array,
      },
      paginationShow: {
        type: Boolean,
      },
    },
    data() {
      return {
        terms: [],
        dataTerms: [],
        totalTaxPages: 0,
      }
    },
  }
</script>

1~24行目

一旦適当なhtmlをあてています。scssもサンプルなので適当です。

27行目

今回はタクソノミーのREST APIにアクセスして取得するので、axiosを読み込んでおきます。

30行目~

propsは親コンポーネントであるarchive.vueからのデータの引き渡しであり、
defaultやrequireは親コンポーネントに設定していますので、typeのみ設定しておきます。

 

page-test.php

<div id="archive">
  <archive
    :post-fetch-url="'/wp-json/wp/v2/posts'"
    :per-page="10"
    :post-query="{
      '_fields': 'id,content,title,categories,_links,_embedded',
      '_embed': '',
    }"
    :pager-visible="5"
    :pager-scroll="1"
    :pagination-show="true"
    :tax-fetch-url="'/wp-json/wp/v2/categories'"
    :tax-filter-show="true"
    :tax-filter-search="'or'"
    :tax-query="{
      '_fields': 'id,name,slug,count',
      'exclude': 1,
    }"
    #post="{ data }"
  >
    <span v-text="data.id"></span>
  </archive>
</div>

12~18行目

大方defaultと同様の内容にしています。

tax-queryだけ取得するフィールドを制限しておきたいので追記します。

上記以外は前回同様となります。

タームの全件取得

フィルターする前に、フィルターさせるためのタームチェックボックスの一覧をフロントに表示させる必要があります。

一覧データの全件取得の構造については投稿全件取得編で説明しています。

今回はリクエストURLのwp/v2/post_type/の部分がwp/v2/taxonomy/に変わっただけで、
全件取得に使用するパラメーターは投稿もタクソノミーも対して変わりませんので、ざっくりいきます。

タームの取得

taxFilter.vue: methods

async getTermData(num, taxQuery = this.taxQuery) {
  // パラメーターを追加できるようにしておく
  const params = { page: num }
  const insertParams = await {...taxQuery, ...params}
  await axios.get(this.taxFetchUrl, {params: insertParams})
  .then((res) => {
    // タームの情報をdataTermsに結合
    this.dataTerms = this.dataTerms.concat(res.data)
  })
  .catch(error => console.log(error))
},

タクソノミー初期情報の取得

taxFilter.vue: methods

async getTaxInfo() {
  await axios.get(this.taxFetchUrl)
  .then((res) => {
    this.totalTaxPages = Number(res.headers['x-wp-totalpages']) // 合計ページ数
  })
  .catch(error => console.log(error))
},

投稿全件取得時には合計投稿数も取得してきましたが、今回は特に必要ありませんので、合計ページ数のみ変数に代入しておきます。

タームの全件取得

taxFilter.vue: methods

async getTermDatas() {
  // タクソノミー情報の取得
  await this.getTaxInfo()
  // タームの全件取得
  const numTaxAry = [...Array(this.totalTaxPages).keys()].map( i => ++i )
  for (let num of numTaxAry) {
    await this.getTermData(num)
  }
},

mountedに反映

一旦mountedgetTaxDatasを入れておきましょう。

taxFilter.vue: mounted

mounted() {
  this.getTaxDatas()
},

templateに反映

これで、dataTaxに対してターム一覧が入りますので、templateに反映します。

taxFilter.vue: template

...
<div class="filter__label" v-for="term in dataTerms" :key="term.id">
  <input type="checkbox" :id="`term_${term.id}`" v-model="terms" :value="term.id">
  <label :for="`term_${term.id}`" v-text="`${term.name}(${term.count})`" />
</div>
...

idforでテキストを押したときにもチェックが入る仕様にしています。

また、チェックが入った場合にtermsterm.idが格納されていく流れにしたいのでv-modelを使用しています。

term.countはそのタームに属する記事が何記事あるか表示させているだけですので、必須ではありません。

これで、チェックボックスの付いたターム一覧が表示されているはずです。

タクソノミーフィルター

フィルターを行うには、投稿のデータとtermsに格納されているタームIDの値が一致するか否かを確認する必要があります。

それを確認するためには、投稿データのどこにタクソノミーの情報が入っているかを把握しなければなりません。

実際に、タクソノミーの情報は下記画像の箇所にあります。

左側が通常のタクソノミー(カテゴリー)、右側がカスタムタクソノミーの場合のREST APIの構造になります。

keyに対してタクソノミー名があり、その配下にタームIDを確認することができます。

タクソノミー名の取得

タクソノミーはカテゴリーだけではなく、カスタムタクソノミーを指定した場合には、そのタクソノミー名のkeyをたどる必要があります。

そのためタクソノミー名を予め取得しておきます。

taxFilter.vue: computed

taxName() {
  return this.taxFetchUrl.match(/.*\/([\w|~]+).*$/u)[1]
},

正規表現で、「wp-json/wp/v2/taxonomy_name」から taxonomy_name だけを抽出しています。

フィルター作成

まず前提として投稿データは3つあります。

dataOrigin 投稿の全データ
dataBase フィルターされたデータ
data 表示するデータ

流れとしては、下記の通りです。

  1. dataOriginからデータをフィルターする
  2. フィルターされたデータを親コンポーネントに送る
  3. 親コンポーネントで表示するデータを差し替える

また、今回は「or検索」と「and検索」を選べるようにしています。

それを踏まえてご覧ください。

async postTaxFilter() {
  // 送信データの初期化
  let datas = []
  
  // タームが指定されていない場合(チェックを外した場合など)
  if(!this.terms.length){
    // 全データをdataBaseに格納
    datas = await this.dataOrigin
  
  // タームが指定されている場合
  } else {

    // or検索の場合
    if(this.taxFilterSearch == 'or') {
      // フィルターで該当タームに属する投稿をdataBaseに格納
     datas = await this.dataOrigin.filter((obj)=>{
       return this.terms.some(id => obj[this.taxName].includes(id))
     })

    // and検索の場合(or以外の場合)
    } else {
      // フィルターで該当タームに属する投稿をdataBaseに格納
      datas = await this.dataOrigin.filter((obj)=>{
        return this.terms.every(id => obj[this.taxName].includes(id))
      })
    }
  }
  // dataBaseの更新
  this.$emit('changeData', datas)
  // totalPostsの更新
  this.$emit('changeTotal', datas.length)
},

16~18行目

or検索(チェックが入ったタームに1つでも属していれば表示)の場合の処理です。

array.filterはarrayに対してループ処理を行います。

returnがtrue判定だった場合、その配列や要素を返します。

例えば、objがタームID 1,5に属した投稿だったとしましょう。

そして、this.terms=[3,5]と仮定します。

その場合、下記のようになります。

[3,5].some(id => [1,5].includes(id))
// (3 => [1,5].includes(3))  // false
// (5 => [1,5].includes(5))  // true

includesは引数が対象に含まれているかをtruefalseで返します。

some1つでもtrueであった場合、tureを返します

23~25行目

and検索(チェックが入ったターム全てに属していれば表示)の場合の処理です。

every1つでもfalseがあった場合はfalseを返します

29, 31行目

フィルターを通した投稿」と「フィルターを通した投稿の合計数」を親コンポーネントに送ります。

ただ、今のままでは親が受け取れませんし、定義されていないemitを書いているのでエラーになります。

親コンポーネントでのデータ受取

では、親で「フィルターを通した投稿」と「フィルターを通した投稿の合計数」を受け取ります。

まずは、methodsで受け取るための関数を定義します。

archive.vue: methods

// dataBaseの受け取り
changeDataBase(datas) {
  this.dataBase = datas
},
// totalPostsの受け取り
changeTotalPosts(num) {
  this.totalPosts = num
},

そして、templateでemitに定義します。

archive.vue: template

<taxFilter
  v-if="taxFilterShow"
  ...
  @changeData="changeDataBase"
  @changeTotal="changeTotalPosts"
/>

親子を見比べるとこんな感じです。

// 親
changeDataBase(datas)           // methods
@changeData="changeDataBase"    // template

// 子
this.$emit('changeData', datas) // methods

子は親のコンポーネントを直接使うことができないので、emitに定義して呼び出している形になります。

タクソノミー変更時の設定(発火設定)

このままでは、チェックボックにチェックが入っても、特に何も起こりません。

ですので、チェックボックにチェックが入った時=termsが更新された時にフィルターを発火させます。

taxFilter.vue: watch

async terms() {
  // フィルターかける
  await this.postTaxFilter()
  // paginationを表示しない場合はスルー
  if(this.paginationShow) this.$parent.$refs.pagination.pagerDefault()
},

3行目

フィルターの処理が重めなので、awaitで完了まで待つよう設定しておきます。

5行目

ページネーションがある場合は、フィルターされた投稿数に応じたページネーションに変更し、
pagerを1に戻してほしいので、前記事で書いたpagerDefaultを呼び出します。

$parent = archive.vue

$refsは定義する必要があるのですが、前記事で下記のように定義していますので、
$parent.$refs.paginationがたどれるわけです。

archive.vue: template

...
<pagination
  v-if="paginationShow"
  ref="pagination"
  ...

フィルター表示時の挙動調整

フィルター表示時に投稿を全件取得するようにします。

というのも、全件取得しなかった場合、本来10記事あるのに3記事しか表示されないタームが出てくるからです。

archive.vue: methods: getPostDatas

async getPostDatas() {
  // paginationもしくはtaxFilterを表示する場合
  if(this.paginationShow === true || this.taxFilterShow === true) {
    await this.getPostInfo()
    // paginationを表示する場合
    if(this.paginationShow) this.$refs.pagination.pagerDefault()
    const numPostAry = [...Array(this.totalPages).keys()].map( i => ++i )
    for (let num of numPostAry) {
  await this.getPostData(num)
    }
  } else {
      // paginationもしくはtaxFilterを表示しない場合
      await this.getPostData(false)
  }
},

3行目

タクソノミーフィルターがtrueの場合は全件取得

挙動確認

or検索時

チェックを増やすとページャーが増えているのがわかると思います。

and検索時

チェックを増やすとページャーが減っているのがわかると思います。

 

双方問題なく動いていますね。

最後に

3部にわたってお送りしてきたWP REST APIの投稿取得ですが、いかがでしたでしょうか?

私なりのポイントをまとめると

# point

  • asyncawaitはやっぱり便利
  • header情報を一番簡単に取得するにはaxiosが有力解
  • コンポーネント化して使い回せるコードが正義
  • 毎度変わる可能性があるものはpropsslotで対応
  • filter+someeveryは使い所多め

総合して言えるのは、asyncawaitはやっぱり便利だということです。

それはつまり、asyncawaitを理解しない限りsetTimeOutなどの関数を使用して理解しにくい長い構文を書かないといけないということです。

また、今後記事にしていきたいのは下記の内容です。

  • infinit scrollでの追加投稿表示
  • ボタンによる追加投稿表示
  • タクソノミーの階層化(再帰関数)
  • ローディングの実装

どれも普段使いそうなものばかりなので、紹介していければと思います!

それでは、よいWPライフを!

あわせて読みたい記事

【投稿全件取得編】WP REST APIで投稿を非同期で全件取得した話
【投稿全件取得編】WPRESTAPIで投稿を非同期で全件取得した話
WPRESTAPIは、全件取得しようと思ってもper_pageが100までしかきいてくれないので、何も考えなければ100件しか取得することができません。 そのため、全件取得しようと思ったら、下記...
【ページネーション編】WP REST APIで投稿を非同期で全件取得した話
【ページネーション編】WPRESTAPIで投稿を非同期で全件取得した話
前記事【投稿全件取得編】で取得してきた投稿に対して、スタンダードなページネーションを子コンポーネントとして追加する方法です。