20210416のvue.jsに関する記事は9件です。

【vue×firebase】Vue2.0で.envの利用法 ~APIkeyを隠そう~

環境 vue 2.0系 firebase はじめに こんにちは!個人開発でいろいろ開発しているものです! twitter →https://twitter.com/js_manabitai 普段はvue2.0系とfirebaseを使用して開発しています! コードをgithubなどにあげる際、firebaseのapi情報を隠さなくてはなりません。 聞いた話によると、github上にはAPI情報を自動的に取得するクローラーが走っているらしく、APIを公開してしまうと、firebaseを不正利用されて高額請求される恐れがあります。 そのため、github上にはAPI情報をあげては行けません! そのために利用するのが.envというファイルです。 こちらの使用方法ですが、vue2.0での情報が少なく、まとまっていないものや不親切な記事が多かったため、記述しました。 まずはターミナルでコマンドを叩きます。 npm install dotenv --save --saveをつけることで、package.jsonに情報が記載されます。 試しに追加されているか見てみましょう。 しっかり追加されていますね。 それではルートのmain.jsにて、dotenvを利用するコードを書きましょう。 import Vue from 'vue' import App from './App.vue' import router from './router' import vuetify from './plugins/vuetify'; import firebase from 'firebase' import dotenv from 'dotenv' Vue.config.productionTip = false dotenv.config() 必要なのはdotenvのimportとdotenv.config()の部分です。 これが完了したら、ディレクトリのルート直下に.envというファイルを作成してください そして、忘れないうちに.ignoreに.envを追加しましょう # local env files .env .env.local .env.*.local ここまできたら、.envにAPI情報を入力してください VUE_APP_APIKEY=XXXXXXXXXXXXXXXXXXXXX, VUE_APP_AUTHDOMAIN=tXXXXXXXXXX.com, VUE_APP_PROJECTID=XXXXXXXXX, VUE_APP_STORAGEBUCKET=tXXXXXXXXXXXXXXXXXXXcom, VUE_APP_MESSAGINGSENDERID=XXXXXXXXXXXXXXXXXXX, VUE_APP_APPID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, VUE_APP_MEASUREMENTID=XXXXXXXXXXXXXXXXXXX vue3.0ではVUE_APP_**としないと反応しないらしいです。 ここでも念のためVUE_APPから始めています。 登録できたら、一度app.vueのcreated内などでコンソールに表示されるか確認してみましょう。 export default { created() { console.log("env",process.env.VUE_APP_APIKEY); }, } ちゃんと中身が表示されれば、main.jsの表示を変更したい部分を変数に置き換えましょう。 var firebaseConfig = { apiKey: process.env.VUE_APP_APIKEY, authDomain: process.env.VUE_APP_AUTHDOMAIN, projectId: process.env.VUE_APP_PROJECTID, storageBucket: process.env.VUE_APP_STORAGEBUCKET, messagingSenderId: process.env.VUE_APP_MESSAGINGSENDERID, appId: process.env.VUE_APP_APPID, measurementId: process.env.VUE_APP_MEASUREMENTID }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.analytics(); きちんとデータが表示されれば成功です。 ※この際に、正しくデータが表示されないなら、サーバーを再起動してください。 npm run serve 私は再起動しなかったため、何時間もエラーに悩まされました。 .envにAPI情報を載せる手順って結構めんどくさいですね、、、 効率化できる方法があれば教えてください。 参考 https://qiita.com/h-reader/items/9e2f8dbc3b9eaec4f5ee https://www.it-mure.jp.net/ja/node.js/vuejs%E3%81%A7dotenv%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/838436806/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

methodsとcomputedの違い

マスタッシュの中に関数を書くと画面が再描画されるたびに呼ばれる。それを解決するのがcomputed <div id="app"> <p>{{ count }}</p> <button @click="count += 1">+1</button> <p>{{ otherCount }}</p> <button @click="otherCount += 1">+1</button> <p>{{ lessThanThreec }}</p> <p>{{ lessThanThreem() }}</p> </div> new Vue({ el: "#app", data: { count: 0, otherCount: 0 }, computed: { lessThanThreec: function() { console.log('computed'); return this.count > 3 ? "3より上" : "3より下"; } }, methods: { lessThanThreem: function() { console.log('methods'); return this.count > 3 ? "3より上" : "3より下"; } } })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v-on:clickで文字を反転させる

はじめに Vue.jsを学習中の備忘録としてアウトプット。 Vue.jsはスッキリしてていいですね。 結果 See the Pen Vue.js反転 by morioka (@rm5912) on CodePen. vue.js const app= new Vue ({ el: "#app", data: { message: 'Hello World' }, methods: { reverseMessage: function() { this.message = this.message.split('').reverse('').join('') } } }) elでidを指定して, dataの中にmessageを記述。 methodsでreverseMessageメソッドを作成して メッセージをsplitで区切ってから反転、joinで結合して返してます。 index.html <div id = "app"> <p> {{ message }} </p> <button v-on:click="reverseMessage">メッセージ反転</button> </div> マスタッシュ構文でmessagemを囲んで表示。 v-on:clickにメソッドを指定してイベント発火するようにしてあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで学んだこと②

はじめに 前回、Vue.jsを学び Vue.jsを使って何を作るか? Vue.jsを使ってやってみたい事は何か? ということを考えてみた。 Vue.jsを使って何を作るか? 真っ先に思いついたのが、コロナ渦の影響によりテレワークが多くなってきている昨今 出退勤の管理を行うところも増えてきている。 もちろん勤怠管理システムを使っているところもあるだろうが、 簡単にGSSを使って管理するところもあるので GSSと連携したSPAの出退勤管理を作ろうと考えました。 Vue.jsを使ってやってみたい事は何か? 上記で何を作るかが決まったので、やってみたい事は何か? 管理をする上で必要なものは認証だと考えています。 なのでGoogle認証や、Firebase認証を使ってログインしたユーザの出退勤を管理・・・とまでは考えています。 (あくまでもざっくりベース) Firebase認証 Google認証よりまず、Firebase認証を試してみました。 以前GoogleやTwitter、Facebook等の認証系は触ったことはありましたが、 Firebaseはそれより簡単に設定できました。 1.Filebaseでプロジェクトを作成 Filebaseにアプリを追加するだけ!(とても簡単!) 2.認証方法を設定 Authenticationページでmethodを有効にするだけ!(とても簡単!) 3.プロジェクトにアプリを追加 FilebaseのAPI_KEYや認証ドメインを取得するためにアプリを追加するだけ! 今回はWebページですので以下画面になります。 これだけです! あとは追加したアプリのAPI情報をvue.jsの方に設定して認証するだけで簡単に認証することができました! vue.js側の設定 methods: { signIn: function () { firebase .auth() .signInWithEmailAndPassword(this.username, this.password) .then( (user) => { alert("Success!"); this.$router.push("/"); }, (err) => { alert(err.message); } ); }, } 終わりに vue.js側の設定も簡単で、こんなにも簡単に認証ができるとは思わなかったのでビックリしました。 JavaだとAPIキーからトークンを取得したり・・・なんやかんやして認証APIを叩いたりしていたので結構大変だった印象があります。 ※もちろんライブラリを使えば楽に実装はできたのでしょうが・・・ 次回はGoogle認証を試してみたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話と解決法

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 RepsonaではSlackとかTeamsみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かったので、その完成形コードと、そこに至るまでのハマりポイントや解決法などをまとめておきます。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 完成形 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりました。 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでSlackみたいなメンションと入力中の名前色付けを作った完成形コードとハマった話

Repsona LLCの@GussieTechです。「理想のプロジェクト管理ツール」Repsonaを開発しています。 作ったもの これを作るのに大変苦労しました。textareaの特定文字に色はつけられません。ではどうするか?そんなお話です。 忙しい人へ 完成形のコード抜粋をCodeSandboxにつくりましたのでご利用ください。でも・・僕の苦労話も是非読んでいってください・・ 環境 Vue v2.x + Bootstrap v4.x + vue-mention ハマった話と解決法 RepsonaではSlackみたいにメンションでお知らせできるようになっています。受信トレイの新規開発に合わせて、メンションの体感もカイゼンしようということで立ち向かいました。色付きメンションの記事は案外ないのでお役に立てれば嬉しいです。 もくじ メンションポップアップ Tributeが期待した位置に表示されてくれない vue-mentionが表示されない vue-mentionの表示位置がおかしい 全角@で期待通りに動いてくれない 入力中の名前色付け textarea内の文字に色をつける方法がない スクロールバーの有無で色付け位置がズレる メンションポップアップ Tributeが期待した位置に表示されてくれない リリース当初よりTributeでメンションの動作を実装していました。通常の文字数や表示位置では問題なく動作していたのですが、画面全体のスクロール位置や入力文字数によって期待した位置に表示されない問題がありました。かなり試行錯誤をしたのですがうまくいかなかったため、vue-mentionに乗り換えることにしました。 vue-mentionが表示されない 乗り換えたところ、なぜか表示されませんでした。これがかなりハマりました。vue-mentionはv-tooltipに依存しているのですが、Bootstrapの.tooltipクラスとぶつかって、なんとopacity: 0;が採用されてしまっていました。 v-tooltip内の.tooltipに関しては、opacity: 1;が適用されるようにCSSを設定して回避しました。 [id^="popover_"].tooltip { opacity: 1; } vue-mentionの表示位置がおかしい いや違う、左側ではなく右側にでて欲しいのです。デモでは右に出ているのです。 細かい原因は調べませんでしたが、きっとまたBootstrapのCSSと競合しているのだろうと思いました。そこでまるっとv-tooltipのCSSが優先して適用されれば良いのではないかと考えました。 CSSは、対象を詳しく指定しているセレクタが優先されるので、idと合わせて指定することでオーバーライドしてやります([id^="popover_"].tooltip)。v-tooltipのデフォルトのCSSで上書きすることで、うまく解決しました。 [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, .1); } .popover-arrow { border-color: $color; } } &[aria-hidden='true'] { visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; } &[aria-hidden='false'] { visibility: visible; opacity: 1; transition: opacity .15s; } } きました!(幅やカラー指定の調整は省略しています) 全角@で期待通りに動いてくれない 全角@でポップアップが出て、確定するとなんだか@がいっぱい出てきてしまいます。 ドキュメントに記載はないのですが、どうやら隠しオプションみたいなものがあって、omitKeyというのを見つけました。 これをセットすることで、起動キーを排除した上で、valueをセットできるようになりました。valueには@も含めることで、期待通り動作します。でも、まだもうひとつ@が残ってしまいます。 IMEの変換確定とメンションの決定が重なって、決定後に@が残ってしまっています。IME確定後のみ決定を発動させる必要がありそうです。 vue-mentionのコードをみてみるとonKeyDown()が担っているようです。この部分ですね。 if ((e.key === 'Enter' || e.key === 'Tab' || e.keyCode === 13 || e.keyCode === 9) && 非常にdirtyですがe.key === 'Enter' || e.key === 'Tab'を無効にするオーバーライドをすることで回避しました。しかし、keyCodeはdeprecatedなので、いずれ期待通り動かなくなるかもしれません。他サービスで全角@が気持ちよく動作しないものがあるのは、きっとこの辺りの事情があるのでしょう。英語圏の人たちには関係ないでしょうし・・。ところで、正しくはどう実装するべきなのだろう。 入力中の名前色付け textarea内の文字に色をつける方法がない さまざまなサービスで簡単そうに実現しているのですが、甘くみていました。textarea css font-colorなどとググってみても、どうやらtextarea内の特定の文字に色をつける方法はないようです。 そうするとcontenteditableが頭をよぎりますが、プレーンテキストとして取り扱うのはかなりしんどさがあります(以前とんでもなくハマったのですがここでは省略します)。 そこで、textareaに、入力中の文字とまったく同じ文字を表示するdivを重ねる方法を採用しました。 ピンクで表示したdiv内にtextareaで記述した内容をそのまま透明文字で表示して、必要に応じて<span>等でカラーをつける作戦です。仮実装してすぐ期待通りに動いたので、簡単そうに見えました。しかし・・・ スクロールバーの有無で色付け位置がズレる ああ・・なるほど。スクロールバーさんを忘れていました。サイズ変更などでも表示されますね。スクロールバー表示有無を検出してスクロールバー幅分をpaddingしてやる必要がありそうです。 const barWidth = this.$refs.comment.$el.getBoundingClientRect().width - this.$refs.comment.$el.clientWidth (実際はtextareaに適用されたボーダーなどのスタイルも加味して計算しています) うまくいきました。 完成形 <template> <div id="app"> <div class="p-3"> <div class="position-relative"> <div ref="textareaCover" class="textarea-cover" v-html="commentMention" ></div> <mentionable :keys="['@', '@']" :items="users" insert-space omit-key> <textarea ref="comment" class="form-control comment" rows="4" v-model="comment" @keyup="commentScroll" @scroll="commentScroll" /> <template #item="{ item }"> <div class="user"> <span class="font-weight-bold"> {{ item.value }} </span> <span class="ml-2"> {{ item.firstName }} </span> </div> </template> </mentionable> </div> </div> </div> </template> <script> import { Mentionable } from "vue-mention"; Mentionable.methods.onKeyDown = function (e) { if (this.key) { if (e.key === "ArrowDown" || e.keyCode === 40) { this.selectedIndex++; if (this.selectedIndex >= this.displayedItems.length) { this.selectedIndex = 0; } this.cancelEvent(e); } if (e.key === "ArrowUp" || e.keyCode === 38) { this.selectedIndex--; if (this.selectedIndex < 0) { this.selectedIndex = this.displayedItems.length - 1; } this.cancelEvent(e); } if ( (e.keyCode === 13 || e.keyCode === 9) && this.displayedItems.length > 0 ) { this.applyMention(this.selectedIndex); this.cancelEvent(e); } if (e.key === "Escape" || e.keyCode === 27) { this.closeMenu(); this.cancelEvent(e); } } }; export default { name: "App", components: { Mentionable, }, data() { return { comment: "", users: [ { value: "@akryum", firstName: "Guillaume", }, { value: "@posva", firstName: "Eduardo", }, { value: "@atinux", firstName: "Sébastien", }, ], }; }, computed: { commentMention() { if (typeof this.comment?.replaceAll !== "function") { return this.comment; } const replaced = this.comment ?.replaceAll("&", "&amp;") ?.replaceAll(">", "&gt;") ?.replaceAll("<", "&lt;") + "\n\n"; const search = new RegExp( this.users .slice() .sort((a, b) => b.value?.length - a.value?.length) .map((user) => user.value) .join("|"), "g" ); return replaced.replace(search, (match, offset) => { return `<span class="mention-str">${match}</span>`; }); }, }, methods: { mounted() { setTimeout(() => { this.resize(); window.addEventListener("resize", this.resize); this.$once("hook:beforeDestroy", () => { window.removeEventListener("resize", this.resize); }); }); }, resize() { const barWidth = this.$refs.comment.getBoundingClientRect().width - this.$refs.comment.clientWidth - 2; // border this.$refs.textareaCover.style.paddingRight = `calc(12px + ${barWidth}px)`; }, commentScroll() { this.$refs.textareaCover.scrollTop = this.$refs.comment.scrollTop; this.resize(); }, }, }; </script> [id^="popover_"].tooltip { display: block !important; z-index: 10000; .tooltip-inner { background: black; color: white; border-radius: 16px; padding: 5px 10px 4px; } .tooltip-arrow { width: 0; height: 0; border-style: solid; position: absolute; margin: 5px; border-color: black; z-index: 1; } &[x-placement^="top"] { margin-bottom: 5px; .tooltip-arrow { border-width: 5px 5px 0 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; bottom: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="bottom"] { margin-top: 5px; .tooltip-arrow { border-width: 0 5px 5px 5px; border-left-color: transparent !important; border-right-color: transparent !important; border-top-color: transparent !important; top: -5px; left: calc(50% - 5px); margin-top: 0; margin-bottom: 0; } } &[x-placement^="right"] { margin-left: 5px; .tooltip-arrow { border-width: 5px 5px 5px 0; border-left-color: transparent !important; border-top-color: transparent !important; border-bottom-color: transparent !important; left: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &[x-placement^="left"] { margin-right: 5px; .tooltip-arrow { border-width: 5px 0 5px 5px; border-top-color: transparent !important; border-right-color: transparent !important; border-bottom-color: transparent !important; right: -5px; top: calc(50% - 5px); margin-left: 0; margin-right: 0; } } &.popover { $color: #f9f9f9; .popover-inner { background: $color; color: black; padding: 10px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1); } .popover-arrow { border-color: $color; } } &[aria-hidden="true"] { visibility: hidden; opacity: 0; transition: opacity 0.15s, visibility 0.15s; } &[aria-hidden="false"] { visibility: visible; opacity: 1; transition: opacity 0.15s; } } .mention-item { padding: 4px 10px; border-radius: 4px; } .mention-selected { background: #00ABE7; color: white; } .textarea-cover { z-index: 1; position: absolute; color: transparent; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; overflow-y: hidden; padding: 6px 12px; border: 1px solid transparent; } .comment { width: 100%; } .mention-str { color: #00ABE7; background-color: rgba(0, 171, 231, 0.05); } 実際のサービス上は血の滲むようなCSSの調整をがんばっております。もしかしたらまだバグがあるかもしれません。何かお気づきの点がありましたらお知らせいただけますと幸いでございます。 まとめ vue-mention + Bootstrap は css をオーバーライド(リセット)して使う 全角@問題はkeyCodeで解決する(ただしdepricatedなのが悩ましい) textareaに色付けできないので上に文字を重ねて表現する 簡単そうで実は難しい「普通の動き」の実装にとっても苦労してハマりました。これで気持ちよくメンションして受信トレイでサラサラと通知を管理できるようになりそうです。ますます便利になった「プロジェクト管理と情報共有のためのツール(ガントチャート無料)」Repsona、ぜひお試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelからVueにのデータ渡す

ネット上の情報を収集してから、実装した上での心得を整理してみました。 背景 Bladeファイルでは、Laravel Controllerからのデータを受け取って使うことが可能ですが、Vue.jsファイルの中に同じことができるかどうかを知りたくて、調査してみました。 調査した結果だと現時点では同じことができなさそうですが、代案がありまして、これを実装すればControllerからViewに渡すデータをVue側で取れます。 jsの処理をBladeファイル内に実装すれば良いではないか? そういうのも考えましたが、これも代案の一つ選択肢だと思います。 けど、やはりjs処理をBladeファイルに書きたくないので、別の案をしました。 対応方法 簡単な方法ですので、先に内容を書いておきます。 Bladeファイル内Controllerから渡してきたデータを受け取って、jsのGlobal変数に保存するとVue側で利用することが可能になります。 これからは対応の詳細を書きます。 1. 全体の構成 2. 本題に入る 全体の構成 Controllers app/Controllers/xxxController.php 表示したいViewと渡したいデータを設定します。 xxxController.php ...略 public function index() { ...略 return view('life_style/life_style')->with('data', $data); } Views resources/views/yyy.blade.php Viewの構成を管理します。 また、laravel-mixを使ってVue関連処理のjsファイルをrequireしますので、Vue関連処理のjsファイル(resources/js/zzz.js)が必要となります。また、resources/js/zzz.jsにはresources/bootstrap.jsをrequireしています。 yyy.blade.php ...略 <script src="{{ mix('js/life_style.js') }}"></script> zzz.js require('../bootstrap'); ...略 laravel-mixを使うため、webpack.mix.jsも編集します。 webpack.mix.js mix .js('resources/js/zzz.js', 'public/js') .vue(); Routes ルートを設定するため、routes/web.phpを編集します。 web.php ...略 Route::get('/xxx_index', [xxxController::class, 'index'])->name('xxx.index'); ここまでのまとめ 一つの画面を作成するには、追加&修正が必要なファイルは下記となります。 - xxxController.php - yyy.blade.php - zzz.js - webpack.min.js - web.php 本題に入る Vue側でControllerのデータを使う 対応方法にも書きましたが、まずはBladeファイル側でControllerのデータを受け取る。受け取ったデータをwindow.LaravelというGlobal変数に保存します。 同じ処理を各Bladeファイルに実装するのではなく、共通化できるようにしたいため、親Bladeファイル(base.blade.php)を作成し、その中に処理を実装し、各Bladeファイルがこの親Bladeファイルに継承するようにしました。 下記のように、<script>内にjsの処理を実装しました。 base.blade.php ...略 <script> window.Laravel = {!! json_encode(['data' => $data ?? null, 'api_token' => $api_token ?? null ]) !!}; </script> Vue側で利用する let data = window.Laravel.data; これでVue側で利用することができました! 追加:api_tokenの処理 api_tokenもController側から渡してくる場合は、同じくjs側で使えます。 APIを呼び出す時に必要なデータですので、axiosに設定しておくと便利です。 (注意点としては、Blade側でControllerのデータを取得して、Global変数に設定するのをbootstrap.js実装するのより優先にしないと行けないことです。) bootstrap.js window.axios = require('axios'); if (Laravel && Laravel.api_token) { window.axios.defaults.headers .common['Authorization'] = 'Bearer '+ Laravel.api_token; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelからVueにデータ渡す

ネット上の情報を収集してから、実装した上での心得を整理してみました。 背景 Bladeファイルでは、Laravel Controllerからのデータを受け取って使うことが可能ですが、Vue.jsファイルの中に同じことができるかどうかを知りたくて、調査してみました。 調査した結果だと現時点では同じことができなさそうですが、代案がありまして、これを実装すればControllerからViewに渡すデータをVue側で取れます。 jsの処理をBladeファイル内に実装すれば良いではないか? そういうのも考えましたが、これも代案の一つ選択肢だと思います。 けど、やはりjs処理をBladeファイルに書きたくないので、別の案をしました。 対応方法 簡単な方法ですので、先に内容を書いておきます。 Bladeファイル内Controllerから渡してきたデータを受け取って、jsのGlobal変数に保存するとVue側で利用することが可能になります。 これからは対応の詳細を書きます。 1. 全体の構成 2. 本題に入る 全体の構成 Controllers app/Controllers/xxxController.php 表示したいViewと渡したいデータを設定します。 xxxController.php ...略 public function index() { ...略 return view('/yyy')->with('data', $data); } Views resources/views/yyy.blade.php Viewの構成を管理します。 また、laravel-mixを使ってVue関連処理のjsファイルをrequireしますので、Vue関連処理のjsファイル(resources/js/zzz.js)が必要となります。また、resources/js/zzz.jsにはresources/bootstrap.jsをrequireしています。 yyy.blade.php ...略 <script src="{{ mix('js/zzz.js') }}"></script> zzz.js require('../bootstrap'); ...略 laravel-mixを使うため、webpack.mix.jsも編集します。 webpack.mix.js mix .js('resources/js/zzz.js', 'public/js') .vue(); Routes ルートを設定するため、routes/web.phpを編集します。 web.php ...略 Route::get('/xxx_index', [xxxController::class, 'index'])->name('xxx.index'); ここまでのまとめ 一つの画面を作成するには、追加&修正が必要なファイルは下記となります。 - xxxController.php - yyy.blade.php - zzz.js - webpack.min.js - web.php 本題に入る Vue側でControllerのデータを使う 対応方法にも書きましたが、まずはBladeファイル側でControllerのデータを受け取る。受け取ったデータをwindow.LaravelというGlobal変数に保存します。 同じ処理を各Bladeファイルに実装するのではなく、共通化できるようにしたいため、親Bladeファイル(base.blade.php)を作成し、その中に処理を実装し、各Bladeファイルがこの親Bladeファイルに継承するようにしました。 下記のように、<script>内にjsの処理を実装しました。 base.blade.php ...略 <script> window.Laravel = {!! json_encode(['data' => $data ?? null]) !!}; </script> Vue側で利用する let data = window.Laravel.data; これでVue側で利用することができました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.js × firebaseによる再認証機能

前提 使用言語:vue.js、firebase 開発環境:vueCLI 再認証機能とは firebaseのAuthenticationに登録されている自分のユーザー情報を更新・変更するときにパスワードを入力して、以前に設定したパスワードと合致すればユーザー情報を更新・変更できるというもの。 ちなみにconsole.logによる開発ツールでパスワードを確認することはできない。 コードは以下の通り EmailAuthProvider.credentia()に自分で入力したメールアドレスとパスワードを引数として渡す。 EmailAuthProvider を使って認証情報を発行し、reauthenticateAndRetrieveDataWithCredentialで再認証。 main.vue const user = firebase.auth().currentUser const credential = firebase.auth.EmailAuthProvider.credential("email", "password") user.reauthenticateWithCredential(credential) 実装例 テキストフォームに入力したpasswordをv-modelでcredential()に渡すことで、再認証の処理が開始される。 ユーザー再認証→ パスワード合致OK→ メールアドレス変更処理 という流れで処理される。 data(){ return { password: " } }, methods: { onReAuth(){ //ユーザー再認証 const user = firebase.auth().currentUser const credential = firebase.auth.EmailAuthProvider.credential(user.email, this.password) user.reauthenticateWithCredential(credential) .then(()=> { console.log("再認証完了") }) //メールアドレス更新 .then(() =>{ const user = firebase.auth().currentUser user.updateEmail(this.email).then(()=> { console.log("メールアドレス更新完了") }) .catch((error)=> { console.log(error) }) }) } } <v-text-field v-model="password"> </v-text-field> 参照リンク https://tenderfeel.xsrv.jp/javascript/3606/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む