firestoreには日頃から感謝していますが、まだまだクエリには難があるようですね…
ということで、今回はfirestoreのクエリ範囲をページネーションによって操作する方法を備忘録としてまとめていきたいと思います。
使用環境
Nuxt (version: 2.11.0)
Vuetify (version: 2.1.15)
firestore
やりたかったこと
- 相当数(1000以上)のデータが格納されたDBから、必要に応じた数を指定した範囲でクエリしたい。
- もちろんソートをかけた時に、DB全てが対象にしたい。
- ソートをかけた際に、同じ値のものが複数ある時にも対応したい。
- 部分一致検索も出来るようにしたい。
- 検索も特定のフィールドだけではなく、必要に応じてフィールドを選べるようにしたい。
- けど、Algoria等の外部APIは使用したくない。
- リロードが発生しても、状態を維持できるようにしたい。
- ページ番号も振りたい。
先に結論だけ言うと
- ページ遷移によるクエリ範囲の変更は可能。
- ソートも出来る。ただし、同じ値が複数ある場合はフィールドが2つ必要。
- 完全な部分一致は出来ないが、前方一致は出来る。
-
Vuex
で保存すればリロードを挟んでも維持できる。 - ページ番号は振れるのですが、ページの総数までは抜き出すことができませんでした。(強がりを言うと、実装することは出来るのですが、データ数が多くなると現実的ではないと考えました…)
となります。
以下見にくいかもしれませんが、成果物のgifを貼っておきます。
今回省略する部分
- firebaseのインストール方法
- nuxtアプリの生成方法
どちらも拙記事に掲載していますので、気になる方はそちらを確認してください。
nuxtアプリの生成方法
firebase関係のインストール方法
前置きが長くなりましたが、本題に入ります。
0.概観
ディレクトリ構造
今回作っていくアプリのディレクトリ構造です。
関係のない部分は端折っています。
<プロジェクト名>
├── components
│ ├── GetData.vue
│ └── Table.vue
├── layouts
│ └── default.vue
├── pages
│ ├── Book.vue
│ └── index.vue
~
~
└── store
└── index.js
今回 layouts
はシンプルに pages
のみを写すだけのものとします。
DB構造
今回の firestore
のDB構造です。
books
├── <本1>
│ ├── title
│ ├── createdAt
│ └── type
~
~
└── <本n>
├── title
├── createdAt
└── type
users
├── <ユーザー1>
│ ├── age
│ ├── name
│ ├── createdAt
│ └── post_count
~
~
└── <ユーザーn>
├── age
├── name
├── createdAt
└── post_count
このように今回は簡単のため、books
とusers
の2つのテーブルデータをそれぞれ違うpages
を用いて表示させます。
ファイルの相関

1. ページ遷移の方法
今回はクエリした内容をVuetify
のv-data-table
に渡していきます。
まずは全体のコードです。
<template>
<div>
<GetData :seed="seed" :headers="headers" :addBtn="addBtn" />
<v-btn @click="toBook()" />
</div>
</template>
<script>
import GetData from '~/components/GetData.vue'
export default {
components: {
GetData
},
data () {
return {
seed: 'users',
headers: [
{ text: 'アカウント名', sortable: true, value: 'name', align: 'center' },
{ text: '年齢', sortable: true, value: 'age', align: 'center' },
{ text: '投稿数', sortable: true, value: 'post_count', align: 'center' },
{ text: 'ID', sortable: false, value: 'uid', align: 'center' },
{ text: 'createdAt', sortable: false, value: 'createdAtshow', align: 'center' }
]
}
},
methods: {
toBook () {
this.$router.push('/Book')
}
}
}
</script>
<template>
<div>
<GetData :seed="seed" :headers="headers" :addBtn="addBtn" />
<v-btn @click="toIndex()" />
</div>
</template>
<script>
import GetData from '~/components/GetData.vue'
export default {
components: {
GetData
},
data () {
return {
seed: 'books',
headers: [
{ text: '題名', sortable: true, value: 'title', align: 'center' },
{ text: '区分', sortable: false, value: 'type', align: 'center' },
{ text: 'ID', sortable: false, value: 'uid', align: 'center' },
{ text: 'createAt', sortable: false, value: 'createdAtshow', align: 'center' }
]
}
},
methods: {
toIndex () {
this.$router.push('/')
}
}
}
</script>
<template>
<div>
<Table
:OriginList="OriginList"
:seed="seed"
:page="page"
:headers="headers"
:first="first"
:last="last"
@handlepage="updatepage"
@handlesearch="updatesearch"
:search="search"
:itemsPerPage="itemsPerPage"
/>
</div>
</template>
<script>
import firebase from '~/plugins/firebase.js'
import Table from '~/components/Table.vue'
export default {
name: 'GetData',
components: {
Table
},
props: {
seed: {
type: String,
default: ''
},
headers: {
type: Array,
default: []
}
},
data () {
return {
itemsPerPage: 6,
OriginList: [],
fuid: '',
luid: '',
first: true,
last: false,
firstitem: '',
lastitem: '',
filterseed: '',
page: 1,
baseitem: '',
basetime: '',
search: '',
docRef: '',
lastRef: '',
orderItem: '>=',
order: 'asc',
initial: {
page: 1,
baseitem: '',
basetime: ''
}
}
},
created () {
if (this.seed === 'users') {
this.filterseed = 'name'
} else if (this.seed === 'books') {
this.filterseed = 'title'
}
this.makeList(this.filterseed, this.orderItem, this.order)
},
methods: {
showTime (time) {
if (time) {
const year = new Date(time.seconds * 1000).getFullYear().toLocaleString('ja-JP').replace(/,/, '')
let month = new Date(time.seconds * 1000).getMonth().toLocaleString('ja-JP')
month = parseFloat(month) + 1
const day = new Date(time.seconds * 1000).getDate().toLocaleString('ja-JP')
const thisDate = year + '/' + month + '/' + day
return thisDate
} else {
}
},
makeList (filterseed, orderItem, order) {
// 初期化
this.OriginList = []
this.first = false
this.last = false
const db = firebase.firestore().collection(this.seed)
const t = this
const invList = []
const key = 0
// 接続先のDBを選択 lasRefは一番最後のUidを取ってくるやつ
if (this.search === '') {
this.docRef = db.orderBy(filterseed, order).orderBy('createdAt', order).startAfter(this.baseitem, this.basetime)
this.lastRef = db.orderBy(filterseed, 'desc').orderBy('createdAt', 'desc')
}
// DB作成
this.docRef.limit(this.itemsPerPage).get().then(function (snapshot) {
snapshot.forEach(function (doc) {
const dd = doc.data()
dd.uid = doc.id
dd.createdAtshow = t.showTime(dd.createdAt)
t.OriginList.push(dd)
})
// 前のページに遷移するときは順番を逆にする
if (orderItem === '<=') { t.OriginList = t.OriginList.reverse() }
if (t.OriginList.length !== 0) {
// 表示されているものの最初と最後取ってくる
t.lastitem = t.OriginList[t.OriginList.length - 1][filterseed]
t.lasttime = t.OriginList[t.OriginList.length - 1].createdAt
t.firstitem = t.OriginList[0][filterseed]
t.firsttime = t.OriginList[0].createdAt
// 1ページ目ならDB全体の最初と最後も取ってくる
if (t.page === 1) {
t.fuid = t.OriginList[0].uid
t.lastRef.limit(1).get()
.then(function (snapshot) {
snapshot.forEach(function (doc) {
invList.push({
luid: doc.id
})
t.luid = invList[0].luid
// t.$store.commit("sessionUid", {fuid: t.fuid, luid: t.luid})
Object.keys(t.OriginList).forEach(function (key) {
if (t.OriginList[key].uid === t.luid) { t.last = true }
})
})
})
.catch(function (error) {
console.log(error)
})
}
// 現在表示しているデータに最初と最後あるか確認
Object.keys(t.OriginList).forEach(function (key) {
if (t.OriginList[key].uid === t.fuid) { t.first = true }
if (t.OriginList[key].uid === t.luid) { t.last = true }
})
} else {
t.first = true
t.last = true
}
})
.catch(function (error) {
console.log(error)
})
},
updatepage (handlepage, filterseed, orderItem, order) {
this.page = handlepage
if (orderItem === '>=') {
this.baseitem = this.lastitem
this.basetime = this.lasttime
} else if (orderItem === '<=') {
this.baseitem = this.firstitem
this.basetime = this.firsttime
}
this.makeList(filterseed, orderItem, order)
}
}
}
</script>
<template>
<div>
<v-card v-show="!showDelete" flat style="background: #F8F8F8; padding: 15px;">
<v-card-title class="card-title">
{{ listName }}管理
</v-card-title>
<v-data-table
v-model="selected"
:headers="headers"
:items="OriginList"
:page.sync="page"
:itemsPerPage.sync="itemsPerPage"
@page-count="pageCount = $event"
show-select
light
hide-default-footer
item-key="uid"
item
/>
<div style="padding-top: 30px; text-align: center">
<v-btn @click="prenav()" :disabled="first" class="pagenation" small fab>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn class="pagenation-center" small fab disabled>
<span>{{ page }}</span>
</v-btn>
<v-btn @click="nextnav()" :disabled="last" class="pagenation" small fab>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</div>
</v-card>
</div>
</template>
<script>
import firebase from '~/plugins/firebase.js'
export default {
name: 'Table',
props: {
OriginList: {
type: Array,
default: []
},
seed: {
type: String,
default: ''
},
headers: {
type: Array,
default: []
},
page: {
type: Number,
default: 1
},
itemsPerPage: {
type: Number,
default: 10
},
first: {
type: Boolean,
default: false
},
last: {
type: Boolean,
default: true
}
},
data () {
return {
pageCount: 0,
filterseed: '',
totalVisible: 1,
selectItem: [],
checkRow: [],
selected: [],
dialogItem: {
uid: '',
name: '',
title: '',
createdAtshow: '設定されていません。'
},
InitialItem: {
uid: '',
name: '',
title: '',
createdAtshow: '設定されていません。'
},
showDelete: false
}
},
mounted () {
if (this.seed === 'users') {
this.filterName = '名前'
this.filterseed = 'name'
} else if (this.seed === 'books') {
this.filterName = '題名'
this.filterseed = 'title'
}
},
methods: {
prenav () {
const currentpage = this.page - 1
const orderItem = '<='
const order = 'desc'
this.$emit('handlepage', currentpage, this.filterseed, orderItem, order)
},
nextnav () {
const currentpage = this.page + 1
const orderItem = '>='
const order = 'asc'
this.$emit('handlepage', currentpage, this.filterseed, orderItem, order)
}
}
}
</script>
少し長くなりましたが、この時点でほぼ全ての実装ができています。
では中身を1つ1つ見ていきましょう。
大まかな流れ
i. index.vue
ファイル(もしくはBooks.vue
にある、固有の変数をGetData.vue
に渡す。
ii. Getdata
ファイルのcreated
時にmakeList
メソッドが発火。
iii. makeList
メソッドによって、firebaseからクエリする条件を整え、クエリした内容を変数OriginList
に連想配列として格納する。
iv. v-data-table
に必要な変数OriginList
等の情報をTable
ファイルに渡す。
v. ページ遷移する際に、「戻る」か「進む」かをprenav
メソッドかnextnav
メソッドで定義して、GetData
ファイルに返し、もう一度その条件でクエリする。
i. 固有変数の受け渡し
各テーブル固有の変数をここで作ります。
具体的に言うと、今回はusers
とbooks
という2種類のテーブルで違う値を取らなければならない、headers
とseed
を設定します。
<script>
export default {
// ...略
data () {
return {
seed: 'books',
headers: [
{ text: '題名', sortable: true, value: 'title', align: 'center' },
{ text: '区分', sortable: false, value: 'type', align: 'center' },
{ text: 'ID', sortable: false, value: 'uid', align: 'center' },
{ text: 'createAt', sortable: false, value: 'createdAtshow', align: 'center' }
]
}
}
// ...略
}
</script>
ii. mkaeListメソッドの発火
ここではあくまでcreated
しているだけですが、makeList
メソッドとして分けている部分がミソになります。
こうすることで、非同期処理が非常になりやすくなり、以降もコードを修正しやすくなります。(個人的な意見ですが)
またここで後に使うfilterseed
をseed
毎に定義しておきます。
<script>
export default {
// ...略
created () {
if (this.seed === 'users') {
this.filterseed = 'name'
} else if (this.seed === 'books') {
this.filterseed = 'title'
}
this.makeList(this.filterseed, this.orderItem, this.order)
}
// ...略
}
<・script>
iii. 生データをOriginListに整形する
ここで、firebaseから生のデータを受け取ります。
現在は素直に受け取るだけなので特に難しいことはないですが、後々に弄りやすい形に意図的にしています。
ただしクエリ文は、重複を許せるように2つのフィールドでorderBy
でクエリするようにしています。
詳しくは公式ドキュメントを確認してください。
流れとしては、
-
docRef
で順方向の生のデータを取り出し、そのデータにドキュメントIDを加え、createdAt
を timestampから任意の形に直した上で、OriginList
に現在のページに必要な分だけのデータを格納します。 - 現在テーブルに表示されている最初のデータのドキュメントIDと
createdAt
、最後のデータのドキュメントIDとcreatedAt
をそれぞれfirstitem
,firsttime
,lastitem
,lasttime
と定義します。これがページを遷移する際に、最も大事になります。 - 現在が1ページ目である場合は
docRef
から全ての生データの一番最初のドキュメントIDをfuid
と定義します。さらに、lastRef
で逆方向から一番最初のデータのドキュメントIDもluid
と定義します。 - 現在のページに
fuid
,luid
がそれぞれある場合に、first
,last
をtrue
にします。
以上の4ステップです。
<script>
export default {
// ...略
methods: {
showTime (time) {
if (time) {
const year = new Date(time.seconds * 1000).getFullYear().toLocaleString('ja-JP').replace(/,/, '')
let month = new Date(time.seconds * 1000).getMonth().toLocaleString('ja-JP')
month = parseFloat(month) + 1
const day = new Date(time.seconds * 1000).getDate().toLocaleString('ja-JP')
const thisDate = year + '/' + month + '/' + day
return thisDate
} else {
}
},
makeList (filterseed, orderItem, order) {
// 初期化
this.OriginList = []
this.first = false
this.last = false
const db = firebase.firestore().collection(this.seed)
const t = this
const invList = []
const key = 0
// 接続先のDBを選択 lasRefは一番最後のUidを取ってくるやつ
if (this.search === '') {
this.docRef = db.orderBy(filterseed, order).orderBy('createdAt', order).startAfter(this.baseitem, this.basetime)
this.lastRef = db.orderBy(filterseed, 'desc').orderBy('createdAt', 'desc')
}
// DB作成
this.docRef.limit(this.itemsPerPage).get().then(function (snapshot) {
snapshot.forEach(function (doc) {
const dd = doc.data()
dd.uid = doc.id
dd.createdAtshow = t.showTime(dd.createdAt)
t.OriginList.push(dd)
})
// 前のページに遷移するときは順番を逆にする
if (orderItem === '<=') { t.OriginList = t.OriginList.reverse() }
if (t.OriginList.length !== 0) {
// 表示されているものの最初と最後取ってくる
t.lastitem = t.OriginList[t.OriginList.length - 1][filterseed]
t.lasttime = t.OriginList[t.OriginList.length - 1].createdAt
t.firstitem = t.OriginList[0][filterseed]
t.firsttime = t.OriginList[0].createdAt
// 1ページ目ならDB全体の最初と最後も取ってくる
if (t.page === 1) {
t.fuid = t.OriginList[0].uid
t.lastRef.limit(1).get()
.then(function (snapshot) {
snapshot.forEach(function (doc) {
invList.push({
luid: doc.id
})
t.luid = invList[0].luid
// t.$store.commit("sessionUid", {fuid: t.fuid, luid: t.luid})
Object.keys(t.OriginList).forEach(function (key) {
if (t.OriginList[key].uid === t.luid) { t.last = true }
})
})
})
.catch(function (error) {
console.log(error)
})
}
// 現在表示しているデータに最初と最後あるか確認
Object.keys(t.OriginList).forEach(function (key) {
if (t.OriginList[key].uid === t.fuid) { t.first = true }
if (t.OriginList[key].uid === t.luid) { t.last = true }
})
} else {
t.first = true
t.last = true
}
})
.catch(function (error) {
console.log(error)
})
}
// ...略
}
ただしこのクエリ方法は、firestoreの複合インデックスというものを使用しています。
初回はその設定を組む必要があります。
複合インデックスを組む
以上のコードを組むと、コンソールに以下のようなログが出てくると思います。
FirebaseError: The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/*******************/firestore/indexes?create_composite=****************************************************************************************************************************
at new n (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:160:23)
at t._i (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:3479:16)
at t.Fi (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:3638:195)
at n.onMessage (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:10098:33)
at eval (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:10051:26)
at eval (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:10082:37)
at eval (webpack-internal:///./node_modules/@firebase/firestore/dist/index.cjs.js:5099:31)
このログに書かれているURLに飛ぶと

このような画面に飛ばされると思います。
この指示に従い、「インデックスを作成」を押すと複合インデックスが自動生成され、数分後にはエラーが起きずに予期したクエリが行われます。
iv. TableにOriginListを渡す
あとはこれをTable
に渡して、表示させるだけです。
ただし、先ほど定義したfirst
とlast
の有無に応じて「←ボタン」や「→ボタン」を押せなくする必要があります。
また、リストに名前を表示させたいので、mounted
時に、各seed
によってlistName
が変化するようにしましょう。
template>
<div>
<v-card v-show="!showDelete" flat style="background: #F8F8F8; padding: 15px;">
<v-card-title class="card-title">
{{ listName }}管理
</v-card-title>
<v-data-table
v-model="selected"
:headers="headers"
:items="OriginList"
:page.sync="page"
:itemsPerPage.sync="itemsPerPage"
@page-count="pageCount = $event"
show-select
light
hide-default-footer
item-key="uid"
item
/>
<div style="padding-top: 30px; text-align: center">
<v-btn @click="prenav()" :disabled="first" class="pagenation" small fab>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn class="pagenation-center" small fab disabled>
<span>{{ page }}</span>
</v-btn>
<v-btn @click="nextnav()" :disabled="last" class="pagenation" small fab>
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</div>
</v-card>
</div>
</template>
<script>
export default {
// ...略
mounted () {
if (this.seed === 'users') {
this.filterName = '名前'
this.filterseed = 'name'
} else if (this.seed === 'books') {
this.filterName = '題名'
this.filterseed = 'title'
}
},
methods: {
prenav () {
const currentpage = this.page - 1
const orderItem = '<='
const order = 'desc'
this.$emit('handlepage', currentpage, this.filterseed, orderItem, order)
},
nextnav () {
const currentpage = this.page + 1
const orderItem = '>='
const order = 'asc'
this.$emit('handlepage', currentpage, this.filterseed, orderItem, order)
}
}
// ...略
}
</script>
v. ページの遷移
ここで本記事のメインであるページ遷移を行います。
**vi.**の工程にあるように、prenav
とnextnav
でページの遷移を行います。
それぞれmakeList
に遷移後のページ番号等の必要な値をGetData
ファイルに渡します。
その後GetData
ファイルのupdatepage
メソッドが発火します。
<script>
export default {
// ...略
methods: {
// ...略
updatepage (handlepage, filterseed, orderItem, order) {
this.page = handlepage
if (orderItem === '>=') {
this.baseitem = this.lastitem
this.basetime = this.lasttime
} else if (orderItem === '<=') {
this.baseitem = this.firstitem
this.basetime = this.firsttime
}
this.makeList(filterseed, orderItem, order)
}
}
// ...略
}
</script>
このように「←ボタン」「→ボタン」に応じて、**iii.**の工程で定めた、firstitem
・firsttime
, lastitem
・lasttime
のどちらか1組をbaseitem
・basetime
に代入します。
こうすることで、次にmakeList
メソッドでクエリする際に、baseitem
・basetime
から始めることができます。
以上で、ページ遷移の部分のコードは完成です。
2. ソートおよび前方一致検索
ここで、検索機能を追加します。
firestoreのクエリ機能はまだ充実してはいないため、完全な部分一致検索はできません。ですが、既存のクエリ機能を組み合わせて前方一致のみは実現可能です。
また、ソート機能はそれ単体であるのではなく、検索機能で「''」を検索した時に作動する感じです。
前方一致検索のクエリの仕組み
if (orderItem === '>=') {
this.deorderItem = '<='
} else if (orderItem === '<=') {
this.deorderItem = '>='
}
if (order === 'asc') {
this.startAtItem = this.search
this.endAtItem = this.search + '\uF8FF'
} else if (order === 'desc') {
this.startAtItem = this.search + '\uF8FF'
this.endAtItem = this.search
}
this.docRef = db.orderBy(filterseed, order).orderBy('createdAt', order).where(filterseed, orderItem, this.startAtItem).where(filterseed, this.deorderItem, this.endAtItem).startAfter(this.baseitem, this.basetime)
**1.- iii.**であったように、最初の2つのorderBy
でfilterseed
順、createdAt
順に並べた後に、where
で始まるポイントと終わるポイントを決めます。
その後、startAfter
でページ遷移する際の基準となるポイントを指定します。
イメージとしては以下のような感じです。

ではこれを組み込んでみます。
<script>
export default {
data () {
return {
// ...略
startAtItem: '',
endAtItem: '',
filterseed: 'name',
// ...略
}
},
methods: {
makeList (filterseed, orderItem, order) {
if (orderItem === '>=') {
this.deorderItem = '<='
} else if (orderItem === '<=') {
this.deorderItem = '>='
}
if (order === 'asc') {
this.startAtItem = this.search
this.endAtItem = this.search + '\uF8FF'
} else if (order === 'desc') {
this.startAtItem = this.search + '\uF8FF'
this.endAtItem = this.search
}
if (this.search === '') {
this.docRef = db.orderBy(filterseed, order).orderBy('createdAt', order).startAfter(this.baseitem, this.basetime)
this.lastRef = db.orderBy(filterseed, 'desc').orderBy('createdAt', 'desc')
} else if (this.search !== '') {
this.docRef = db.orderBy(filterseed, order).orderBy('createdAt', order).where(filterseed, orderItem, this.startAtItem).where(filterseed, this.deorderItem, this.endAtItem).startAfter(this.baseitem, this.basetime)
this.lastRef = db.orderBy(filterseed, 'desc').orderBy('createdAt', 'desc').where(filterseed, orderItem, this.startAtItem).where(filterseed, this.deorderItem, this.endAtItem)
}
// ...略
},
updatesearch (filterseed, orderItem, order, search) {
this.page = this.initial.page
this.baseitem = this.initial.baseitem
this.search = search
this.makeList(filterseed, orderItem, order)
},
// ...略
}
}
</script>
<template>
<div>
<v-card v-show="!showDelete" flat style="background: #F8F8F8; padding: 15px;">
<v-card-title class="card-title">
{{ listName }}管理
</v-card-title>
<v-card-actions>
<v-col v-if="seed == 'books'" cols="2">
<v-select
:items="selectItem"
v-model="filterseed"
item-text="name"
item-value="id"
light
outlined
dense
chips
color="black"
label="検索区分"
class="v-selectItem"
/>
</v-col>
<v-col cols="6">
<v-text-field
v-bind:value="search"
@keyup.enter="searchitem()"
v-on:input="newsearch = $event"
v-bind:label="filterName"
outlined
style="color: black"
color="black"
hide-details
light
dense
/>
</v-col>
<v-btn
@click="searchitem()"
color="blue"
class="mr-4"
style="float: right;"
>
検索
</v-btn>
</v-card-actions>
<v-data-table
// ...略
/>
</v-card>
</div>
</template>
<script>
export default {
props: {
// ...略
search: {
type: String,
default: ''
},
// ...略
},
// ...略
computed: {
filterName () {
if (this.filterseed === '') {
return ''
} else if (this.selectItem.length === 0) {
if (this.seed === 'users') {
return '名前'
} else {
return ''
}
} else {
const t = this
const val = this.selectItem
let target = ''
Object.keys(val).forEach(function (key) {
if (val[key].id === t.filterseed) { target = val[key].name }
})
return target
}
}
},
mounted () {
console.log(this.seed)
if (this.seed === 'users') {
this.listName = 'ユーザー'
this.filterseed = 'name'
console.log(this.seed)
} else if (this.seed === 'books') {
this.listName = '題名'
// this.filterseed = 'title'
this.selectItem = [
{ name: '題名', id: 'title' },
{ name: '区分', id: 'type' }
]
}
},
methods: {
// ...略
searchitem () {
if (!this.newsearch) { this.newsearch = '' }
const orderItem = '>='
const order = 'asc'
this.$emit('handlesearch', this.filterseed, orderItem, order, this.newsearch)
}
}
</scirpt>
mounted
でseed
がbooks
の時にfilterseed
の代わりにselectItem
を追加しています。
このようにすることで、<v-select>
でselectItem
から自由にfilterseed
を変更できるようになります。
3. 状態を保存する
Vue.js
を使う人に関しては既知の内容かもしれませんが、プロジェクト内の状態はVuex
を使います。
Nuxt.js
では、その記述を/store/index.js
に記述していきます。
stoteの詳しい内容はこちらをご覧ください。
今回状態の保存が必要な変数は、fuid, luid, page, path, filterseed, orderItem, order, baseitem, searchです。
ではstore/index.js
に記述していきます。
export const state = () => ({
fuid: '',
luid: '',
page: 1,
path: '',
filterseed: '',
orderItem: '',
order: 'asc',
baseitem: '',
search: ''
})
export const mutations = {
sessionUid (state, payload) {
console.log('store の sessionUid')
state.fuid = payload.fuid
state.luid = payload.luid
},
sessionPage (state, payload) {
state.page = payload.page
state.filterseed = payload.filterseed
state.orderItem = payload.orderItem
state.order = payload.order
state.baseitem = payload.baseitem
},
sessionSearch (state, payload) {
console.log('store の sessionSearch')
state.search = payload.search
},
sessionOnlyFilterseed (state, payload) {
console.log('store の sessionOnlyFilterseed')
state.filterseed = payload.filterseed
},
sessionPath (state, payload) {
console.log('store の sessionPath')
state.path = payload.path
}
}
store
では状態を保存し続けるため、違うテーブルに変更した時に予期せぬエラーが起きます。
そこで、画面が遷移した時のみ状態をリセットするようにmidleware
を設定します。
export default function ({ route, store, redirect }) {
if (route.path !== store.state.path) {
if (route.path === '/') {
store.commit('sessionPage', { page: 1, filterseed: 'name', orderItem: '>', order: 'asc', baseitem: '' })
store.commit('sessionSearch', { search: '' })
store.commit('sessionUid', { luid: '', fuid: '' })
} else if (route.path === '/Book') {
store.commit('sessionPage', { page: 1, filterseed: 'title', orderItem: '>', order: 'asc', baseitem: '' })
store.commit('sessionSearch', { search: '' })
store.commit('sessionUid', { luid: '', fuid: '' })
}
}
}
export default {
// ...略
router: {
middleware: 'index'
},
// ...略
}
こうすることで、リロード時には状態を保存できるが画面が遷移した時にはリセット出来ます。
ではstore
の値を使うようにcomponents
の記述も変えていきます。
<script>
export default {
// ...略
created () {
this.$store.commit('sessionPath', { path: this.$route.path }) // ルート変わっていたら、その都度初期化
this.page = this.$store.state.page // ページをstoreから持ってくる
this.fuid = this.$store.state.fuid// DB全体最初のuidをstoreから持ってくる
this.luid = this.$store.state.luid// DB全体最初のuidをstoreから持ってくる
this.baseitem = this.$store.state.baseitem// 前回のデータから今回表示するデータの基準になるものをstoreからもっってくる
this.makeList(this.$store.state.filterseed, this.$store.state.orderItem, this.$store.state.order)
},
methods: {
// ...略
updatesearch (filterseed, orderItem, order, search) {
this.page = this.initial.page
this.baseitem = this.initial.baseitem
this.search = search
this.$store.commit('sessionSearch', { search: this.search })
this.$store.commit('sessionPage', { page: this.page, filterseed, orderItem, order, baseitem: this.baseitem })
this.makeList(filterseed, orderItem, order)
},
updatepage (handlepage, filterseed, orderItem, order) {
this.page = handlepage
if (orderItem === '>=') {
this.baseitem = this.lastitem
this.basetime = this.lasttime
console.log('this.baseitem, this.basetime')
console.log(this.baseitem, this.basetime)
} else if (orderItem === '<=') {
this.baseitem = this.firstitem
this.basetime = this.firsttime
console.log('this.baseitem, this.basetime')
console.log(this.baseitem, this.basetime)
}
this.$store.commit('sessionPage', { page: this.page, filterseed, orderItem, order, baseitem: this.baseitem })
this.makeList(filterseed, orderItem, order)
}
}
}
</script>
<script>
export default {
mounted () {
this.filterseed = this.$store.state.filterseed
// ...略
},
methods: {
// ...略
searchitem () {
if (!this.newsearch) { this.newsearch = '' }
const orderItem = '>='
const order = 'asc'
this.$store.commit('sessionOnlyFilterseed', { filterseed: this.filterseed })
this.$emit('handlesearch', this.filterseed, orderItem, order, this.newsearch)
}
}
}
</script>
これでリロードしても検索の値、ページ数等が維持できるのではないでしょうか。
まとめ
出来たことには出来たのですが、Algoria
の方が簡単そうでした…
今度Algoria
で実装してみます。
わからない点・間違えている点・より良い方法などありましたらご指摘いただけると泣いて喜びます!!