filterメソッドで多階層のセレクトボックスを非同期で実装することがあったので復習がてら記事にします。
前提条件
フロント: nuxt
バック: rails
api通信はできている
データ構成は下記記事を参考にする
Rails * Vue.js * Ajaxで多階層カテゴリを作った話
rails側の実装
- seed_fuでデータ作成
- ルーティング生成
- コントローラ作成
- jbuilderでデータをjsonで返す
001_category.rb
Category.seed(:id,
{ id: 100, name: '親1'},
{ id: 110, name: '親1の子1'},
{ id: 111, name: '親1の子1の孫1'}, { id: 112, name: '親1の子1の孫2'}, { id: 113, name: '親1の子1の孫3'},
{ id: 120, name: '親1の子2'},
{ id: 121, name: '親1の子2の孫1'}, { id: 122, name: '親1の子2の孫2'}, { id: 123, name: '親1の子2の孫3'},
{ id: 130, name: '親1の子3'},
{ id: 131, name: '親1の子3の孫1'}, { id: 132, name: '親1の子3の孫2'}, { id: 133, name: '親1の子3の孫3'},
{ id: 200, name: '親2'},
{ id: 210, name: '親2の子1'},
{ id: 211, name: '親2の子1の孫1'}, { id: 212, name: '親2の子1の孫2'}, { id: 213, name: '親2の子1の孫3'},
{ id: 220, name: '親2の子2'},
{ id: 221, name: '親2の子2の孫1'}, { id: 222, name: '親2の子2の孫2'}, { id: 223, name: '親2の子2の孫3'},
{ id: 230, name: '親2の子3'},
{ id: 231, name: '親2の子3の孫1'}, { id: 232, name: '親2の子3の孫2'}, { id: 233, name: '親2の子3の孫3'}
)
routes.rb
Rails.application.routes.draw do
resources :categories, only: %i[index]
end
categories_controller.rb
class CategoriesController < BaseController
def index
@categories = Category.all
end
end
index.jbuilder
json.categories @categories do |category|
json.extract! category, :id, :name
end
nuxt側の実装
nuxtの方はvuestifyというマテリアルデザインコンポーネントフレームワークを使用
category.vue
<template>
<div>
<v-select
label="親カテゴリー"
:items="parentCategory"
item-text="name"
item-value="id"
v-model="parentSelected"
/>
<v-select
label="子カテゴリー"
:items="childCategory"
item-text="name"
item-value="id"
v-model="childrenSelected"
/>
<v-select
label="孫カテゴリー"
:items="grandChildCategory"
item-text="name"
item-value="id"
v-model="grandChildrenSelected"
/>
</div>
</template>
<script>
export default {
mounted() {
this.fetchCategory()
},
data: () => {
return {
categories: [],
parentSelected: '',
childrenSelected: '',
grandChildrenSelected: '',
}
},
computed: {
parentCategory() {
return this.categories.filter((category) => category.id % 100 === 0)
},
childCategory() {
if (this.parentSelected === '') {
return []
}
return this.categories.filter((category) => {
// 100の位が同じcategory
return (Math.floor(category.id / 100) === Math.floor(this.parentSelected / 100) &&
// 1の位が0のcategory
category.id % 10 === 0 &&
// 親カテゴリーと一致しないcategory
category.id !== this.parentSelected)
})
},
grandChildCategory() {
if (this.childrenSelected === '') {
return []
}
return this.categories.filter((category) => {
// 10の位が一致するcategory
return (Math.floor(category.id / 10) === Math.floor(this.childrenSelected / 10) &&
// 1の位が0ではないのcategory
category.id % 10 !== 0)
})
}
},
methods: {
fetchCategory() {
const url = 'api/categories'
this.$axios
.$get(url)
.then((response) => {
this.categories = response.categories
})
.catch((errors) => {
console.log(errors)
})
}
}
}
</script>
- mountedでfetchCategoryを呼び出し
- セレクトボックスのオプションをcomputedでそれぞれ定義
- それぞれのセレクトボックスにv-modelを設定
- 親のv-modelが変更 -> 子カテゴリーのオプションが変更
- 子のv-modelが変更 -> 孫カテゴリーのオプションが変更
filterはこのように使用します
[検証したいデータ].filter((1データ) => 条件式)
親カテゴリーの例でみてみます
.vue
// 親カテゴリー
<v-select
label="親カテゴリー"
:items="parentCategory"
item-text="name"
item-value="id"
v-model="parentSelected"
/>
// オプション定義
parentCategory() {
return this.categories.filter((category) => category.id % 100 === 0)
// 100 % 100 = 0, 110 % 100 = 10, 111 % 100 = 11, 200 % 100 = 0
},
:items="オプションのデータ"
なのでcomputed内にあるparentCategory
を参照します
this.categoriesにはrailsで定義したCategory.all
のデータが入っています。
categoryには{ id: 100, name: '親1' }
のように一つのデータが入っています。
そのデータのidを100で割った時のあまりが0と一致するデータを返すという処理になっているので
この場合idが100, 200, 300...のレコードが返ります。
以上です。
filterの条件式をもう少しうまく記述できそうな気がしますが。。。
他にいい方法があればコメントで教えてください!