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









いいなと思ったら応援しよう!