【Firebase】検索処理と検索結果でページング処理【タイムライン】

前書き

前回こういった記事を書きました。
次は検索機能を追加し、検索ワードに絞って、タイムライン機能を実装させたいといった内容です。
前回の記事を見ないと「なんのこっちゃ」になるかもしれません。

melheaven.hatenadiary.jp

【実装】 Home.vue

<v-form color="primary">
        <v-text-field v-model="searchWord" placeholder="1ワードまで" label="検索ワード(タイトル名)" type="text" />
        <v-text-field v-model="searchUser" placeholder="1ユーザーまで" label="検索ユーザー" type="text" />
        <p>検索タイトル「{{searchWordInput}}」 検索ユーザー「{{searchUserInput}}」で検索します!!</p>
        <v-btn @click="search" color="primary">検索</v-btn>
 </v-form>
data() {
    return {
      pagingToken: "",
      searchWord: "",
      searchUser: "",
      maps_data: null
    };
  },
computed: {
    searchWordInput: function() {
      return this.searchWord;
    },
    searchUserInput: function() {
      return this.searchUser;
    }
  },
}

検索フォームを作成しました。検索欄の中身が変わると、computedプロパティで検知してくれます。

search: async function() {
      this.pagingToken = "";
      // 検索欄の中身が空白の場合→全検索
      if (this.searchWordInput != "" || this.searchUserInput != "") {
        let data = await getSearchData(
          3,
          this.searchWordInput,
          this.searchUserInput,
          this.pagingToken
        );
        let buffData = await downloadImageToBox(data.BuffData);
        this.maps_data = buffData;
        this.pagingToken = data.nextPageToken;
      } 
  // 検索欄の中身が空白でない場合→キーワード検索
  else {
        let data = await getAllData(3, this.pagingToken);
        let buffData = await downloadImageToBox(data.BuffData);
        this.maps_data = buffData;
        this.pagingToken = data.nextPageToken;
      }
    }

ここでは検索ボタンを押した後の処理をmethodsに記述しています。
検索欄の中身が空白でない場合、キーワード検索を実行するようにします。
前回の記事で説明しましたが、pagingTokenはTLに表示されるべき最後の投稿の日時を格納しています。
ページング処理ではロードボタンを押した時に取得する投稿は、pagingTokenの時間情報を頼りに取得します。

melheaven.hatenadiary.jp

しかし検索ボタンを押す際はpagingTokenをリセットする必要があります。

【実装】auth.js

export async function getSearchData(limit, searchWord, searchUser, pagingToken) {
    let nextToken = "";
    let query = db.collection("comments").orderBy('createdAt')
    if (pagingToken != "") {
        const [seconds, nanoseconds] = pagingToken.split(':');
        const timestamp = new firebase.firestore.Timestamp(seconds, nanoseconds);
        query = query.startAfter(timestamp);
    }
    if (searchUser == "" && searchWord != "") {
        query = query.where('title', '==', searchWord).limit(limit);
    }
    else if (searchUser != "" && searchWord == "") {
        query = query.where('displayName', '==', searchUser).limit(limit);
    }
    else if (searchUser != "" && searchWord != "") {
        query = query.where('displayName', '==', searchUser).where('title', '==', searchWord).limit(limit);
    }


    const result = await query.get().then((snapshot) => {
        if (snapshot.docs.length >= limit) {
            const last = snapshot.docs[snapshot.docs.length - 1];
            const lastData = last.data();
            const time = lastData.createdAt;
            nextToken = `${time.seconds}:${time.nanoseconds}`;
        }
        return { "BuffData": snapshot, "nextPageToken": nextToken };
    }).catch(() => {
        alert("エラーが発見されました:データ取得時");
    });
    return result;
}

解説

検索欄に文字が存在する場合に検索ボタンを押した時の処理です。
前回の全検索におけるページング処理と大体が同じ構造になっています。

検索処理

let query = db.collection("comments").orderBy('createdAt')
・・・
if (searchUser == "" && searchWord != "") {
        query = query.where('title', '==', searchWord).limit(limit);
    }
    else if (searchUser != "" && searchWord == "") {
        query = query.where('displayName', '==', searchUser).limit(limit);
    }
    else if (searchUser != "" && searchWord != "") {
        query = query.where('displayName', '==', searchUser).where('title', '==', searchWord).limit(limit);
    }

こちらでは取得したキーワード(ワードorユーザー)ごとに検索を行います。
正直もう少し綺麗なコードを書けるだろと思ったのですが、妥協しました。
また部分検索(Like検索)はFireStoreでは使えないようなので諦め。
キーワードの先頭部分一致は実現できるようですが、以下の記事を参考に。

qiita.com

結果

f:id:electric-city:20201211123306p:plain
検索前のTLの状態

投稿ユーザーを”Nagoya”に指定すると、"名古屋"がタイトルの投稿になります。

f:id:electric-city:20201211123858p:plain
検索後