Node.jsのpostとgetで詰まったところ

やりたいこと

 

トップページ(top.ejs)内の検索ボックス(inputタグ)に入力した値を使用して検索をかける。

検索ワードを含む記事をデータベースから取得し一覧(d_search.ejs)で表示させる。

 

試したこと

 

一覧を表示させるために

d_search.jsを用意

このファイルはtop.ejsからpostした内容の処理と一覧画面を表示させるgetの処理を記述している。

 

d_search.js

const express = require('express');
const router = express.Router();
const connection = require('../mysqlConnection');
const multer = require('multer');//画像取得ようパッケージ(multer)読み込み
const moment = require('moment');//日付取得用パッケージ読み込み

 
router.post('/', (req, res) => {
// export let search = req.body.search;
console.log(req.body.search)//inputタグの値が入る
console.log("post検索:"+ req.body.search)
res.redirect('/d_search')//ここで下のget処理に移動する
});


router.get('/',(req,res)=>{
// import { search } from './d_search.js';
//ここに上のpostで取得した値を取得したい。
var sql = 'SELECT D.id, D.user_id, D.title, D.image, D.tag, D.text,ifnull(U.name, \'名無し\') AS name, DATE_FORMAT(D.created_at, \'%Y年%m月%d日 %k時%i分%s秒\') AS created_at FROM diary D LEFT OUTER JOIN user U ON D.user_id = U.user_id ORDER BY D.created_at DESC'; // 変更
connection.query(sql,(error,results)=>{
res.render('d_search.ejs',{items:results})
// console.log(results)
console.log('get検索結果表示')
});
})

module.exports = router;

 

top.ejs(今回使用している部分を抜粋)

<form action="/d_search" method='post'>
<span class='search-bar'>店舗検索</span><input type="text" name='search' placeholder="ワード入力" required>
<input type="submit" value='検索'>
</form>

 

 

ターミナルで確認すると、検索の値をpostした時、

req.body.search

に検索した文字の値が格納されることを確認。

この値を変数に入れて、postの下のget記述に持っていき、var sqlで定義しているSELECT文の後半に WHERE title LIKE '%「postした変数」%'

とすれば検索結果を取得出来るのではと考えた。

 

しかし、d_search.jsのpostで

export let search = req.body.search;

を定義し

d_search.jsのgetで

import { search } from './d_search.js';

をimportしようとしても

SyntaxError: Unexpected token 'export'

のエラーがターミナルに表示される。

 

inputタグでpostした値をsqlで使用するにはどう言った処理が必要なのか

検討を続ける。

 

Vue.js 連絡先追加フォームのfirebase連携

以下でフロントを実装した連絡先フォームをfirebaseと連携する

 

kirikko-scondcube.hatenablog.com

 

firebaseセットアップ

firebaseコンソールからプロジェクトの追加を選択。

f:id:kirikko_Scondcube:20200803020452p:plain

必要事項を選択し、プロジェクトを作成後、webアプリを選択で上記画面が出る。

このスクリプトをコピーしVue cliのpublic/index.htmlのbodyタグのとじタグの上へ貼り付ける。

これでVue.jsでfirebaseを利用する下準備は完了

 

 

firebaseのライブラリの設定

前述でfirebaseの設定をしただけだと読み込みのタイミングでエラーが出ることがある。そのため、Vueに適した内容にfirebaseをセットアップする

 

コンソールで

npm install firebase

 

インストール後、index.htmlの前述で貼り付けたfirebaseのスクリプトを全て切り取り、src/main.jsへ移動する。

移動先はVue.config.productiontTipの下

そしてそのまま移動するとエラー表示が出る(波下線など)。

そこで<script>タグを削除しconfigのみを残す。

そしてデフォルトのvarをconstとし、firebaseをインポートする

 

src/main.js(keyなどの記述は削除しているが配置は以下)

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify';
import firebase from 'firebase';


Vue.config.productionTip = false



// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);



new Vue({
router,

//router:router,と同じ意味上は省略の記載方法
store,

vuetify,
render: h => h(App)
}).$mount('#app')

以上でセットアップ完了

 

Google認証の設定方法

firebase consoleにいく。

プロジェクトを選択

サイドメニューのAuthenticationを選択

sign in methodを選択する。

「無効」となっているGoogleを選択すると以下の画面になる。

f:id:kirikko_Scondcube:20200803023204p:plain

右上のバーを押して無効→有効にするに変更。

プロジェクト名を任意で記述する。

プロジェクトのサポートメールに自身のメールアドレスを入力する。

右下の保存ボタンを押して認証が有効になる。

 

 

firebaseログイン認証の実装

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: {
drawer: false,
addresses:

},
mutations: {
toggleSideMenu(state){
state.drawer = !state.drawer
},
addAddress (state, address) {
state.addresses.push(address)
}
},
actions: {
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)
}
},
modules: {
}
})

firebase をインポートする

import firebase from 'firebase'

アクションにログイン処理を記入

login(){
const google_auth_provider = new firebase.auth.GoogleAuthProvider()
firebase.auth().signInWithRedirect(google_auth_provider)
},

 

 

views/Home.vue

<template>
<v-container text-xs-center justify-center>
<v-layout row wrap>
<v-flex xs12>
<h1>マイアドレス帳</h1>
<p>マイアドレス帳をご利用の方は、Googleアカウントでログインしてください。</p>
</v-flex>

<v-flex xs12 mt-5>
<v-btn color='info' @click="login">Googleアカウントでログイン</v-btn>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
// @ is an alias to /src @/components以下でファイルを指定すると参照できるようになるよと書いてある。
import{mapActions} from 'vuex'

export default {
methods:{
...mapActions(['login'])
}}
</script>

ログイン用のカードをvitifyから引用

googleアカウントでログインのボタンにクリックイベントを持たせ、押すとloginアクションが実行されるようにする

<v-container text-xs-center justify-center>
<v-layout row wrap>
<v-flex xs12>
<h1>マイアドレス帳</h1>
<p>マイアドレス帳をご利用の方は、Googleアカウントでログインしてください。</p>
</v-flex>

<v-flex xs12 mt-5>
<v-btn color='info' @click="login">Googleアカウントでログイン</v-btn>
</v-flex>
</v-layout>
</v-container>

 

vuexをインポートする

import{mapActions} from 'vuex'

メソッドでstoreのログインアクションを呼び出せるようにする

methods:{
...mapActions(['login'])
}}

 

結果

f:id:kirikko_Scondcube:20200803091726p:plain


ログインボタンを押すとgoogle認証が働く

*この時本来ログイン画面では上のサイドメニューを消したいが消せていない。

原因を追求する必要あり

後に削除する機能を実装するため今は無視して平気

 

ログインユーザー取得機能の実装

 

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
},
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)
},
toggleSideMenu({commit}){
commit('toggleSideMenu')
},
addAddress({ commit }, address){
commit('addAddress',address)
}
},
modules: {
}
})

stateに

login_user:null,
 

を追加

mutationsに

setLoginUser(state, user){
state.login_user =user
},

を追加

actionsに

setLoginUser({commit},user){
commit('setLoginUser',user)
},

 

views/App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click.stop="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<SideNav/>

<v-content>
<SideNav/>
<v-container fluid fill-height align-start>
<router-view/>
</v-container>
</v-content>
</v-app>
</template>
<script>
import firebase from 'firebase'
import {mapActions} from 'vuex'
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
created(){
firebase.auth().onAuthStateChanged(user => {
if(user){
this.setLoginUser(user)
}
})
},
data: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser'])
}
};
</script>

 

firebaseをインポート

import firebase from 'firebase'
 

 

createdの処理を追加

created(){
firebase.auth().onAuthStateChanged(user => {
if(user){
this.setLoginUser(user)
}

 

mapActionsにsetLoginUserを追記

...mapActions(['toggleSideMenu', 'setLoginUser'])
 

 

この記述をするとvueのデベロッパーツールでログイン状態を見れる。

f:id:kirikko_Scondcube:20200803092547p:plain

 

stateのlogin_userが最初はnullだが、ログイン後に見るとobjectが格納される。

 

ログアウト機能の実装

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click.stop="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn @click='logout'>ログアウト</v-btn>
</v-toolbar-items>
</v-app-bar>
<SideNav/>

<v-content>
<SideNav/>
<v-container fluid fill-height align-start>
<router-view/>
</v-container>
</v-content>
</v-app>
</template>
<script>
import firebase from 'firebase'
import {mapActions} from 'vuex'
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
created(){
firebase.auth().onAuthStateChanged(user => {
if(user){
this.setLoginUser(user)
}else
this.deleteLoginUser()
})
},
data: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser','deleteLoginUser'])
}
};
</script>

 

ログアウトボタンを追加

<v-toolbar-items>
<v-btn @click='logout'>ログアウト</v-btn>
</v-toolbar-items>

 

ログイン状態の変更を管理するためにelse文を追加

}else
this.deleteLoginUser()

 

mapActionsにログアウト機能で使用する「logout, deleteLoginUser」を追加

...mapActions(['toggleSideMenu', 'setLoginUser','deleteLoginUser'])
 

 

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, user){
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)
}
},
modules: {
}
})

 

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)
},
logout () {
firebase.auth().signOut()
},
deleteLoginUser ({ commit }) {
commit('deleteLoginUser')
},
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)
}
},
modules: {
}
})

 

ミューテーションズにログアウト時にstateの状態をnullに戻す記述を追加

deleteLoginUser (state) {
state.login_user = null
},

 

アクションズにログアウト機能と、ユーザーログイン状態の削除機能を追加

logout () {
firebase.auth().signOut()
},
deleteLoginUser ({ commit }) {
commit('deleteLoginUser')
},

 

ログイン認証後、自分のgoogleのアイコン(img画像)と名前が出るように実装 & ログイン時のみログアウト表示されるようにする

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click.stop="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="$store.state.login_user">
<v-btn text @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-app-bar>
<SideNav/>
<v-content>
<v-container fluid fill-height align-start>
<router-view/>
</v-container>
</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: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser'])
}
};
</script>

 

追加項目

<v-toolbar-items v-if="$store.state.login_user">
<v-btn text @click="logout">ログアウト</v-btn>
</v-toolbar-items>
 

v-toolvarpitemsにv-ifを入れてログイン状態のチェックをすることで、ログインボタンの表示・非表示をしている。

 

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)
},
logout () {
firebase.auth().signOut()
},
deleteLoginUser ({ commit }) {
commit('deleteLoginUser')
},
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)
}
},
getters: {
userName: state => state.login_user ? state.login_user.displayName : '',
photoURL: state => state.login_user ? state.login_user.photoURL : ''
},
modules: {
}
})

gettersを追加し使用可能にする

getters: {
userName: state => state.login_user ? state.login_user.displayName : '',
photoURL: state => state.login_user ? state.login_user.photoURL : ''
},

 

src/components/SideNav.vue

<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
<img v-if="photoURL" :src="photoURL">
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{userName}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index" :to="item.link">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
items: [
{ title: 'ホーム', icon: 'mdi-home', link: { name: 'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link: { name: 'addresses' } }
]
}
},
computed: {
...mapGetters(['userName', 'photoURL'])
}
}
</script>

 

v-list-item-avatarのimgを書き換え

<img v-if="photoURL" :src="photoURL">

 

さらにマスタッシュでuserNameとすることで自身のgoogleアカウント名を表示

<v-list-item-title>{{userName}}</v-list-item-title>
 

 

ゲッターズを使用できるようにインポート

import { mapGetters } from 'vuex'
 

 

ゲッターズの使用は以前から使用しているmapActionsに似ているが、こちらではmethodsではなくcomputedを使用する。

computed: {
...mapGetters(['userName', 'photoURL'])
}
※ここで復習mapActions

store/index.jsでactionsに処理を記載

その処理を〇〇.vueファイルでimport。同一vueファイルのインポートの下にmethodsを定義しindex.jsのactionsを呼び出す

 

ログイン後に連絡先一覧ページに遷移 / ログアウト後にホーム画面に遷移を実装*(未解決あり)*

 

src/App.vue

<!-- <v-app-bar-nav-icon @click.stop="toggleSideMenu"></v-app-bar-nav-icon> -->
<v-app-bar-nav-icon v-show="$store.state.login_user" @click.stop="toggleSideMenu"></v-app-bar-nav-icon>

コメントアウト部分を下の行のようにv-showを追記

v-showでログインしていないときはサイドバーのメニューアイコンを消し、ログインしていると表示するようにしている。

 

以下ではログイン後に連絡先ページへ遷移する記述と、ログアウト時ホーム画面に戻る記述をしている。

*しかし、コメントアウト部分が本来必要なのだが、これを入れるとログインしていないときにログインボタンがホーム画面で表示されないバグがある。

if (user) {
this.setLoginUser(user)
if (this.$router.currentRoute.name === 'home') {
this.$router.push({ name: 'addresses' }, () => {})
}
} else {
this.deleteLoginUser()
// this.$router.push({ name: 'home' }, () => {})
}

 

 

src/comoponents/SideNav.vue

// { title: 'ホーム', icon: 'mdi-home', link: { name: 'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link: { name: 'addresses' } }

コメントアウト部分を削除してOKにした。

 

cloud firestoreとの接続

firebaseコンソールへいく。

左側のサイドメニューからdatabaseを選択

新規でデータベースの登録をする

今回はロックモードで登録

登録するとトップ画面にいく

f:id:kirikko_Scondcube:20200803140345p:plain

 

上記画面まで行ったら次に上にあるルールタブを選択。

f:id:kirikko_Scondcube:20200803140622p:plain

この画面で読み書き可能なルールを設定する。

cloud 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 /databases/{database}/documentsが保存先のパス

allowで認証されたユーザーIDが投稿のIDと同じ時に読み取り、更新、削除を許可している。

allow createは認証済みユーザーであれば新規登録を可能にしている。

 

ルールーは以上

 

cloud firestoreに保存する記述をする

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)
},
logout () {
firebase.auth().signOut()
},
deleteLoginUser ({ commit }) {
commit('deleteLoginUser')
},
login(){
const google_auth_provider = new firebase.auth.GoogleAuthProvider()
firebase.auth().signInWithRedirect(google_auth_provider)
},
toggleSideMenu({commit}){
commit('toggleSideMenu')
},
addAddress({getters, commit }, address){
if(getters.uid)firebase.firestore().collection(`users/${getters.uid}/addresses`).add(address)
commit('addAddress',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
},
modules: {
}
})

 

追加点

ゲッターズにuidを定義

uid: state => state.login_user ? state.login_user.uid:null
 

 

actionsのaddAddressの引数にgettersを追加し、if文を追加する。

addAddress({getters, commit }, address){
if(getters.uid)firebase.firestore().collection(`users/${getters.uid}/addresses`).add(address)
commit('addAddress',address)
}

 

そうすると連絡先の追加をするとcloud firestoreのデータベース上に保存される

(この時点では保存はされるが表示はリロードで消える)

f:id:kirikko_Scondcube:20200803143228p:plain

 

このようにデータが登録されていることが確認できる

 

登録したデータの表示

store/index.js

actionsに以下の記述を追加

fetchAddresses({getters, commit}){
firebase.firestore().collection(`users/${getters.uid}/addresses`)
.get().then(snapshot =>{snapshot.forEach(doc => commit('addAddress', doc.data()))
})
},

snapshotにfirebaseに保存したデータがオブジェクトでは入っている。それをforEachで取得している。

 

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<!-- <v-app-bar-nav-icon @click.stop="toggleSideMenu"></v-app-bar-nav-icon> -->
<v-app-bar-nav-icon v-show="$store.state.login_user" @click.stop="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="$store.state.login_user">
<v-btn text @click="logout">ログアウト</v-btn>
</v-toolbar-items>
</v-app-bar>
<SideNav/>
<v-content>
<v-container fluid fill-height align-start>
<router-view/>
</v-container>
</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: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser','fetchAddresses'])
}
};
</script>

 

追加ポイントの説明

mapActionsに以下のコード(fetchAddresses)を追記

...mapActions(['toggleSideMenu', 'setLoginUser', 'logout', 'deleteLoginUser','fetchAddresses'])
 

 

さらにcreatedに以下のコードを追記

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' }, () => {})
}

 

ここでは

this.fetchAddresses()

の一文を追加している。

 

これで登録した内容がリロードしても表示されるようになる

 

編集を可能にする

 

store/index.js

getters部分に

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)
 

 

views/AddressForm.vue

<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
import { mapActions } from 'vuex'

export default {
created(){
if(!this.$route.params.address_id)return
 
const address = this.$store.getters.getAddressById(this.$route.params.address_id)
if(address){
this.address = address
}else{
this.$router.push({name:'addresses'})
}
},
data () {
return {
address: {}
}
},
methods: {
submit () {
this.addAddress(this.address)
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress'])
}
}
</script>

 

以下createdが追記部分

created(){
if(!this.$route.params.address_id)return
 
const address = this.$store.getters.getAddressById(this.$route.params.address_id)
if(address){
this.address = address
}else{
this.$router.push({name:'addresses'})
}
},

 

これで編集ページにいくと登録データが表示される。

 

更新処理の実装

 

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)
},
updateAddress(state, {id, address}){
const index = state.addresses.findIndex(address => address.id === id)
state.addresses[index] = 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 })
})
}
},
updateAddress({getters, commit},{id, address}){
if(getters.uid){
firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).update(address).then*1
})
},
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 })
})
}
},
updateAddress({getters, commit},{id, address}){
if(getters.uid){
firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).update(address).then*2{
this.deleteAddress({id})
}
},
...mapActions(['deleteAddress'])
}
}
</script>

 

追加場所

ゴミ箱アイコンを追加

<v-icon small class='mr-2' @click='deleteConfirm(item.id)'>mdi-delete</v-icon>
 

 

mapActionsをインポート

import {mapActions} from 'vuex'
 

クリックイベント(ゴミ箱)を追加

methods:{
deleteConfirm(id){
if(confirm('削除してよろしいですか?')){
this.deleteAddress({id})
}
},
...mapActions(['deleteAddress'])
}

 

これで削除機能を実装

f:id:kirikko_Scondcube:20200803193703p:plain

f:id:kirikko_Scondcube:20200803193712p:plain

firebase hostingでの公開方法

 

npm install -g firebase-tools

これでfirebaseのコマンドを使用できるようにする

firebase login

プロダクトの場所(今回はsample-app)へ移動

firebase init

f:id:kirikko_Scondcube:20200803194208p:plain

Hostingを選択しスペースキーで選択→エンターで決定

use exist projectを選択し、以下の自分のプロジェクトを選ぶ

f:id:kirikko_Scondcube:20200803194504p:plain

↓ここではyを押してエンター

f:id:kirikko_Scondcube:20200803194437p:plain

↓distと入力しエンター(何も入れないとpublicが選択される)

f:id:kirikko_Scondcube:20200803194628p:plain

これで設定完了

 

次に

npm run build

これでdistフォルダが作成されdistフォルダ以下にコンパイルされたファイルが配置され、先ほどdistディレクトリを公開対象に設定したので、

 

firebase deploy 

を打つことでアプリケーションを公開できる。

 

 

公開したアプリケーションの停止方法

firebase hosting:disable

 

このコマンドで公開を停止できる

 

サイド公開するときはfirebase deploy

 

 

*1:) => {

commit('updateAddress', {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)
},
modules: {
}
})

 

追加箇所は

actionsの

updateAddress({getters, commit},{id, address}){
if(getters.uid){
firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).update(address).then(() => {
commit('updateAddress', {id, address})
})
}
}

 

mutationsの

updateAddress(state, {id, address}){
const index = state.addresses.findIndex(address => address.id === id)
state.addresses[index] = address
}

 

AddressForm.vue

<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
import { mapActions } from 'vuex'

export default {
created(){
if(!this.$route.params.address_id)return
 
const address = this.$store.getters.getAddressById(this.$route.params.address_id)
if(address){
this.address = address
}else{
this.$router.push({name:'addresses'})
}
},
data () {
return {
address: {}
}
},
methods: {
submit () {
if(this.$route.params.address_id){
this.updateAddress({id: this.$route.params.address_id, address: this.address})
}else{
this.addAddress(this.address)
}
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress', 'updateAddress'])
}
}
</script>

 

追加箇所

...mapActions(['addAddress', 'updateAddress'])
 

updateAddressを追加

submit () {
if(this.$route.params.address_id){
this.updateAddress({id: this.$route.params.address_id, address: this.address})
}else{
this.addAddress(this.address)
}
this.$router.push({ name: 'addresses' })
this.address = {}
},

submit配下にif,else文を追加

 

これで更新処理ができるようになる

 

削除処理の実装

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)
},
updateAddress(state, {id, address}){
const index = state.addresses.findIndex(address => address.id === id)
state.addresses[index] = address
},
deleteAddress(state, {id}){
const index = state.addresses.findIndex(address => address.id === id)
state.addresses.splice(index,1)
}
},
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() }

*2:) => {

commit('updateAddress', {id, address})
})
}
},
deleteAddress({getters, commit},{id}){
if(getters.uid){
firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).delete().then(() => {
commit('deleteAddress', {id})
})
}
}
},
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)
},
modules: {
}
})

 

ミューテンションに

deleteAddress(state, {id}){
const index = state.addresses.findIndex(address => address.id === id)
state.addresses.splice(index,1)
}

 

アクションズに

deleteAddress({getters, commit},{id}){
if(getters.uid){
firebase.firestore().collection(`users/${getters.uid}/addresses`).doc(id).delete().then(() => {
commit('deleteAddress', {id})
})
}
}

上記を追加

 

views/Address.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-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>


<v-flex xs12 mt-5 justify-center>
<v-data-table :headers='headers' :items='addresses'>
<template v-slot:item.action="{ item }">
<router-link :to="{ name: 'address_edit', params: { address_id: item.id }}">
<v-icon small class="mr-2">mdi-pencil</v-icon>
</router-link>
<v-icon small class='mr-2' @click='deleteConfirm(item.id)'>mdi-delete</v-icon>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
import {mapActions} from 'vuex'
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: '操作', value: 'action', sortable: false }
],
addresses: []
}
},
methods:{
deleteConfirm(id){
if(confirm('削除してよろしいですか?'

Vue js Vuexの使い方とvuexを使用した連絡先追加フォームの作成

Vue cli をインストールする際にデフォルトではなくカスタムを選択し、Vuexを選択すると使える。

storeフォルダのindex.jsのファイルを記述していくことで使う

ステイト、ミューテーション、アクション、ゲッターの機能がある

 

デフォルトのコードは以下

 

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer:false
},
mutations: {
},
actions: {
},
modules: {
}
})

importでvuexを読み取り、useで使用できるようになっている。

 

別の〇〇.vueファイルを開いた際、storeへアクセスするにはテンプレートから「$store」でアクセス可能。

stateは$store.stateで参照できる

 

 

サイドメニューを開閉するページを作る例を使って覚えよう

src/store/index.js / SideNav.vue / App.vueというファイルを編集する中での具体例を以下に記述する

 

デフォルトはこちら

 

 

index.js

 
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})

 

SideNav.vue

<template>
<v-container fluid fill-height>
<v-btn color="pink" dark @click.stop="drawer = !drawer">Toggle</v-btn>

<v-navigation-drawer v-model="drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>

<v-divider></v-divider>

<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-container>
</template>

<script>
export default {
data () {
return {
drawer: false,
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>
 

 

App.vue

<template>
<v-app>
<div>
<v-app-bar
color="grey darken-2"
dark
>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
</div>
<SideNav/>

</v-app>
</template>

<script>
import SideNav from './components/SideNav'

export default {
name: 'App',

components: {
SideNav,
 
},

data: () => ({
//
}),
};
</script>

 

 

この記述ではまだstoreとの連携はない

f:id:kirikko_Scondcube:20200802203654p:plain

f:id:kirikko_Scondcube:20200802203659p:plain

toggleボタンを押すと、サイドメニューが表示される

 

 

これを以下に変更すると

メニューボタンを押してサイドメニューの開閉が可能に

 

index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer:false

},
mutations: {
toggleSideMenu(state){
state.drawer = !state.drawer
}
},
actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
}
},
modules: {
}
})

 

SideNav.vue

<template>
<v-container fluid fill-height>
 
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-container>
</template>
<script>
export default {
data () {
return {
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>

変更点

v-modelを「$store.state.drawer」に変更

 

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click.stop="openSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-content>
<SideNav/>
</v-content>
</v-app>
</template>
<script>
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
data: () => ({
//
}),
methods: {
openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
}
};
</script>

変更点

<v-app-bar-nav-icon @click.stop="openSideMenu"></v-app-bar-nav-icon>
 とし、サイドメニューのアイコンをクリックしたときにopenSideMenuのメソッドを実行するようにした。
methods: {
openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
}
そしてmethodsの記入をした。
コンポーネントインスタンス上では「this.$store」でstoreにアクセスができる。
dispatchiはstoreのメソッド
dispatchi()でアクションを呼び出せる

 

src/store/index.js

actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
}
},

で定義している

 

mapActionsを使用した書き換え

 

vuexにはmapActionsと言うメソッドがあり、これを使用することで上記のアクションを使用したサイドメニューの開閉をよりシンプルに定義できる。

 

書き換えは以下(結果は同じ)

 

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-content>
<SideNav/>
</v-content>
</v-app>
</template>
<script>
import {mapActions} from 'vuex'
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
data: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu'])
}
};
</script>

script以下に

import {mapActions} from 'vuex'

を追加。

先ほどあったmethodsの

openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
を削除し
...mapActions(['toggleSideMenu']) に変更する。 
そして呼び出しの@clickを<v-app-bar-nav-icon @click="toggleSideMenu"></v-app-bar-nav-icon> へ変更する
 

サイドバーでリンクの遷移を作る

 
SideNav.vueリンクを先の追加→ホーム画面から別ページ(src/Addresses.vue)へのリンクの設定をする
 
変更前
 
SideNav.vue
<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
data () {
return {
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>
 
 
これを
 
<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index" :to='item.link'>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
data () {
return {
items: [
{ title: 'ホーム', icon: 'mdi-home', link:{name:'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link:{name:'addresses'} }
]
}
}
}
</script>
こうする。
 
やったこと
<v-list-item v-for="(item, index) in items" :key="index" :to='item.link'>
 to=の記述を設定
itemのところにリンク先を設定

 

{ title: 'ホーム', icon: 'mdi-home', link:{name:'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link:{name:'addresses'} }
 
結果
localhost:8081のページでメニューを開く

 

f:id:kirikko_Scondcube:20200803002246p:plain

メニューから連絡先一覧をクリック

f:id:kirikko_Scondcube:20200803002254p:plain

 

 

localhost:8081/addressesへ遷移するようになった。

f:id:kirikko_Scondcube:20200803002304p:plain

 
 

さらに連絡先追加を押して別ファイルsrc/views/AddressFormへ遷移するようにする

 

src/views/AddressForm.vueを追加する

 

AddressForm.vue

<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
data () {
return {
address: {}
}
}
}
</script>

 

src/router/index.jsに追記

import Vue from 'vue'//vue本体の読み込み
import VueRouter from 'vue-router'//vue Routerのライブラリの読み込み
import Home from '../views/Home.vue'
import Addresses from '../views/Addresses.vue'
import AddressForm from '../views/AddressForm.vue'

Vue.use(VueRouter)//useメソッドにルーターを渡して有効にしている

const routes = [//ここでルートの操作をする処理を記載。各ページに関するルートとそのルートで表示するコンポーネントの設定を書く
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/addresses',
name: 'addresses',
component: Addresses
},
{
path: '/addresses/:address_id?/edit',
name: 'address_edit',
component: AddressForm
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]

const router = new VueRouter({//ここより以下でルーティングの設定をしている
mode: 'history',//mode: 各画面のパスの設定「history」ではパスを「/」で区切る一般的なやり方を指定している(他にhash, abstractなどがある。)
base: process.env.BASE_URL,//base: アプリケーションのベースとなるパスを指定するもの
routes
})

export default router

 

追加箇所は

import AddressForm from '../views/AddressForm.vue'

↑で新しいページのインポート

{
path: '/addresses/:address_id?/edit',
name: 'address_edit',
component: AddressForm
},

↑で新しいページのパスを追加。

 
:address_id?/edit',

この部分での意味は

:address_id?でアドレスのIDが入る。

IDが1のアドレスだったら「:/addresses/1/edit」が入る想定

このように「:」から始まる文字列をパスに含めることでパスに含まれるIDなどのパラメーターをコンポーネントで受け取ることができる。

これは既存の連絡先を編集する際に編集対象の連絡先を特定するために使用する。

パスの後ろの「?」の意味

?をつけることでパスに含まれるパラメーターをオプションの扱いにしている。

オプションなのでアドレスIDを渡さないパスとして「/addresses/edit」と言うパスの場合にもこのルートが該当するようになる。

こうすることでアドレスIDがないパスでアクセスが来た場合には新規のアドレス追加として扱うようにこれからコードを追加していくことが可能

 

src/views/Address.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-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>


<v-flex xs12 mt-5 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>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
data () {
return {
headers: [
{ text: '名前', value: 'name' },
{ text: '電話番号', value: 'tel' },
{ text: 'メールアドレス', value: 'email' },
{ text: '住所', value: 'address' }
],
addresses: [
{
name: '友人1',
tel: '090-0000-1111',
email: 'sample1@mail.com',
address: '東京都渋谷区'
},
{
name: '友人2',
tel: '090-2222-3333',
email: 'sample2@mail.com',
address: '東京都品川区'
}
]
}
}
}
</script>

 

修正箇所
 
<v-flex xs12 mt-5 mr-5 text-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>
を追加
この記述で追加のボタンを表示させ、
先ほどindex.jsで指定したページ名にto=""で遷移するように記述している。
 
結果
連絡先追加を押すと以下の画面に遷移する(AddressForm.vue)

f:id:kirikko_Scondcube:20200803004539p:plain

連絡先追加機能の実装

前述の連絡先フォームを入力し、保存を可能にする
 
 
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer: false,
addresses:

},
mutations: {
toggleSideMenu(state){
state.drawer = !state.drawer
},
addAddress (state, address) {
state.addresses.push(address)
}
},
actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
},
addAddress({ commit }, address){//第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際に
commit('addAddress',address)
}
},
modules: {
}
})
追加ポイント
addresses:
mutationsに
addAddress (state, address) {
state.addresses.push(address)
}
actionsに
addAddress({ commit }, address){//第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際に
commit('addAddress',address)
}
を追加
addAddressの第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際にアドレスを渡すことを想定している。それをミューテーションに渡してAddresses.vueのaddressesの配列に渡すようにしている。
 
 
AddressForm.vue
<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
import { mapActions } from 'vuex'

export default {
data () {
return {
address: {}
}
},
methods: {
submit () {
this.addAddress(this.address)
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress'])
}
}
</script>
 
追加点
保存クリックの記述に関数をもたせる
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
 
インポート
import { mapActions } from 'vuex'
関数の追加
methods: {
submit () {
this.addAddress(this.address)
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress'])
}
 
 
 
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-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>


<v-flex xs12 mt-5 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>
</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' }
],
addresses:
}
}
}
</script>
 
追加、修正点
createdの追加
created () {
this.addresses = this.$store.state.addresses
},
 
dataに設定のaddressesの配列の中身を削除。
初期値なしにし、AddressForm.vueでsubmit関数が実行され追加した内容がプッシュされるようにする
addresses:
 
 
 
 
 

Vue js Vuetify導入について

Vue cliでプロジェクトをcreateする方法はこちら参照

https://qiita.com/H-Toshi/items/2dbd9f72d28815a67924

 

作成したプロジェクト内で

ターミナルを開き

$ vue add vuetify

でプロジェクトを作成可能

 

インストールすると作成したフォルダが書き換えられる。

成功するとトップページの表示が切り替わる

f:id:kirikko_Scondcube:20200801025410p:plain

Vuetify インストール後この画面がホーム画面になる

App.vueの初期表示は以下

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<div class="d-flex align-center">
<v-img
alt="Vuetify Logo"
class="shrink mr-2"
contain
transition="scale-transition"
width="40"
/>

<v-img
alt="Vuetify Name"
class="shrink mt-1 hidden-sm-and-down"
contain
min-width="100"
width="100"
/>
</div>

<v-spacer></v-spacer>

<v-btn
target="_blank"
text
>
<span class="mr-2">Latest Release</span>
<v-icon>mdi-open-in-new</v-icon>
</v-btn>
</v-app-bar>

<v-main>
<HelloWorld/>
</v-main>
</v-app>
</template>

<script>
import HelloWorld from './components/HelloWorld';

export default {
name: 'App',

components: {
HelloWorld,
},

data: () => ({
//
}),
};
</script>

 

自分のプロジェクトを作る場合ここの不要な部分を削除して作っていく

不要な部分を削除した状態が以下

App.vue(編集後:最低限の記述を残した物)

<template>
<v-app>
 
</v-app>
</template>

<script>

export default {
name: 'App',

components: {
},

data: () => ({
//
}),
};
</script>

不要な部分を削除した状態が以下

 

削除の内容は

<template>
<v-app>
 
</v-app>
</template>

上記v-app内の記述

 

scirptタグ内は初期のHelloworldのimportとcomponentsの記述を削除している。

 

v-app内にvuetify公式のApp-Barなどをコピペで入れられる。

https://vuetifyjs.com/ja/components/app-bars/

テンプレートの右上からコードをコピーして

f:id:kirikko_Scondcube:20200801030344p:plain

vuetifyのappbarサンプル

v-app内に貼り付ければ使用可能

 

 

 

 

 

 

 

 

 

 

Vue js router-view , router-linkについて

過去記事の続きです。

 

kirikko-scondcube.hatenablog.com

kirikko-scondcube.hatenablog.com

 

 

今回最初に操作するのは

src/App.vue

 

App.vue

<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>

<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}

#nav {
padding: 30px;

a {
font-weight: bold;
color: #2c3e50;

&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

 

ここで注目するのが

<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>

 

この

<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>

<router-view/>
 の部分
 
この2つはrouter/index.js内でルーターモジュールを読み込んだことで使えるようになる。
*疑問点*
ルーターモジュールとは??

 

<router-view/>
について 
この部分にルートに合わせたコンポーネント が表示される。
そのため全てのページで使用するグローバルビューはこの部分の外側に記述する
 
 
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>

について

これはデフォルトの内容で

Homeとaboutのページを行き来するためにしようされている。

上記「to='/'」のように記述しパスを指定する。

 

ルートに名前をつけて指定することも可能

その場合は以下のように書き換える

<router-link to="{name: 'home'}">Home</router-link> |
<router-link to="{name: 'about'}">About</router-link>

この記述方法のメリットはパスが変わっても適用可能と言う点にある。 

基本はこちらが推奨される。

 

また、内のHome, Aboutの記述を帰ればリンクを変更できる

(文字列を変えたりimgにリンクを貼れたりする)

<router-link to="{name: 'home'}"><img width='100' src="https://images.unsplash.com/photo-1595970731082-f7267436b6f1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60" alt=""></router-link> |
<router-link to="{name: 'about'}">アバウト</router-link>
 
結果

f:id:kirikko_Scondcube:20200801013055p:plain

このように変更することも可能
 

 

 

 

 

 

 

 

 

 

Vue js SPAとRouterについて

この記事は前回の記事の続きです。

 

 

kirikko-scondcube.hatenablog.com

 

SPAの特徴

SPAはページの描画は一度(初回アクセスが重くなることがある)。

軽快に動作するアプリケーションが作れる。

 

Vue Routerの機能

ページごとの画面の切り替えをする機能

 

確認するにはsrc/main.js

 

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
router,
//router:router,と同じ意味上は省略の記載方法
store,
render: h => h(App)
}).$mount('#app')

 

import App from './App.vue'で読み込んで
new Vueの中のrouter,で定義している。
これによってVueインスタンスの初期化時にrouterが渡される。
 
次にsrc/router/index.jsをみてみる
 
index.js
import Vue from 'vue'//vue本体の読み込み
import VueRouter from 'vue-router'//vue Routerのライブラリの読み込み
import Home from '../views/Home.vue'

Vue.use(VueRouter)//useメソッドにルーターを渡して有効にしている

const routes = [//ここでルートの操作をする処理を記載。各ページに関するルートとそのルートで表示するコンポーネントの設定を書く
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]

const router = new VueRouter({//ここより以下でルーティングの設定をしている
mode: 'history',//mode: 各画面のパスの設定「history」ではパスを「/」で区切る一般的なやり方を指定している(他にhash, abstractなどがある。)
base: process.env.BASE_URL,//base: アプリケーションのベースとなるパスを指定するもの
routes
})

export default router

 

routesの解説

const routes = [//ここでルートの操作をする処理を記載。各ページに関するルートとそのルートで表示するコンポーネントの設定を書く
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]

ルーツでは配列の中で各ページごとの記載をしている。

扱いたいページが増やしたい時はこの配列の中に新しいページの記述を増やしていく。

書くページの記述はオブジェクトで指定している。

{
path: '/',
name: 'Home',
component: Home
},

path: そのページを表示するパス

→'/'はホーム画面(デフォルトで最初に表示されるパス)

 

name: 名前付きルートのこと(ルーターリンクコンポーネントで使用)

→ルートにネームを指定しておくことで名前でリンクに飛べるようになる(詳細は以下記事ルーターリンクコンポーネントにて)

 

kirikko-scondcube.hatenablog.com

 

 

component:実際に表示するコンポーネントを記述

コンポーネントの読み込み方法は2つ

 

①同一ファイル内にimportされたファイルを指定する方法

 

今回の例ではHomeコンポーネントは事前にインポートされている。

component: Homeとなっていることがわかる↓

import Vue from 'vue'//vue本体の読み込み
import VueRouter from 'vue-router'//vue Routerのライブラリの読み込み
import Home from '../views/Home.vue'

Vue.use(VueRouter)//useメソッドにルーターを渡して有効にしている

const routes = [//ここでルートの操作をする処理を記載。各ページに関するルートとそのルートで表示するコンポーネントの設定を書く
{
path: '/',
name: 'Home',
component: Home
},

 

コードを見ると上で

import Home from '../views/Home.vue'
を定義しているのがわかる。
これをcomponent:Homeで指定している。

 

メリット

小規模プロジェクトであれば管理しやすい。

 

デメリット

大規模開発で多数のコンポーネントの読み込みが必要な場合、importするファイルが多くなり、最初の読み込みに時間がかかる

 

②必要な物を取ってくる方法(関数でコンポーネントを取ってくる方法)

この方法だと最初にimportをする必要がなくなるため、軽快に使用可能(しかし記述がわかりづらい)

 

今回の例だとaboutのインポートで使われている

{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}

component部分の記述がHomeと違うことがわかる↓

component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
 

ここの記述では関数の戻り値でコンポーネントの場所を返すことができるため、そのページが読み込まれたタイミングで初めてインポートされるため複数コンポーネントの読み込みが一度に起こらず軽快な操作を実現できる。

*コメント部分「* webpackChunkName: "about" */」にも意味があるがそこは割愛。

udemyのセクション40でも解説がある。

 

基本的に軽い記述であれば①を使えば良さそう

 

 

 

 

 

Vue CLI のcreateしたファイルについて

Vue CLIでcreateしたファイルの主なフォルダの役割について

 

f:id:kirikko_Scondcube:20200731213644p:plain

インストールしたファイルの階層

 

メモ

画面で表示される。ファイルはpublicフォルダに入れる。

大事なのはpublic/index.html

内の<div id='app'><div>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

 

このapp要素にvueインスタンスをマウントすることでアプリケーションが動作する。

 

次にインスタンスの生成部分

src/main.js

 

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

ここで#appとなっているところがポイント

render: h => h(App)

そしてこれでsrc/App.vueを指定している

 

App.vue

<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>

<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}

#nav {
padding: 30px;

a {
font-weight: bold;
color: #2c3e50;

&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

このように1つのファイルで1つのコンポーネントが設定されている。

これが単一ファイルコンポーネント

→1つのファイルにテンプレートjavascript, css を書く。

 

コンポーネントは作成したファイルの中のsrc/components/の中に作っていく。

*例外あり→例外①参照*

基本的な作りはtemplate, script, styleの3点記入

ちなみにtemplateの記載があれば動作はするscript , styleは任意

vue create をした際に設定するとvcliにはscssが入る。

langにscssを設定するとstyleタグをscss形式で記入ができる。

.vueを作る時の基本構造は以下

<template>
</template>

<script>
</script>

<style scoped lang="scss">//langにscssと記述するとscssの形式でスタイルを記載できる。
 
</style>

 

*例外①

コンポーネントでもファイル表示のコンポーネント

src/views/の中に○○.vueという名前で記述をする。

ここにはHome.vueというファイルがある。これが初期ではホームコンポーネント となっている。

*ホームコンポーネント とはnpm run serveでサーバーを立ち上げた際に一番最初に表示される画面のこと。

Home.vue

<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>

 

元ファイルを元に自分の作りたいファイルを追加していく方法について

まず

src/componets内にAddresses.vueファイルを作成。

 

Addresses.vue

<template>
<div>
<h1>マイアドレス帳</h1>
</div>
</template>

<script>

</script>

<style scoped lang="scss">//langにscssと記述するとscssの形式でスタイルを記載できる。

</style>

 

シンプルにh1タグの記述のみにしています。

 

次にホームコンポーネントの設定を変更

初期ではHome.vueからsrc/component/HelloWorld.vueが設定されている

 

Home.vue

<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>

これを修正し、src/components/Addresses.vueを読み込むようにすると、、

<template>
<div class="home">
<!-- <img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/> -->
<Addresses/>
</div>
</template>

<script>
// @ is an alias to /src @/components以下でファイルを指定すると参照できるようになるよと書いてある。
// import HelloWorld from '@/components/HelloWorld.vue'
import Addresses from '@/components/Addresses.vue'//作成したアドレスコンポーネントを読み込み


export default {
name: 'Home',
components: {
// HelloWorld,
Addresses
}
}
</script>

このようになる(元あった不要なHelloWorld.vueへの表示のための記述はコメントあうとしてあります)

ポイント

テンプレート

<Addresses/>

 

スクリプト

import Addresses from '@/components/Addresses.vue'//作成したアドレスコンポーネントを読み込み

 

スタイル

components: {
// HelloWorld,
Addresses
}

 

の部分

こうするとnpm run serveでの初期画面が

 

f:id:kirikko_Scondcube:20200731223019p:plain

デフォルトから上記のように変更される。

 

初期ファイルの変更は以上。

つづく。