やりたいこと
Nuxt.jsとfirestoreを使用して、リアルタイムでダッシュボードを更新するシステムを作る。
リロードとかせずサクサクと!
firestoreの作成
コンソールからプロジェクトを作成して設定
※あとで書く
準備
①firebaseをインストール
yarn add firebase
or npm install firebase --save
②plugins/firebase.jsの作成
firebaseの初期設定をします!
firebaseのページの設定画面下部にある情報をそのまま貼り付けてます。
開発環境と本番環境でfirebaseのアカウントを使い分けるためにENVにしています。
.env
で書いた環境変数名を書きます
import firebase from 'firebase/app'
import 'firebase/firestore'
firebase.initializeApp({
apiKey: process.env.FIRE_BASE_API_KEY,
authDomain: process.env.FIRE_BASE_AUTH_DOMAIN,
databaseURL: process.env.FIRE_BASE_DATABASE_URL,
projectId: process.env.FIRE_BASE_PROJECT_ID,
storageBucket: process.env.FIRE_BASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIRE_BASE_MESSAGING_SENDER_ID,
appId: process.env.FIRE_BASE_APP_ID,
measurementId: process.env.FIRE_BASE_MEASUREMENT_ID
})
const db = firebase.firestore()
export default db
③nuxt.config.jsへ追記
...省略
plugins: [
...
{ src: '~/plugins/firebase' }
],
...省略
これでfirestoreを使う準備が整いました!
store/firestoreディレクトリを用意する
私はstore配下をAPI毎にディレクトリを切って、actions.js/getters.js/index.js/mutations.js
と分けて書く方法が好きです。
理由は見やすいし追いやすいからです。
今回はfirestoreの通信についてなので、firestoreとディレクトリを切ります。
まずはactions.js
import db from '~/plugins/firebase'
// コレクション名をセット
const fireStore = db.collection('dashboard')
export const actions = {
GET_REALTIME_LIST({ commit }, payload) {
fireStore
.where('report_datetime', '>', payload.from)
.where('report_datetime', '<', payload.to)
.orderBy('report_datetime', 'asc')
.orderBy('created_at', 'asc')
.onSnapshot(
res => {
commit('GET_REALTIME_LIST', res)
},
error => {
console.error('GET_REALTIME_LIST', error)
}
)
},
UPDATE_FIRE_STORE({ commit }, payload) {
fireStore
.doc(payload.id)
.set(payload)
.then(res => {
commit('UPDATE_FIRE_STORE', res)
})
.catch(e => {
console.error('UPDATE_FIRE_STORE', e)
})
}
}
export default actions
const fireStore = db.collection('dashboard')
ここで接続をするコレクション名をセットします。
今回のコレクション名はdashboard
なのでこのように記述。
onSnapshot
これがリアルタイム同期をするポイントです。
他にも追加されたものだけ取得、削除されたものだけ取得、とか書き方はありますが
今回のダッシュボードは全体を常に同期していたいのでこう書いてます。
また、firestoreではあまり柔軟にクエリが書けません。
between句もありません><
そこで
.where('report_datetime', '>', payload.from)
.where('report_datetime', '<', payload.to)
このように書くと、between句のように使えます。
次はmutations.js
import {
GET_REALTIME_INCOMPLETE_LIST_FIRE_STORE,
UPDATE_FIRE_STORE
} from '~/store/types'
export const mutations = {
GET_REALTIME_LIST(state, content) {
state.realtimeFireStore = content.docs.map(res => {
const fireStore = res.data()
fireStore.id = res.id
return fireStore
})
},
UPDATE_FIRE_STORE(state, content) {
state.updateFireStore = content
}
}
export default mutations
actionsでmutationsにcommitする際に、res.docsとやってもmutationsでmapで回す前にやっても大丈夫です。
responseではdocsの中に一覧が返ってきます。
そのまま直接参照することは出来ないので、mapで回しながらdata()とやってあげます。
fireStore.id = res.id
ここでなぜidを入れているかというと
データを入れる際に、自動生成IDで作成しているので自動生成された一意のIDがデータ内に入っていません。
保持をしておかないとupdateで使用する際に困るので、データ内に突っ込んでます。
自動生成されたIDは、docs内にあります。IDはdata()で参照する中にはなく直接参照できるので、上記のように書きます。
続いてgetters.js
export const getters = {
getRealtimeFireStore: state => state.realtimeFireStore,
getUpdateFireStore: state => state.updateFireStore
}
export default getters
ここは特にポイントはありません
続いてindex.js
import Actions from './actions'
import Getters from './getters'
import Mutations from './mutations'
export const state = () => ({
realtimeFireStore: [],
updateFireStore: ''
})
export const actions = Actions
export const getters = Getters
export const mutations = Mutations
ここも特にポイントはありません。
これでstoreに準備完了です。
pages/dashboard.vueの作成
さて、やっと画面に取り掛かります。
<template>
<div>
<div class="contents">
<h1 class="title">テスト</h1>
</div>
<div>
<table class="dashboard">
<tr v-for="(dashboardRow, rowIndex) in dashboards" :key="rowIndex">
<td v-for="(dashboard, dashboardIndex) in dashboardRow" :key="dashboard.id">
<div class="dashboard-card">
<input type="text" :value="dashboard.title" @blur="save(rowIndex, dashboardIndex)" />
</div>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
import moment from 'moment'
import { mapGetters, mapActions } from 'vuex'
import chunk from 'lodash/chunk'
import cloneDeep from 'lodash/cloneDeep'
export default {
name: "dashboard",
computed: {
...mapGetters('firestore/', [
'getUpdateFireStore',
'getRealtimeFireStore'
]),
dashboards: {
get(){
const getFireStore = cloneDeep(this.getRealtimeFireStore)
const dashboards = chunk(filterData, 3).map((dashboardItems, index) => {
if (dashboardItems.length !== 3) {
for (let i = 0; i < 4 - dashboardItems.length; i++) {
dashboardItems.push({})
}
}
return dashboardItems
})
return dashboards
},
set(dashboards){
return dashboards
}
}
},
created() {
this.getDashboard()
},
methods: {
...mapActions('firestore/', [
'UPDATE_FIRE_STORE',
'GET_REALTIME_LIST'
]),
async getDashboard() {
this.GET_REALTIME_LIST({
from: moment().subtract(3, 'days').format('YYYY-MM-DD 00:00:00'),
to: moment().format('YYYY-MM-DD 23:59:59')
})
},
async save(rowIndex, dashboardIndex) {
await this.UPDATE_FIRE_STORE(this.dashboards[rowIndex][dashboardIndex])
},
}
}
</script>
<style scoped>
table.dashboard {
width: 100%;
margin-top: 40px;
}
table.dashboard td {
width: 30%;
padding: 20px 14px;
}
.dashboard-card {
border: 1px solid #cecece;
border-radius: 4px;
padding: 10px 6px;
background-color: #f7f1f1;
box-shadow: 0px 0px 9px 0px #afafaf;
}
</style>
直近3日のデータを取得しています。
created() {
this.getDashboard()
},
createdでリアルタイムでgetするactions: GET_REALTIME_LIST しているメソッドを呼びます。
これで常にリアルタイム通信が可能となります。
computed内で、リアルタイムでgetしているデータを(gettersのgetRealtimeFireStore)を加工してあげます。
横3個ずつズラ〜と出したいのでlodashのchunkでわけてあげます。
カード内のテキストボックスでは、文字firestoreに入れてるデータのtitleを表示しています。
この値を変更してフォーカスを外してタイミングで更新メソッド(save)を呼んでいます。
actions:UPDATE_FIRE_STOREを呼んで、更新処理を行います。
この更新がかかると、今actions:GET_REALTIME_LISTがリアルタイムで取得しているので、
getters: getRealtimeFireStoreの値が変更されるため
computedのdashboardsが動き、画面上のカードがリアルタイムで変更されます。
リアルタイムで動いている確認方法
方法①このlocalhostを2つ開き、画面に分けて表示して
片方で更新かけて、もう1つの方でリロードなしに変更がされれば成功です!
方法②firestoreのコンソールで直接データを変更して、リロードなしにダッシュボード内のカードが更新されれば成功です!
以上でリアルタイムダッシュボードの実装は終わりです!