VueCLIでアプリ作成(アドレス帳)④
ログアウト実装
src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import firebase from "firebase";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
login_user: null,
drawer: false,
addresses: [],
},
mutations: {
setLoginUser(state, user) {
state.login_user = user;
},
deleteLoginUser(state) {
state.login_user = null;
},
toggleSideMenu(state) {
state.drawer = !state.drawer;
},
addAddress(state, address) {
state.addresses.push(address);
},
},
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
logout() {
firebase.auth().signOut();
},
login() {
const google_auth_provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(google_auth_provider);
},
toggleSideMenu({ commit }) {
commit("toggleSideMenu");
},
addAddress({ commit }, address) {
commit("addAddress", address);
},
},
});
①ログインユーザーにnullをいれる
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
・actions内にdeleteLoginUserメソッドでコミット
mutations: {
setLoginUser(state, user) {
state.login_user = user;
},
deleteLoginUser(state) {
state.login_user = null;
},
・mutaitions内でstateの状態を変える
②firebaseのサインアウト機能を実装
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
logout() {
firebase.auth().signOut();
},
③1と2で実装した機能をsrc/App.vueに搭載
<template>
<v-app>
<v-toolbar app>
<v-toolbar-side-icon @click.stop="toggleSideMenu"></v-toolbar-side-icon>
<v-toolbar-title class="headline text-uppercase">
<span>マイアドレス帳</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="$store.state.login_user">
<v-btn @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-toolbar>
<SideNav/>
<v-content>
<router-view/>
</v-content>
</v-app>
</template>
<script>
import firebase from 'firebase'
import SideNav from './components/SideNav'
import { mapActions } from 'vuex'
export default {
name: 'App',
components: {
SideNav
},
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
} else {
this.deleteLoginUser()
}
})
},
data () {
return {
//
}
},
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser'])
}
}
</script>
1.methods内のマップアクションでストアアクションlogout, deleteLoginUser追加
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser'])
}
2.template内にログアウトボタン設置
<v-toolbar-items>
<v-btn @click="logout">ログアウト</v-btn>
</v-toolbar-items>
3.firebaseのonAuthStateChangedでuserの状態を管理
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
} else {
this.deleteLoginUser()
}
})
},
4.ログインしている時だけログアウトボタンを表示させるようにtemplate修正
<v-toolbar-items v-if="$store.state.login_user">
<v-btn @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-toolbar>
ストアからユーザー名とユーザーの画像を取得
src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import firebase from "firebase";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
login_user: null,
drawer: false,
addresses: [],
},
mutations: {
setLoginUser(state, user) {
state.login_user = user;
},
deleteLoginUser(state) {
state.login_user = null;
},
toggleSideMenu(state) {
state.drawer = !state.drawer;
},
addAddress(state, address) {
state.addresses.push(address);
},
},
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
login() {
const google_auth_provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(google_auth_provider);
},
logout() {
firebase.auth().signOut();
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
toggleSideMenu({ commit }) {
commit("toggleSideMenu");
},
addAddress({ commit }, address) {
commit("addAddress", address);
},
},
getters: {
userName: (state) => (state.login_user ? state.login_user.displayName : ""),
photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
},
});
①gettersメソッドを定義する。gettersメソッドはstateを受け取るので、三項演算子(条件 ? 真の処理:偽の処理)で名前と画像を取得
getters: {
userName: (state) => (state.login_user ? state.login_user.displayName : ""),
photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
},
②サイドNavに取得したユーザー名・画像を表示
src/components/SideNav.vue
<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list class="pa-1">
<v-list-tile avatar>
<v-list-tile-avatar>
<img v-if="photoURL" :src="photoURL">
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>{{ userName }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-list class="pt-0" dense>
<v-divider></v-divider>
<v-list-tile v-for="item in items" :key="item.title" :to="item.link">
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
data () {
return {
items: [
{ title:'ホーム', icon:'home', link:{ name:'home'}},
{ title: '連絡先一覧', icon: 'list',link:{name:'addresses'}}
]
}
},
computed:{
...mapGetters(['userName','photoURL'])
}
}
</script>
1.ストアのgettersをインポート
import {mapGetters} from 'vuex'
2.computedメソッド内で使用するmapgettersを指定
comutedに組み込むことで、コンポーネントのプロパティとして使用できる。
computed:{
...mapGetters(['userName','photoURL'])
}
3.template書き換え(アイコン画像・名前)
<v-list-tile avatar>
<v-list-tile-avatar>
<img v-if="photoURL" :src="photoURL">
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>{{ userName }}</v-list-tile-title>
</v-list-tile-content>
ログインによって画面の切り替え(ルートの制御)
src/App.vue
<template>
<v-app>
<v-toolbar app>
<v-toolbar-side-icon v-show="$store.state.login_user" @click.stop="toggleSideMenu"></v-toolbar-side-icon>
<v-toolbar-title class="headline text-uppercase">
<span>マイアドレス帳</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="$store.state.login_user">
<v-btn @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-toolbar>
<SideNav/>
<v-content>
<router-view/>
</v-content>
</v-app>
</template>
<script>
import firebase from 'firebase'
import SideNav from './components/SideNav'
import { mapActions } from 'vuex'
export default {
name: 'App',
components: {
SideNav
},
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
if(this.$router.currentRoute.name === 'home')this.$router.push({name:'addresses'})
} else {
this.deleteLoginUser()
this.$router.push({name:'home'})
}
})
},
data () {
return {
//
}
},
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser'])
}
}
</script>
①created内のonAuthStateChangedメソッドでログインを制御しているので、ここにルートの指示も足す
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
if(this.$router.currentRoute.name === 'home')this.$router.push({name:'addresses'})
} else {
this.deleteLoginUser()
this.$router.push({name:'home'})
}
})
},
②ログインしている時だけメニューアイコンを表示させる
<v-toolbar-side-icon v-show="$store.state.login_user" @click.stop="toggleSideMenu"></v-toolbar-side-icon>
cloud FireStoreでのデータベース設定
asia-northeast1 東京
この画面で読み書きのルール設定。firestoreではデータの保存場所をパスの形式で指定。今回は各ユーザーの連絡先情報は以下の階層に保存。
/users/{userId}/addresses/{addressId}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/addresses\{addressId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid !=null
}
}
}
①データベースのルートパスを記述
match /users/{userId}/addresses/{addressId}
②認証済みのユーザーがパス(DB)のuserIDと同じなら読み、更新、削除を許可(自分の階層のみ許可)
allow read, update, delete: if request.auth.uid == userId;
③認証済みユーザーであれば、データ作成可能。(今回であればGoogleでログインしていればデータ作成可能。していなければできない。)
allow create: if request.auth.uid !=null
VuexのストアのデータをFirestoreへ保存
src/store/index.js
保存先はパスで指定する。/users/{userId}/addresses/の配下にユーザーごとのアドレスを追加していく。
①ユーザーIDの取得。ここでつかうユーザIDはログインユーザーのオブジェクト内にuidという名前で持っているので、uidを取得するgetterを定義
getters: {
userName: (state) => (state.login_user ? state.login_user.displayName : ""),
photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
uid: (state) => (state.login_user ? state.login_user.uid : null),
},
②addAddressのアクションに処理を追加してFirestoreへデータを保存。
addAddress({ getters, commit }, address) {
if (getters.uid)
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.add(address);
commit("addAddress", address);
},
1.addAddress({ getters,commit }, address)
アクションメソッドの引数のコンテキストオブジェクトにはgettersも含まれているので、{ getters,commit }と書くことでgettersも取得する
2.if(getters.uid) gettersからuidが取得できたら、
firebase.firestore(). Firestoreに
collection(`users/${getters.uid}/addresses`). collectionメソッドでDBのパスを書いて add(address)でaddressを追加
※ここのパスが違うとデータベース利用できないので注意★
3.firestoreでコレクションの開始を作成
/users/{userId}/addresses/{addressId} の{}は自動IDを選択
4.ブラウザからデータを保存。(ブラウザリロードは消える)
Firestoreからデータ取得
src/store/index.js
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
fetchAddresses({ getters, commit }) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.get()
.then((snapshot) => {
snapshot.forEach((doc) => commit("addAddress", doc.data()));
});
},
データ保存時と同様、collectionメソッドでパスを指定。get()メソッドで非同期に取得して、then()メソッドに渡した引数の関数でデータを取得できる。
.collection(`users/${getters.uid}/addresses`)
.get()
.then((snapshot) => {
snapshot変数にアドレスのデータが格納されているので、foreachで1つ1つ取得する。
snapshot.forEach((doc) => commit("addAddress", doc.data()));
snapshotは配列ではなく、クエリースナップショットというオブジェクト。
foreachで各要素をdoc変数にいれていますが、このdocに直でデータが入っているわかではなく、doc.data()とすることでデータを取得する。
これでデータをfirestoreからダウンロードができるようになります。
fetchAddressesアクションを実装
src/App.vue
<template>
<v-app>
<v-toolbar app>
<v-toolbar-side-icon v-show="$store.state.login_user" @click.stop="toggleSideMenu"></v-toolbar-side-icon>
<v-toolbar-title class="headline text-uppercase">
<span>マイアドレス帳</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="$store.state.login_user">
<v-btn @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-toolbar>
<SideNav/>
<v-content>
<router-view/>
</v-content>
</v-app>
</template>
<script>
import firebase from 'firebase'
import SideNav from './components/SideNav'
import { mapActions } from 'vuex'
export default {
name: 'App',
components: {
SideNav
},
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
this.fetchAddresses()
if(this.$router.currentRoute.name === 'home')this.$router.push({name:'addresses'})
} else {
this.deleteLoginUser()
this.$router.push({name:'home'})
}
})
},
data () {
return {
//
}
},
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser','fetchAddresses'])
}
}
</script>
①actionsでmapactionsにfetchAddressesを追加
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser','fetchAddresses'])
}
②認証時にfetchAddresses()を追加
created () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setLoginUser(user)
this.fetchAddresses()
if(this.$router.currentRoute.name === 'home')this.$router.push({name:'addresses'})
} else {
this.deleteLoginUser()
this.$router.push({name:'home'})
}
})
},
ブラウザでログアウトして、ログインしてもデータが残っていればOK。
データの編集機能
ストアの変更
src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import firebase from "firebase";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
login_user: null,
drawer: false,
addresses: [],
},
mutations: {
setLoginUser(state, user) {
state.login_user = user;
},
deleteLoginUser(state) {
state.login_user = null;
},
toggleSideMenu(state) {
state.drawer = !state.drawer;
},
addAddress(state, { id, address }) {
address.id = id;
state.addresses.push(address);
},
},
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
fetchAddresses({ getters, commit }) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.get()
.then((snapshot) => {
snapshot.forEach((doc) =>
commit("addAddress", { id: doc.id, address: doc.data() })
);
});
},
login() {
const google_auth_provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(google_auth_provider);
},
logout() {
firebase.auth().signOut();
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
toggleSideMenu({ commit }) {
commit("toggleSideMenu");
},
addAddress({ getters, commit }, address) {
if (getters.uid) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.add(address)
.then((doc) => {
commit("addAddress", { id: doc.id, address });
});
}
},
},
getters: {
userName: (state) => (state.login_user ? state.login_user.displayName : ""),
photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
uid: (state) => (state.login_user ? state.login_user.uid : null),
},
});
①連絡先のオブジェクトにidを追加。下のadd(address)でfirestoreで自動生成されたidを取得できるので、idも一緒にmutaitionにわたるように変更。
addAddress({ getters, commit }, address) {
if (getters.uid)
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.add(address);
commit("addAddress", address);
},
↓
addAddress({ getters, commit }, address) {
if (getters.uid) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.add(address)
.then((doc) => {
commit("addAddress", { id: doc.id, address });
});
}
},
addressのデータはdoc変数で受け取り、commitには第2引数で渡すために{ id: doc.id, address }とオブジェクト形式で記述。
②mutation側もIDを取得できるように設定。
addAddress(state, { id, address }) {
address.id = id;
state.addresses.push(address);
},
引数をstate,{id,address}にする。address.id =idを追記することで、addressにidを含めることができる。
③fetchAddressesにもid追加
fetchAddresses({ getters, commit }) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.get()
.then((snapshot) => {
snapshot.forEach((doc) =>
commit("addAddress", { id: doc.id, address: doc.data() })
);
});
},
src/addresses.vueで操作ボタン追加
<template>
<v-container text-xs-center justify-center>
<v-layout row wrap>
<v-flex xs12>
<h1>連絡先一覧</h1>
</v-flex>
<v-flex xs12 mt-5 mr-5 text-xs-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>
<v-flex xs12 mt-3 justify-center>
<v-data-table :headers='headers' :items='addresses'>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.name }}</td>
<td class="text-xs-left">{{ props.item.tel }}</td>
<td class="text-xs-left">{{ props.item.email }}</td>
<td class="text-xs-left">{{ props.item.address }}</td>
<td class="text-xs-left">
<span>
<router-link :to="{name:'address_edit',params:{address_id:props.item.id}}">
<v-icon small class="mr-2">edit</v-icon>
</router-link>
</span>
</td>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
created () {
this.addresses = this.$store.state.addresses
},
data () {
return {
headers: [
{ text: '名前', value: 'name' },
{ text: '電話番号', value: 'tel' },
{ text: 'メールアドレス', value: 'email' },
{ text: '住所', value: 'address' },
{ text:'操作',sortable:false}
],
addresses: []
}
}
}
</script>
<style scoped lang="scss">
a{
text-decoration: none;
}
</style>
追加したのはここ
<td class="text-xs-left">
<span>
<router-link :to="{name:'address_edit',params:{address_id:props.item.id}}">
<v-icon small class="mr-2">edit</v-icon>
</router-link>
</span>
</td>
:to="{name:'address_edit'は編集ページへのリンク、params:{address_id:props.item.id}}は連絡先IDをaddress_idにいれる。
編集ページにデータを反映させる
src/store/index.jsのgettersメソッドに追記
import Vue from "vue";
import Vuex from "vuex";
import firebase from "firebase";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
login_user: null,
drawer: false,
addresses: [],
},
mutations: {
setLoginUser(state, user) {
state.login_user = user;
},
deleteLoginUser(state) {
state.login_user = null;
},
toggleSideMenu(state) {
state.drawer = !state.drawer;
},
addAddress(state, { id, address }) {
address.id = id;
state.addresses.push(address);
},
},
actions: {
setLoginUser({ commit }, user) {
commit("setLoginUser", user);
},
fetchAddresses({ getters, commit }) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.get()
.then((snapshot) => {
snapshot.forEach((doc) =>
commit("addAddress", { id: doc.id, address: doc.data() })
);
});
},
login() {
const google_auth_provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(google_auth_provider);
},
logout() {
firebase.auth().signOut();
},
deleteLoginUser({ commit }) {
commit("deleteLoginUser");
},
toggleSideMenu({ commit }) {
commit("toggleSideMenu");
},
addAddress({ getters, commit }, address) {
if (getters.uid) {
firebase
.firestore()
.collection(`users/${getters.uid}/addresses`)
.add(address)
.then((doc) => {
commit("addAddress", { id: doc.id, address });
});
}
},
},
getters: {
userName: (state) => (state.login_user ? state.login_user.displayName : ""),
photoURL: (state) => (state.login_user ? state.login_user.photoURL : ""),
uid: (state) => (state.login_user ? state.login_user.uid : null),
getAddressById: (state) => (id) =>
state.addresses.find((address) => address.id === id),
},
});
getAddressById: (state) => (id) =>
state.addresses.find((address) => address.id === id),
src/views/AddressForm.vue