便利なメモアプリ 〜Sublime text~

日々のメモ書き用にインストール

  • 日本語化には設定が必要

参考にしたサイト

ファイル拡張子一括変更

プラグインの有効・無効Sublime%20Text%203%E3%81%AE,Command%20Palette%EF%BC%89%20%5D%20%E3%82%92%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%80%82&text=(2)%E6%A4%9C%E7%B4%A2%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%8F%BE%E3%82%8C,%5B%20Enter%20%5D%20%E3%82%AD%E3%83%BC%E3%82%92%E6%8A%BC%E3%81%99%E3%80%82)

やりたいこと

1つのformタグから複数画像をアップロード

 

myprofileEdit.ejs

<form action="/myprofile_img" method="post" enctype="multipart/form-data">
プロフィール写真の変更<input type="file" name="account_img"><br>
店舗詳細写真の変更 <input type="file" name="shop_img"><br>
店舗画像1<input type="file" name="img1"><br>
店舗画像2<input type="file" name="img2"><br>
<input type="submit" value="送信">
</form>

 

myprofile_img.js

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

const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/images/uploads')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + Math.round(Math.random() * 1E9)+file.originalname)
}
})
// const upload = multer({ dest: './public/images/uploads' })//ファイルの保存先指定
const upload = multer({ storage: storage })

const path = require('path')

router.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'views/myprofile.ejs'))
}) ;

var cpUpload = upload.fields([{
name:'account_img', maxCount:1
},{
name:'shop_img', maxCount:1
},{
name:'img1', maxCount:1
},{
name:'img2', maxCount:1
}
]);


//送信する画像が1つの時第二引数は 「upload.single('account_img')」を使う
router.post('/', cpUpload, function (req, res, next) {
 
console.log('req.filesここからここからここからここからここからここから')
console.log(req.files)
console.log("req.files000000000000000000000000000000")
console.log(req.files.account_img)//ok
console.log(req.files.account_img[0])//ok
console.log(req.files.account_img[0].filename)//1599803609347-263008020image.png
// res.redirect('/myprofile')

})

module.exports = router;


これでアップロードしたファイルの名前を取得するために検証を以下で進める

 

console.log(req.files)
 

この値をターミナルでみると


[Object: null prototype] {
account_img: [
{
fieldname: 'account_img',
originalname: 'image.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './public/images/uploads',
filename: '1599803609347-263008020image.png',
path: 'public/images/uploads/1599803609347-263008020image.png',
size: 1249386
}
],
shop_img: [
{
fieldname: 'shop_img',
originalname: 'images (44).jpeg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './public/images/uploads',
filename: '1599803609356-110568068images (44).jpeg',
path: 'public/images/uploads/1599803609356-110568068images (44).jpeg',
size: 20278
}
],
img1: [
{
fieldname: 'img1',
originalname: 'kasa.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './public/images/uploads',
filename: '1599803609356-307667741kasa.png',
path: 'public/images/uploads/1599803609356-307667741kasa.png',
size: 222415
}
],
img2: [
{
fieldname: 'img2',
originalname: 'a0258141_2455659.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './public/images/uploads',
filename: '1599803609358-937438576a0258141_2455659.jpg',
path: 'public/images/uploads/1599803609358-937438576a0258141_2455659.jpg',
size: 17810
}
]
}

 

さらに細かくみていく

console.log(req.files.account_img)

でみると

[
{
fieldname: 'account_img',
originalname: 'image.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './public/images/uploads',
filename: '1599803609347-263008020image.png',
path: 'public/images/uploads/1599803609347-263008020image.png',
size: 1249386
}
]

 

次に

console.log(req.files.account_img[0])

でみると


{
fieldname: 'account_img',
originalname: 'image.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './public/images/uploads',
filename: '1599803609347-263008020image.png',
path: 'public/images/uploads/1599803609347-263008020image.png',
size: 1249386
}

そして

console.log(req.files.account_img[0].filename)

でみると

1599803609347-263008020image.png

 

ついに画像の名前を取得できた!

Node.jsの更新による複数画像の投稿について詰まったところ

今回は既にinsert済みのshopテーブルにてnullで指定していた画像を投稿するカラムの更新について詰まった点を記述する

 

使用コードは2点

formタグを使用してpost処理を実行する

myprofileEdit.ejs

 

postを受け取りupdate文を実行する

myprofile_img.js

 

コードは以下

 

myprofileEdit.ejs

<h2>画像情報編集</h2>
<form action="/myprofile_img" method="post" enctype="multipart/form-data">
プロフィール写真の変更<input type="file" name="account_img"><br>
<input type="submit" value="送信">
</form>
<form action="/myprofile_img2" method="post" enctype="multipart/form-data">
店舗詳細写真の変更 <input type="file" name="shop_img"><br>
<input type="submit" value="送信">
</form>
<form action="/myprofile_img3" method="post" enctype="multipart/form-data">
店舗画像1<input type="file" name="img1"><br>
<input type="submit" value="送信">
</form>
<form action="/myprofile_img4" method="post" enctype="multipart/form-data">
店舗画像2<input type="file" name="img2"><br>
<input type="submit" value="送信">
</form>

(上記コードは画像投稿に使用しているform部分を抜粋)

上記では4つの画像投稿をするため処理を行うjsファイルを4つ用意しそれぞれのjsファイルで処理を実行している

 

myprofile_img.js

router.post('/', upload.single('account_img'), function (req, res, next) {
console.log(req.file);
console.log(req.file.filename);
var userId = req.session.user_id? req.session.user_id: 0;

var sql='UPDATE shop SET account_img = ? WHERE user_id = '+ userId +'';
connection.query(sql, [req.file.filename],(error,result)=>{
res.redirect('/myprofile')
})
})

(上記コードはpostされた値を処理する部分を抜粋)

内容としては

upload.single('account_img'),

部分のaccount_imgのところがmyprofileEdit.ejsのformタグ内inputタグの

input type="file" name="account_img"><br>

上記name部分で指定した内容を記述することになっている。

参考:https://reffect.co.jp/node-js/express-js-file-upload

(1.4 ルーティングの設定参照)

試したこと

upload.single('account_img',shop_img'),

上記のようにshop_imgを追加し

var sql='UPDATE shop SET account_img = ?, shop_img = ? WHERE user_id = '+ userId +'';
connection.query(sql, [req.file.filename, req.file.filename],(error,result)=>{

のようにupdate文にもshop_imgを追加してみた

 

しかしこれはエラーとなり実行不可

そのため、上述のコードのようにformタグを4つとそれに対応するjsファイルを4つ使用して更新処理が出来るようにいったん実装をした。

 

1つのformタグで4つの画像を更新する方法はあるのだろうか、、検証は続く

 

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内に貼り付ければ使用可能