前置き
チャットシリーズの続きです✨
今回はVuexに移行していきます❣️
これが終わったら
本当にCSSです…😂笑
【Nuxt.js】開発ブログ:firebaseでチャットアプリ①
【Nuxt.js】開発ブログ:firebaseでチャットアプリ②
【Nuxt.js】開発ブログ:firebaseでチャットアプリ③
Vuexに移行
⬇️Vuexは解説記事を
いくつか出していますが
簡潔に使い方を
まとめたものはこちらです❤️
【Nuxt.js】Vuexまとめ編:はじめる前に、簡単理解!
まずは取得データのpostsを移行
index.vueにあったpostsを
store/index.jsに移します🚗💨
fetchでactionsを呼び出して
最初にpostsを取得していきます。stateの上書きはmutationsでしかできないのでactionsの最後にcommitで呼び出します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import axios from 'axios' import firebase from '@/plugins/firebase' export const state = () => ({ posts: [], }) export const getters = { posts: state => { return state.posts }, } export const actions = { getData ({ commit }) { return axios .get('https://sample-6a560.firebaseio.com/message.json') .then((res) => { const postArray = [] for (const key in res.data) { let allKey = res.data[key] postArray.push(allKey) } commit('getData', postArray) }) } } export const mutations = { getData (state, postArray) { state.posts = postArray }, } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
<template> <div class="page"> <form class="form" @submit.prevent > <label class="label"> <span class="label"> email </span> <input class="input" type="text" v-model="email" > </label> <label class="label"> <span class="label"> password </span> <input class="input" type="password" v-model="password" > </label> <button class="button" type="submit" @click="login" > Login </button> </form> <ul> <li v-for="post in posts" :key="post.id" :class="{ me: user.email === post.userId}" > <div> {{ post.name }} | {{ $dayjs(post.time).format('MM-DD HH:mm') }} <img :src="post.thumbnail" alt=""> <p v-html="post.message">{{ post.message }}</p> </div> </li> </ul> <form @submit.prevent> <label class="img" > <input type="file" class="file" @change="changeImg" > </label> <img :src="user.thumbnail" alt=""> <label> <span> 名前: </span> <input type="text" v-model="user.name" > </label> <label> <span> message: </span> <textarea v-model="user.message" > </textarea> </label> <button type="submit" @click="submit" > Submit </button> </form> </div> </template> <script> import axios from 'axios' import firebase from '@/plugins/firebase' export default { async fetch ({ store }) { await store.dispatch('chat/getData') }, computed: { posts () { return this.$store.getters['chat/posts'] }, }, data () { return { email: '', password: '', thumbnail: '', user: { email: '', name: "", message: "", thumbnail: '', }, } }, methods: { submit () { let Ref = firebase.database().ref().child('message') let stamp = firebase.database.ServerValue.TIMESTAMP Ref.push({ name: this.user.name, message: this.user.message, userId: this.user.email, time: stamp, thumbnail: this.user.thumbnail }) .then(response => { this.getData () this.user.name = '' this.user.message = '' }) let storage = firebase.storage() let storageRef = storage.ref().child('file.png') storageRef.put(this.thumbnail) .then(res => console.log(res)) .catch(error => console.log(error)) }, login() { firebase.auth().signInWithEmailAndPassword(this.email, this.password) .then(data => { console.log(data.user.email) this.user.email = data.user.email }).catch((error) => { console.log(error) }); }, changeImg (e) { this.thumbnail = e.target.files[0] if (this.thumbnail) { const reader = new FileReader() reader.onload = () => { this.user.thumbnail = reader.result + '' } reader.readAsDataURL(this.thumbnail) } }, }, } </script> |
あとはsubmitを最後にして
他の物から移行していきます。
loginを移行
mutationsでstateの上書きをしていても
このエラーが出ました。
一旦配列にpushすれば解決。
Error
Do not mutate vuex store state outside mutation handlers
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import axios from 'axios' import firebase from '@/plugins/firebase' export const state = () => ({ posts: [], loginUser: { email: '', password: '', }, }) export const getters = { posts: state => { return state.posts }, user: state => { return state.user }, } export const actions = { login ({commit}, login) { firebase.auth().signInWithEmailAndPassword(login.email, login.password) .then(data => { console.log(data) commit('login', data) }).catch((error) => { console.log(error) }) }, getData ({ commit }) { return axios .get('https://sample-6a560.firebaseio.com/message.json') .then((res) => { const postArray = [] for (const key in res.data) { let allKey = res.data[key] postArray.push(allKey) } commit('getData', postArray) }) }, submit ({ commit }, { name, message, email, thumnail }) { let Ref = firebase.database().ref().child('message') let stamp = firebase.database.ServerValue.TIMESTAMP Ref.push({ name: name, message: message, userId: email, time: stamp, thumbnail: thumbnail }) .then(response => { dispatch('getData') }) let storage = firebase.storage() let storageRef = storage.ref().child('file.png') storageRef.put(thumbnail) .then(res => console.log(res)) .catch(error => console.log(error)) }, } export const mutations = { getData (state, postArray) { state.posts = postArray }, login (state, data) { let loginData = [] loginData.push.data state.loginUser = loginData }, } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
<template> <div class="page"> <form class="form" @submit.prevent > <label class="label"> <span class="label"> email </span> <input class="input" type="text" v-model="email" > </label> <label class="label"> <span class="label"> password </span> <input class="input" type="password" v-model="password" > </label> <button class="button" type="submit" @click="login" > Login </button> </form> <ul> <li v-for="post in posts" :key="post.id" :class="{ me: user.email === post.userId}" > <div> {{ post.name }} | {{ $dayjs(post.time).format('MM-DD HH:mm') }} <img :src="post.thumbnail" alt=""> <p v-html="post.message">{{ post.message }}</p> </div> </li> </ul> <form @submit.prevent> <label class="img" > <input type="file" class="file" @change="changeImg" > </label> <img :src="user.thumbnail" alt=""> <label> <span> 名前: </span> <input type="text" v-model="user.name" > </label> <label> <span> message: </span> <textarea v-model="user.message" > </textarea> </label> <button type="submit" @click="submit" > Submit </button> </form> </div> </template> <script> import axios from 'axios' import firebase from '@/plugins/firebase' export default { async fetch ({ store }) { await store.dispatch('chat/getData') }, computed: { posts () { return this.$store.getters['chat/posts'] }, loginUser () { return this.$store.getters['chat/loginUser'] }, }, data () { return { email: '', password: '', thumbnail: '', user: { email: '', name: "", message: "", thumbnail: '', }, } }, methods: { submit () { this.$store.dispatch('chat/submit', { name: this.user.name, message: this.user.message, userId: this.user.email, thumbnail: this.user.thumbnail }) this.user.name = '' this.user.message = '' }, login(email, password) { this.$store.dispatch('chat/login', { email: this.email, password: this.password }) }, changeImg (e) { this.thumbnail = e.target.files[0] if (this.thumbnail) { const reader = new FileReader() reader.onload = () => { this.user.thumbnail = reader.result + '' } reader.readAsDataURL(this.thumbnail) } }, }, } </script> |
画像部分の移行
inputで選択した画像を表示させつつ、submitした時にデータを送信📤
ということでreader.readAsDataURL(thumbnail)して
urlをstateに入れて
表示させようとしたのですが
これはできませんでした。<img :src="$store.getters['change/thumbnail']" alt="">
バインドするdataがstateに置き換わっているので
こう書くしかないんですよね…。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<template> <div class="page"> <form @submit.prevent> <label class="img" > <input type="file" class="file" @change="changeImg" > </label> <img :src="this.$store.getters['change/thumbnail']" alt=""> <button type="submit" @click="submit" > Submit </button> </form> </div> </template> <script> import axios from 'axios' import firebase from '@/plugins/firebase' export default { data () { return { user: { thumbnail: '', }, } }, methods: { submit () { this.$store.dispatch('change/submit') }, changeImg (e) { this.$store.dispatch('change/change', { e: e, thumbnail: this.thumbnail, userThumbnail: this.user.thumbnail }) }, }, } </script> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import axios from 'axios' import firebase from '@/plugins/firebase' export const state = () => ({ thumbnail: '', }) export const getters = { thumbnail: state => { return state.thumbnail }, } export const actions = { submit ({ commit }, { name, message, email, thumnail }) { // 省略 }, change ({ commit }, { e, thumbnail, userThumbnail }) { thumbnail = e.target.files[0] if (thumbnail) { const reader = new FileReader() reader.onload = () => { console.log(e.target.result) userThumbnail = reader.result + '' } reader.readAsDataURL(thumbnail) } commit('change', thumbnail, userThumbnail) }, } export const mutations = { change (state, thumbnail, userThumbnail) { state.thumbnail = userThumbnail } } |
そもそもimgで表示させるのではなくinputに表示させてみる方が
良いかもしれません🤔💭
というより、
プレビューだけならVuexにする必要もないかもです。readerで何をしているか明確にし、
分けていこうと思います💡
textareaの選択した文字に装飾(ボツネタ)
コードを共有したい時
選択範囲のテキストを
コード用の装飾に変更します。
なのでtextarea内の
選択範囲の先頭と後ろに
タグを追加して
実装しようとしていました。
ただ、
そもそもtextareaに
タグを入れてはいけない
ようで…
ボツになりました笑
参考になるか分かりませんが
それまでの道のりを
記しておきます…。
参考:
javascript 選択中のテキストを取得する
範囲選択した文字列を修飾する
JavaScript初級者から中級者になろう
v-htmlで表示確認
とりあえず
メッセージ入力部分がinputになっていたのでtextareaに変更。
textareaに
手打ちでタグを適当に入れてv-htmlで表示させてみます。<p style="color: red;">red</p>

firebase上では
こんな感じ。

表示部分
|
1 2 3 4 5 |
<div> {{ post.name }} | | {{ $dayjs(post.time).format('MM-DD HH:mm') }} <img :src="post.thumbnail" alt=""> <p v-html="post.message">{{ post.message }}</p> </div> |
ということで
あとは選択範囲を認識させてspanタグとかをつけて
firebaseに送信できれば
大丈夫そうです🙆♀️
選択範囲の認識
そもそもtextareaに
タグを直接入れてはいけないので<は>>は<
に変更する必要はありそう。
参考:
選択(Selection) と 範囲(Range)
テキストエリア内にタグ?
selectイベントで
まずは選択範囲の
文字列だけ取得します💫
|
1 2 3 4 5 6 7 8 9 10 |
<label> <span> message: </span> <textarea v-model="user.message" @select="select" > </textarea> </label> |
methodsで
選択範囲のSelectionオブジェクトを取得。
|
1 2 3 |
select () { let obj = document.getSelection() }, |
ここからRangeオブジェクトを取得
とりあえずgetRangeAtの引数は
空ではダメっぽい。
TypeError: Failed to execute 'getRangeAt' on 'Selection': 1 argument required, but only 0 present.
rangeにnode追加
とりあえずnodeの種類もよく分からないのでcreateTextNodeを使ってみました。
https://qiita.com/mima_ita/items/bacf3ca3c078d1e6c70f
どこにタグが来るのか
チェックするためこれだけ追記。
|
1 2 3 4 5 6 7 |
select () { let obj = document.getSelection() let range = obj.getRangeAt(0) console.log(range) let textNode = document.createTextNode("<span>") range.insertNode(textNode) }, |
ところが。。。
textarea内の文字列を選択しても
枠の外にタグが追加されます。。。
そもそもタグではなく文字列扱いに。createElementも試しましたが
何も追加されませんでした。

やっぱりtextareaに
タグを入れること自体
よくないようですね。
無難にエディタを使用した方が良さそうです。
https://qiita.com/mima_ita/items/bacf3ca3c078d1e6c70f
まとめ
Vuexに慣れてきたものの、
画像のところで
詰まってしまいました😖💦
ただ次にやれば良いことが
見えてきたので必ず移行させます…❗️
移行して機能に問題なければ
CSS整えて完成予定です…❣️
そして今回はボツになってしまった
選択範囲の装飾。
CSSとかの方がよっぽど大事ですが
気になるので調べまくりました。。。
でも無駄だったというのが
とても悲しいです😭笑
まぁそんな時もありますよね←