Vue.jsでfirestoreのクエリ範囲をページネーションで変化させる

firestoreには日頃から感謝していますが、まだまだクエリには難があるようですね…
ということで、今回はfirestoreのクエリ範囲をページネーションによって操作する方法を備忘録としてまとめていきたいと思います。


#使用環境
Nuxt　(version: 2.11.0）
Vuetify　(version: 2.1.15）
firestore

#やりたかったこと
* 相当数(1000以上)のデータが格納されたDBから、必要に応じた数を指定した範囲でクエリしたい。
* もちろんソートをかけた時に、DB全てが対象にしたい。
* ソートをかけた際に、同じ値のものが複数ある時にも対応したい。
* 部分一致検索も出来るようにしたい。
* 検索も特定のフィールドだけではなく、必要に応じてフィールドを選べるようにしたい。
* けど、Algoria等の外部APIは使用したくない。
* リロードが発生しても、状態を維持できるようにしたい。
* ページ番号も振りたい。

#先に結論だけ言うと


* ページ遷移によるクエリ範囲の変更は可能。
* ソートも出来る。ただし、同じ値が複数ある場合はフィールドが２つ必要。
* 完全な部分一致は出来ないが、前方一致は出来る。
* `Vuex`で保存すればリロードを挟んでも維持できる。
* ページ番号は振れるのですが、ページの総数までは抜き出すことができませんでした。（強がりを言うと、実装することは出来るのですが、データ数が多くなると現実的ではないと考えました…）

となります。

以下見にくいかもしれませんが、成果物のgifを貼っておきます。

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/5faa8e65-f8c8-52b1-aa17-510e2c6eeaa7.gif)


#今回省略する部分
* firebaseのインストール方法
* nuxtアプリの生成方法

どちらも拙記事に掲載していますので、気になる方はそちらを確認してください。
[nuxtアプリの生成方法](https://qiita.com/sychocola1/items/6aa185960a9f4e40d3d0)
[firebase関係のインストール方法](https://qiita.com/sychocola1/items/e7ca6a0bac16a9f8f3fa)


前置きが長くなりましたが、本題に入ります。


##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`の２つのテーブルデータをそれぞれ違う`pages`を用いて表示させます。

###ファイルの相関

<img width="1511" alt="スクリーンショット 2020-05-03 16.03.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/9bf908f0-c913-1475-0d9e-ba93500d1e4b.png">
雑把に言うとこのような相関関係になります。

##1. ページ遷移の方法
今回はクエリした内容を`Vuetify`の`v-data-table`に渡していきます。
まずは全体のコードです。

```pages/index.vue
<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>
```

```pages/Book.vue
<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>
```

```components/GetData.vue
<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>
```

```components/Table.vue
<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>
```

少し長くなりましたが、この時点でほぼ全ての実装ができています。

では中身を１つ１つ見ていきましょう。

###大まかな流れ
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`を設定します。

```components/GetData.vue
<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`毎に定義しておきます。

```components/GetData.vue
<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から生のデータを受け取ります。
現在は素直に受け取るだけなので特に難しいことはないですが、後々に弄りやすい形に意図的にしています。
ただしクエリ文は、重複を許せるように２つのフィールドで`orderBy`でクエリするようにしています。
詳しくは[公式ドキュメント](https://cloud.google.com/firestore/docs/query-data/query-cursors?hl=ja)を確認してください。
流れとしては、

* `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`にします。

以上の４ステップです。

```components/GetData.vue
<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に飛ぶと

<img width="1680" alt="スクリーンショット 2020-05-05 16.35.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/b39ca424-0d66-d001-2b73-1dd7c7392e50.png">


このような画面に飛ばされると思います。
この指示に従い、「インデックスを作成」を押すと複合インデックスが自動生成され、数分後にはエラーが起きずに予期したクエリが行われます。

####iv. TableにOriginListを渡す
あとはこれを`Table`に渡して、表示させるだけです。
ただし、先ほど定義した`first`と`last`の有無に応じて「←ボタン」や「→ボタン」を押せなくする必要があります。
また、リストに名前を表示させたいので、`mounted`時に、各`seed`によって`listName`が変化するようにしましょう。

```components/Table.vue
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`メソッドが発火します。

```components/GetData.vue
<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のクエリ機能はまだ充実してはいないため、完全な部分一致検索はできません。ですが、既存のクエリ機能を組み合わせて前方一致のみは実現可能です。
また、ソート機能はそれ単体であるのではなく、検索機能で「**''**」を検索した時に作動する感じです。

###前方一致検索のクエリの仕組み

```js
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.**であったように、最初の２つの`orderBy`で`filterseed`順、`createdAt`順に並べた後に、`where`で始まるポイントと終わるポイントを決めます。
その後、`startAfter`でページ遷移する際の基準となるポイントを指定します。
イメージとしては以下のような感じです。

<img width="705" alt="スクリーンショット 2020-05-05 17.32.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/1e29f955-a853-c0d5-806d-bb777cc9289c.png">


ではこれを組み込んでみます。

```components/GetData.vue
<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>
```

```components/Table.vue
<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の詳しい内容は[こちら](https://qiita.com/sychocola1/items/d3f41ac0fdf19c1a4764)をご覧ください。

今回状態の保存が必要な変数は、**fuid, luid, page, path, filterseed, orderItem, order, baseitem, search**です。

では`store/index.js`に記述していきます。

```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`を設定します。

```/midlware/index.js
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: '' })
    }
  }
}
```

```/nuxt.config.js
export default {

// ...略

  router: {
    middleware: 'index'
  },

// ...略

}
```
こうすることで、リロード時には状態を保存できるが画面が遷移した時にはリセット出来ます。


では`store`の値を使うように`components`の記述も変えていきます。

```components/Getdata.vue
<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>

```

```components/Table.vue
<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`で実装してみます。

わからない点・間違えている点・より良い方法などありましたらご指摘いただけると泣いて喜びます！！