20210417のvue.jsに関する記事は8件です。

Vue.js+Firebase Authentication(メール/パスワード)で認証

何故Firebase Authenticationで認証しようとしたのか 当初はBasic認証させたかった(Functionsの利用)が、 Firebaseの無料枠では出来なかったため、 Firebase Authenticationでメールアドレス+パスワード認証をすることにした 実装方法 Firebaseの設定 ①プロジェクト作成 ②Authenticationの設定 ・[Authentication]>[Sign-in method]>[メール/パスワード]を有効に変更 ・[Authentication]>[Users]>[ユーザー追加]で使用するメール/パスワードを登録しておく ③プロジェクトの設定 ・[設定]>[全般]でウェブ API キーをコピー ・[設定]>[サービスアカウント]でAdmin SDK 構成スニペットをコピー ・新しい秘密鍵の生成を押下して、秘密鍵情報のjsonファイルを取得(serviceAccountKey.jsonにリネームしておく) コーディング(Vue.js) main.js import firebase from 'firebase' var admin = require("firebase-admin"); //③で取得した秘密鍵情報のjsonファイルを指定 var serviceAccount = require("path/to/serviceAccountKey.json"); const config = { apiKey: 【③で取得したAPIキー】, credential: admin.credential.cert(serviceAccount) }; firebase.initializeApp(config); SignIn.vue(新規作成) <div class="signin"> <div> <div class="message"> {{ message }} </div> <div class="input-area"> <b-container> <b-row> <b-col>メールアドレス:</b-col> </b-row> <b-row> <b-col><b-form-input type="email" v-model="mailaddress"/></b-col> </b-row> <br> <b-row> <b-col>パスワード:</b-col> </b-row> <b-row> <b-col><b-form-input type="password" v-model="password"/></b-col> </b-row> </b-container> <br> <b-button variant="primary" @click="signIn">サインイン</b-button> </div> </div> </div> <script> import firebase from 'firebase' export default { name: 'SignIn', data() { return { mailaddress: '', password: '', message: '', }; }, methods: { signIn: function () { //firebaseSDKのサインイン関数を利用 firebase.auth().signInWithEmailAndPassword(this.mailaddress, this.password) .then(()=>{ // 成功時の処理 console.log("OK"); this.$router.push({ name: 【TOPページ】 }); } ) .catch(()=>{ // エラー時の処理 console.log("NG"); this.message = "サインイン情報が間違っています。"; this.password = ""; } ) } } } </script> 追記 ・basic認証代わりなので、今回は手動でメール/パスワードを用意したが、 認証ユーザー作成(firebase.auth().createWithEmailAndPassword)も可能 ・認証状態の取得(firebase.auth().onAuthStateChanged)とrouter.beforeEachを組み合わせれば、認証されていな場合は、サインイン画面に戻る等も実装可能
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】graphql-rubyでのオフセットベースのページネーションの実装方法【Graphql】

要件からカーソルベースではなくオフセットベースのページネーションを作らないといけなかったがスムーズにいかなかったので当時のメモ。先輩にも助けて頂いた。 使用技術は フロントエンド - nuxt + vuetify バックエンド - rails + Graphql という組み合わせ 実現方法としては - カーソルベースで一度頭から該当ページのカーソルIDを取ってそれを元に1ページの個数をとるという2度APIを取る方法 - graphql-rubyにコードを追加してoffsetやtotal countを返す方法 の二つ考えられるが今回は後者を書く。 app/graphql/offset_extension.rb class OffsetExtension < GraphQL::Schema::Field::ConnectionExtension def apply super field.argument :offset, 'Int', 'Offset value.', required: false end def after_resolve(args) offset = args[:memo][:offset] new_args = offset ? args.merge(value: args[:value].offset(offset)) : args super(new_args) end end app/graphql/types/base_field.rb module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument connection_extension(::OffsetExtension) end end app/graphql/types/pagination_connection.rb module Types class PaginationConnection < GraphQL::Types::Relay::BaseConnection field :total_count, Int, null: false def total_count object.items.size object.items.unscope(:offset).unscope(:limit).count end end end 参考 GraphQL RubyのPaginationについて|Daiki Tanaka|note GraphQLでのPaginationの実装方法について(for ruby) - Qiita graphql-rubyでページネーションがサクッと実装できたのでGemが何をやっているのか覗いてみた - Qiita RailsでGraphQL APIを作る時に悩んだ5つのこと | スペースマーケットブログ Offset based pagination in GraphQL-ruby - Blog by Abhay Nikam Introduction to pagination in GraphQL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QR忘れ物アプリ (2) ログインページ [Vue,Vuetify,Firebase]

はじめに 前回:環境構築 https://qiita.com/rayan/items/5d04ee2ca7860c220dec GitHubリポジトリ 準備中 用いる省略名称 RTDB: Realtime Database 操作するファイル public index.js authenticationOps.js databaseOps.js src router index.js views Authentication.vue (新規ファイル) App.vue public/index.js authenticationOps.js、databaseOps.jsを呼び出すscriptの一覧に追加。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- update the version number as needed --> <script src="/__/firebase/8.3.1/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script src="/__/firebase/8.3.1/firebase-auth.js"></script> <script src="/__/firebase/8.3.1/firebase-database.js"></script> <script src="/__/firebase/8.3.1/firebase-functions.js"></script> <!-- initialize the SDK after all desired features are loaded, set useEmulator to false to avoid connecting the SDK to running emulators. --> <script src="/__/firebase/init.js?useEmulator=true"></script> <script src="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.js"></script> <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.css" /> <script type="application/javascript" src="databaseOps.js"></script> <script type="application/javascript" src="authenticationOps.js"></script> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> public/databaseOps.js Firebase RTDB関連のfunctionを格納 addUser ユーザーを追加し、userPreferencesを初期設定します。 Firebase AuthのユーザーID(userId) をkeyとして用います。 var vars = {}; // stores global variable function addUser(userId, userName, email){ var data = { 'email': email, 'name': userName }; vars.userId = userId; userCredentials.add(data); userPreferences.init(); } userCredentials = { 'add': function (data) { firebase.database().ref('userCredentials/'+vars.userId).set(data); }, }; userPreferences = { 'init': function () { var data = { 'notifyAtLinkOpen': false, 'notifyNotLostItem': true, 'whereToNotify': 'email' }; firebase.database().ref('userPreferences/'+vars.userId).set(data); }, }; public/authenticationOps.js Firebase Auth関連のfunctionを格納 mounted mountedは要素がDOMに追加されたときに呼ばれます。 uiconfig EmailAuthProvider: メールアドレスとパスワードによるログインを行います。 callbacks signInSuccessWithAuthResult サインインに成功したときに呼ばれます。 サインイン結果からuid、displayName、emailを得ることができます。 新規登録の時にはaddUserを呼び、RTDBにユーザーを登録します。 uiShown UIがロードされたときに呼ばれます。今回はロードが終了したため、loaderの表示を消します。 function loginScreen (){ // FirebaseUI config. var uiConfig = { signInOptions: [ // Leave the lines as is for the providers you want to offer your users. //firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.EmailAuthProvider.PROVIDER_ID, ], callbacks: { signInSuccessWithAuthResult: (authResult, redirectUrl) => { var userId = authResult.user.uid; if (authResult.additionalUserInfo.isNewUser){ // if new user, register to database addUser(userId, authResult.user.displayName, authResult.user.email) } document.getElementById('userAuthentication').style.display = 'none'; //document.getElementById('mainMenu').style.display = 'block'; return false; }, uiShown: () => { document.getElementById('loader').style.display = 'none'; }, }, }; // Initialize the FirebaseUI Widget using Firebase. var ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig); } src/views/Authentication.vue src/views内に新しくAuthentication.vueを作ります。ログイン・新規登録画面のHTMLです。 ログイン・新規登録画面画面 loaderはログイン・新規登録画面のdiv要素がロード中に"Loading..."と表示するdiv要素です。 firebaseui-auth-containerはログイン・新規登録画面のdiv要素です。 <template> <!-- authentication --> <div id="userAuthentication" style="display:block"> <div id="firebaseui-auth-container"></div> <div id="loader">Loading...</div> </div> </template> <script> export default { name: "Authentication", mounted () { loginScreen(); }, data: () => ({ // }), }; </script> src/router/index.js Authentication.vueの設定を追加します。 import Vue from "vue"; import VueRouter from "vue-router"; import Home from "../views/Home.vue"; Vue.use(VueRouter); const routes = [ { path: "/", name: "Home", component: Home, }, { path: "/authentication", name: "Authentication", // 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/Authentication.vue"), }, ]; const router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes, }); export default router; src/App.vue Authentication.vueの設定を追加します。 <template> <v-app> <v-main> <Authentication /> </v-main> </v-app> </template> <script> //import HelloWorld from "./components/HelloWorld"; import Authentication from "./views/Authentication"; export default { name: "App", components: { Authentication, }, data: () => ({ // }), }; </script> Build ビルドし、動作をテストします。動作のテストにはFirebase Emulatorが必要であるため、起動します。 npm run build firebase emulators:start dist/index.htmlをブラウザで閲覧し下の画像のような画面が出ることを確認します。 アカウント作成実行後、AuthとTRDBに正常にアカウント登録の処理がされていることを確認します。 Firebase Authentication Emulator http://localhost:4000/auth にアクセス 登録したメールアドレス、名前のアカウントが追加されていることを確認する。 Firebase RTDB Emulator http://localhost:4000/database/{データベース名}/data にアクセス 以下のjsonファイルが生成されていることを確認する。 http://127.0.0.1:9000/?ns=[データベース名]: { userCredentials: { [Firebase AuthのUID]: { email: "[Email]" name: "[名前]" } }, userPreferences: { [Firebase AuthのUID]: { notifyAtLinkOpen: false, notifyNotLostItem: true, whereToNotify: "email" } } } 次回 アイテム登録画面
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】Vueコンポーネントの中ではdataは関数である必要がある。

Vue.jsのコンポーネントについて復習しています。 <script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script> <div id="app"> <my-component></my-component> </div> Vue.component('my-component', { data: { number: 12 }, template: '<p>いいね({{number}})</p>' }) new Vue({ el: "#app", }) これだとtemplateの中に記述されているnumberの12が表示されていない。 本当は、()のなかに12と表示させたい。 コンポーネントの中ではdataは関数にする必要がある。 コンポーネントの中ではdataは関数にする必要があります。 だから、記述を少しだけ変更します。 <script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script> <div id="app"> <my-component></my-component> </div> Vue.component('my-component', { data: function(){ return { number: 12 } }, template: '<p>いいね({{number}})</p>' }) new Vue({ el: "#app", }) dataを関数にして、returnで返したいオブジェクトを記述します。 ここでは以下のように記述を変更しました。 data: function(){ return { number: 12 } これで()の中に12という文字が表示されるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-cropperjs Error in render: "TypeError: (0 , _vue.h) is not a function"のエラーについて

エラー情報から原因が掴みにくい問題であり、記事もなかったので挙げておきます。 error検出(2021/04/15) 環境 node: 14.16.1 npm: 6.14.12 vue-croppperjs 5.0.0 エラー一覧 error [Vue warn]: Error in render: "TypeError: (0 , _vue.h) is not a function" TypeError: (0 , _vue.h) is not a function [Vue warn]: Error in mounted hook: "Error: The first argument is required and must be an <img> or <canvas> element." Error: The first argument is required and must be an <img> or <canvas> element. すべてVueCropperコンポーネントから出たエラーです。 原因 最新バージョンによるエラーでした。 バージョン4.0.0に下げることによって解消しました。 npm cmd //最新バージョンのvue-croppperjsをアンインストール npm uninstall --save-dev vue-cropperjs //4.0.0をインストール npm install --save-dev vue-cropperjs@4.0.0 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Js・vue.js】chart.jsでのデータ加工のやり方

こんにちは! 個人的に開発しているvuejs × rails apiのアプリにてグラフを使ったデータ表示を実装した時に躓いたポイントがあったので記事にしたいと思います。 環境 rails 5.2.3 rails 5.2.3 vue.js 2.6.12 参考 【Javascript】配列(オブジェクト)の操作【map/filter/some/reduce】 ActiveRecordで日付ごとにgroup byしたい Railsガイド 躓いたポイント chart.jsの使い方は参考記事がたくさんあったのですが、「じゃあapiで取得したデータをどうやってグラフに渡すの?」ってなり、なかなか参考記事が見つからずに苦労したので... 完成 これがchart.jsを使って表示している棒グラフです やりたい事 1週間の学習時間をDBから取得 1日に複数の学習時間を登録している場合は合計して1つの連想配列にまとめる 学習時間を登録していない日は学習時間を0とする 最終的に取得したデータをchart.jsに渡して棒グラフを表示する はじめにデータを取得 欲しいデータは以下のようなデータです [ {"id":9,"time":1.5,"user_id":1,"created_at":"2021-01-25T12:00:00.000+09:00","day_of_week":1}, {"id":11,"time":2.0,"user_id":1,"created_at":"2021-01-27T09:39:30.000+09:00","day_of_week":3}, {"id":14,"time":0.5,"user_id":1,"created_at":"2021-01-28T07:52:24.000+09:00","day_of_week":4} ] railsのactiveRecordを使って取得します まず今週の日〜土で期間指定します # models/study.rb def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) end これでcreated_atが今週2021/01/24~2021/01/30で期間が指定できます 次にフロントで使うデータを指定して取得します def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) .select(:id, "sum(time) as time", :user_id, :created_at, "dayofweek(created_at) - 1 as day_of_week") end 不要なデータも取ってきてますが気にしない笑 ポイントは、 - sum(time) as timeとして1日に複数回の学習時間(time)を登録している場合もあるので、その合計値を取得する - dayofweek(created_at) -1 as day_of_weekを使って曜日(この場合は曜日の添字である0~6)を取得する mysqlだとdayofweekで取得できる添字が1~7になるので注意 JsではgetDay()を使うと0~6が取得できる なんか揃ってないのが気になるので-1して揃えているだけ 最後に日付ごとにまとめる def self.get_week_chart_data @this_day = Time.now @range = @this_day.all_week(:sunday) self.where(created_at: @range) .select(:id, "sum(time) as time", :user_id, :created_at, "dayofweek(created_at) - 1 as day_of_week") .group("date(created_at)") end これで曜日ごとにグループ化できたので当初取得したかったデータが取得できる このモデルメソッドをコントローラに渡す. ついでにルーティングも設定 # controllers/studies_controller.rb def history histories = current_user.studies.get_week_chart_data // current_userの説明ははしょります. すいません! render json: histories end # config/routes.rb namespace :api, {format: 'json'} do namespace :v1 do get 'histories', controller: :studies, action: :history // RESTfulじゃないのは許して end end 本題 これで下準備が完了. chart.jsにデータを渡す // components/Chart.vue // これは公式の書き方のまま <script> import { Bar, mixins } from 'vue-chartjs'; const { reactiveProp } = mixins export default { extends: Bar, mixins: [reactiveProp], // reactiveProp使わないとデータ更新できないので注意 props: ['options'], mounted () { this.renderChart(this.chartData, this.options) } } </script> // History.vue <template> <div> <Chart class="chart" v-if="loaded" :chartData="chartData" :options="options"/> </div> </template> <script> import Chart from '../components/Chart'; export default { components: { Chart }, data() { histories: [], loaded: false, chartData: { labels: [], datasets: [] }, // これがchart.jsのデータの定義 options: { // ここの書き方はググるとたくさん出てくるので省略 } }, mounted () { this.$http.secured.get('/api/v1/histories') .then(response => { this.histories = response.data this.fillData() // エラー処理は省略 }, methods: { // ここでchartにデータを渡す処理書きます fillData () { this.loaded = false var data = this.arrayData() // ここでデータを加工するメソッドを呼び出しています this.chartData = { labels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], datasets: [{ data: data }] // 配列の形でデータを渡す必要があります } this.loaded = true } } } </script> これで先程apiで取得したデータをchartに渡せるようになります ただ、まだjsonデータを加工してないのでarrayData()メソッドを書いていきます datasets:[{ data: }]に渡す時は配列にしてデータを渡します なので、jsonでは連想配列を配列にまとめた形でデータを受け取っているのでmapを使って配列に加工します ~省略~ methods: { arrayData () { var getData = this.histories; const times = getData.map(item => item.time) return times } ~省略~ } mapは連想配列のキーを指定する事でvalueだけ取り出してループ処理し配列を作ってくれます const times = getData.map(item => item.time) console.log(times) // => [1.5, 2.0, 0.5] あれ? 本来のデータ通りだと 1/25(月) => 1.5 1/27(水)=> 2.0 1/28(木)=> 0.5 にならなければいけないのに、mapで作ったデータでは 1/24(日) => 1.5 1/25(月) => 2.0 1/26(火) => 0.5 になっちゃってます 確かに配列データを作れたのでdatasets:[{ data: }]に渡せるデータにできましたが、1週間は7日なので、これだと日・月・火のデータになってしまっています... 現状の状態をまとめると、 [0, 1.5, 2.0, 0.5, 0, 0, 0]という配列を作らなくてはいけないのに、[1.5, 2.0, 0.5]という配列ができている状態です mapで作った配列はindex[0]から値を埋めていくので、3つの連想配列から取り出したプロパティはindex[0]~[2]に詰めてセットされてしまっています この問題を解消する為にやりたい事の3つ目学習時間を登録していない日は学習時間を0とする処理をarrayData()メソッドに書きます 考え方 (A) jsonデータのday_of_weekを使って比較用の配列を作成する (B) 1週間は7日なので比較対象の配列を用意する (A)と(B)を比較して(A)が持っていない数字(曜日)を求める 持っていない数字(曜日)番目に{ time: 0 }を突っ込んでやる(無理やり感...) 果たしてこれが良いやり方なのかは甚だ疑問だが、やってみよう データの成形 ~省略~ methods: { arrayData () { var getData = this.histories; // getDataの連想配列数が7つ未満の場合 if(getData.length < 7) { // (A)比較用の配列を作成する // 各連想配列created_atの曜日(添字)を抽出して配列を作成 const arrayDayOfWeek = getData.map(item => item.day_of_week) // => [1, 3, 4] // (B)比較対象の配列を用意する // 1週間7日分の曜日(添字)の数値を格納した配列を作成 const arraySeven = [0, 1, 2, 3, 4, 5, 6] // (A)と(B)を比較して差分を求める // filterメソッドを使って、arrayDay0fWeekが持っていない値を抽出 var resultArray = arraySeven.filter(i => arrayDayOfWeek.indexOf(i) == -1) // => [0, 2, 5, 6]がresultArrayに格納 // このケースだとresultArrayには4つデータが格納されているので4回ループされる // spliceメソッドを使ってgetDataの配列に格納されている[0, 2, 5, 6]番目に{time: 0}を追加 for(var i = 0; i < resultArray.length; i++) { getData.splice(resultArray[i], 0, {time: 0}) }; ↓ 結果 ↓ // 配列index番号の0, 2, 5, 6番目に{time: 0}が追加された(プロパティの数が違うのは気にしない) // getData = [ {time: 0} {id: 9, time: 1.5, user_id: 1, created_at: "2021-01-25T12:00:00.000+09:00", day_of_week: 1} {time: 0} {id: 11, time: 2, user_id: 1, created_at: "2021-01-27T09:39:30.000+09:00", day_of_week: 3} {id: 14, time: 0.5, user_id: 1, created_at: "2021-01-28T07:52:24.000+09:00", day_of_week: 4} {time: 0} {time: 0} ] // } const times = getData.map(item => item.time) return times // chartに渡したい配列を作成できた // => [0, 1.5, 0, 0.5, 0, 0, 0] } ~省略~ } for(var i = 0; i < resultArray.length; i++) { getData.splice(resultArray[i], 0, {time: 0}) }; この処理では、 resultArray[0] // => 配列0番目に格納されている値は[0] getData0番目にspliceメソッドによって{time: 0}が追加 resultArray[1] // => 配列1番目に格納されている値は[2] getData2番目にspliceメソッドによって{time: 0}が追加 をループ処理でresultArray.length分、繰り返します これで目的のデータの成形が完了したのでchartに渡せます chart.jsに成形したデータを渡す // 必要箇所だけ <template> <div> <Chart class="chart" v-if="loaded" :chartData="chartData" :options="options"/> // => chartDataにfillDataメソッドで生成されたデータが渡される // => optionsにdata() {}内で定義したoptionsデータを渡せる(この記事では省略してます) </div> </template> <script> export default { data() { histories: [], loaded: false, chartData: { labels: [], datasets: [] }, }, mounted() { this.$http.secured.get('/api/v1/histories') .then(response => { this.histories = response.data //jsonデータを受け取る this.fillData() // fillDataメソッドを呼び出す }) }, methods: { arrayData () { ~省略~ return times // 返り値 => [0, 1.5, 0, 0.5, 0, 0, 0] }, fillData () { this.loaded = false var data = this.arrayData() // [0, 1.5, 0, 0.5, 0, 0, 0]をdataに格納 this.chartData = { labels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], datasets: [ { data: data } ] } // data() {}内で定義したchartData { labels: [], datasets: [] }に生成したデータが格納され, templateタグ内の<Chart ~~/>へマウントされる this.loaded = true } }, } </script> これで記事の最初に貼ったキャプチャの棒グラフが完成しました 最後に もっと効率的な書き方あれば教えて下さい! ちなみに、dayofweek()はmysqlで用意されているメソッドなのでpostgresqlだとエラーします... herokuでデプロイしたらエラーで萎えました... おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QR忘れ物アプリ (1) 環境構築 [Vue,Vuetify,Firebase]

はじめに Vue + Firebaseの記事が少なく手間取ったため、のちにプロジェクトを作るときの記録として残しておきます。 環境 Windows 10 WSL1 (ビルド、エミュレーター用) Node 10 Visual Studio Code Google Chrome UI フレームワーク Vue 2(Vue 3はVuetifyに未対応(2021/4 現在)) Vue CLI v4.5.12 Vuetify Firebase 8.3.1 Firebase CLI Firebase Auth Firebase Auth UI Firebase Realtime Database (RTDB) Firebase Hosting Firebase Functions Firebase Emulator Suite Auth, RTDB, Hosting, Functions Vue環境構築 node 10のインストール aptではnode 10はインストールできないので、nvmを使う1 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash nvm install 10 nvm use 10 Vueのインストール2 npm install vue Vueプロジェクトをqr-lost-and-foundのディレクトリに作る vue create qr-lost-and-found # Preset ->Manually select features # Features -> Babel, Router, Vuex, Linter # Vue.js version -> 2.x # history mode for router -> Yes # Use history mode for router -> Yes # Linter / formatter config -> Prettier # Additional lint features -> Lint on save # Config location for Babel, ESLint -> In dedicated config files # Save this as a preset -> N Gitのinitial commitを行う cd qr-lost-and-found git config --global user.email {your email} git config --global user.name {your username} git add -A git commit -m "initial commit" Vuetifyを追加 vue add vuetify # preset -> Default Vueの設定変更 vue.config.jsにpublicPathを追加して、ローカルのindex.htmlファイルをクリックして閲覧できるようにする。3 vue uiからも設定の変更可能。その場合、vue uiコマンドで開いて、プロジェクト設定>Vue CLI>公開パスを./にする。 module.exports = { transpileDependencies: ["vuetify"], publicPath: './' }; Build 正常にインストールできているかを確認するためにビルドする。 npm run build ./dist/index.htmlをブラウザで開いてVuetifyの画面が表示されることを確認する。 Firebase環境構築 Firebaseプロジェクト作成 二番煎じになるので、詳しくは書きません。Firebaseプロジェクト作り方を参考に作ってください。 Firebase CLIをインストール Firebase CLIを使い、プロジェクトのテンプレートを作ることができます。 npm install firebase-tools Firebaseのアカウントにログインし、プロジェクトを初期化する。 firebaseはFirebase CLIのコマンドです。 通常の設定からの変更点としてはPublic Directoryをdistに設定にしていることが挙げられます。これは、Vueのビルド先がdist であるためです。 firebase login firebase init # Features: Database, Functions, Hosting, Emulator # Project: Use an existing project # Select project: Firebaseで自分の作ったプロジェクトを選択 # Database security rules: Enter # Language: Javascript # ESLint: No # Install dependencies now: Y # Public directory: dist # Configure as SPA: N # Automatic build and deploy: N # Overwrite public/index.html: N # Emulators setup: Authentication, Functions, Database, Hosting # Port for auth: Enter # Port for functions: Enter # Port for database: Enter # Port for hosting: Enter # Enable Emulator UI: Y # Port for Emulator UI: Enter # Download the emulators now: y Firebase SDKをインポート public/index.html内に登録します。 ?useEmulator=trueになっており、Emulatorを使う設定になっています。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- update the version number as needed --> <script src="/__/firebase/8.3.1/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script src="/__/firebase/8.3.1/firebase-auth.js"></script> <script src="/__/firebase/8.3.1/firebase-database.js"></script> <script src="/__/firebase/8.3.1/firebase-functions.js"></script> <!-- initialize the SDK after all desired features are loaded, set useEmulator to false to avoid connecting the SDK to running emulators. --> <script src="/__/firebase/init.js?useEmulator=true"></script> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> Build 再びテストのためビルドします。先ほどのビルドより時間がかかります。 npm run build ./dist/index.htmlをブラウザで開いて先ほどと同じくVuetifyの画面が表示されることを確認します。 次回 Firebase Auth + Firebase Auth UIを用いてログインページを作ります。 コメント Firebase + Vueの組み合わせの設定方法で参考にしたリンク: https://qiita.com/fukuchan-senpai/items/4ea6cd97c196689d152e QRコードは(株)デンソーウェーブの登録商標です https://askubuntu.com/questions/1273438/how-to-install-node-js-version-10-on-ubuntu-18-04 ↩ https://vuejs.org/v2/guide/installation.html ↩ https://github.com/vuejs/vue-cli/issues/5162 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js ダイアログボックスを表示する

はじめに  本記事は、GASならびにVue.jsを使ってプログラムの開発を行った事がある方を対象としています。  また、GASでのWebアプリの開発について書いています。 概要  タイトル通り、Vue.jsを使用してダイアログボックスを表示する方法を説明します。 使用するファイル Main.gs Index.html Vuejs.html modalStyle.html modalScript.html Main.gs function doGet(e) { const template = HtmlService.createTemplateFromFile('Index'); const title = '氏名一覧'; const html = template.evaluate().setTitle(title).setSandboxMode(HtmlService.SandboxMode.IFRAME); return html; } Index.html <!DOCTYPE html> <html> <head> <!-- Bootstrap --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <!-- Vue.js --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script> <?!= HtmlService.createHtmlOutputFromFile('modalStyle').getContent(); ?> <?!= HtmlService.createHtmlOutputFromFile('modalScript').getContent(); ?> <base target="_top"> </head> <body> <div id="app"> <p></P> <div class="container"> <table id="nameList" class="table table-hover"> <thead> <tr class="alert-primary"> <th class="head01">ID</th> <th class="head02">氏名</th> <th class="head03">メールアドレス</th> <th class="head04">住所</th> </tr> </thead> <tbody> <tr v-for="record in records" v-bind:key="record.id" @click="doShowModal(record.id)"> <td class="col01">{{ record.id }}</td> <td class="col02">{{ record.name }}</td> <td class="col03">{{ record.eMail }}</td> <td class="col04">{{ record.address }}</td> </tr> </tbody> </table> </div> <div class="container"> {{ messageText }} </div> <div class="container"> <button type="button" class="btn btn-primary btn-lg btn-block" id="show-modal" @click="doShowModal(0)">追 加</button> </div> <modal v-if="showModal" v-on:close-modal="doHideModal" v-on:add-data="doAddData" v-on:del-data="doDelData" v-bind:message_text="messageText" v-bind:button_name="buttonName" v-bind:cond="cond"> <h3 slot="header">custom header {{ headerText }}</h3> </modal> </div> <?!= HtmlService.createHtmlOutputFromFile('Vuejs').getContent(); ?> </body> </html> Vuejs.html <script> Vue.component('modal', { template: '#modal-template', props: { message_text: String, button_name: String, cond: { id: Number, name: String, eMail: String, address: String } }, methods: { doAddData: function() { this.$emit('add-data', this.cond); }, doDelData: function() { this.$emit('del-data', this.cond.id); } } }); var app = new Vue({ el: '#app', data: { showModal: false, // ダイアログボックスの表示を制御 messageText: 'テストテスト', headerText: 'あいうえお', buttonName: '', records: [ // 一覧表に表示されるデータ {id: 1, name: '鈴木太郎', eMail: 't-suzuki@xxxxx.xx.jp', address: '東京都新宿区'}, {id: 2, name: '斉藤花子', eMail: 'h-saitoh@xxxxx.xx.jp', address: '埼玉県川越市'}, {id: 3, name: '田中ひろみ', eMail: 'h-tanaka@xxxxx.xx.jp', address: '東京都清瀬市'} ], cond: { id: 0, name: '', eMail: '', address: '' } }, mounted: function() { console.log('*** start'); // 画面表示時に一覧表へデータを追加する処理 this.records.push({id: 4, name: '山田一郎', eMail: 'i-yamada@xxxxx.xx.jp', address: '埼玉県所沢市'}); this.records.push({id: 5, name: '佐藤順子', eMail: 'j-satoh@xxxxx.xx.jp', address: '福井県大野市'}); console.log('*** end'); }, methods: { doShowModal: function(id) { // ダイアログボックスを表示する処理 if(id === 0) { // 追加(新規)の場合 this.buttonName = '追 加'; this.cond.id = 0; this.cond.name = ''; this.cond.eMail = ''; this.cond.address = ''; } // 更新(変更)の場合 else { this.buttonName = '更 新'; this.cond.id = id; for(record of this.records) { if(record.id === id) { this.cond.name = record.name; this.cond.eMail = record.eMail; this.cond.address = record.address; } } } this.showModal = true; }, doHideModal: function() { // ダイアログボックスを非表示にする処理 this.showModal = false; }, doAddData: function(cond) { // 一覧表のデータを更新する処理 this.showModal = false; if(cond.id === 0) { // 追加処理(新規) let maxId = 0; for(record of this.records) { if(record.id > maxId) { maxId = record.id; } } const newCond = {id: 0, name: '', eMail: '', address: ''}; newCond.id = maxId + 1; newCond.name = cond.name; newCond.eMail = cond.eMail; newCond.address = cond.address; this.records.push(newCond); } else { // 更新処理(変更) for(record of this.records) { if(record.id === cond.id) { record.name = cond.name; record.eMail = cond.eMail; record.address = cond.address; } } } }, doDelData: function(id) { // 一覧表のデータを削除する処理 this.showModal = false; for(let i = 0; i < this.records.length; i++) { if(this.records[i].id === id) { this.records.splice(i, 1); break; } } } } }); </script> modalStyle.html <style> .modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: table; transition: opacity 0.3s ease; } .modal-wrapper { display: table-cell; vertical-align: middle; } .modal-container { width: 550px; margin: 0px auto; padding: 20px 30px; background-color: #fff; border-radius: 2px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33); transition: all 0.3s ease; font-family: Helvetica, Arial, sans-serif; } .modal-header h3 { margin-top: 0; color: #42b983; } .modal-body { margin: 0 0 20px 0; padding: 10px; } .modal-footer { display: block; } .modal-default-button { float: right; } /* * The following styles are auto-applied to elements with * transition="modal" when their visibility is toggled * by Vue.js. * * You can easily play with the modal transition by editing * these styles. */ .modal-enter { opacity: 0; } .modal-leave-active { opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { -webkit-transform: scale(1.1); transform: scale(1.1); } </style> modalScript.html <script type="text/x-template" id="modal-template"> <transition name="modal"> <div class="modal-mask"> <div class="modal-wrapper"> <div class="modal-container"> <div class="modal-header"> <slot name="header"> default header </slot> </div> <div class="modal-body"> <slot name="body"> default body {{ message_text }} </slot> <form> <div class="form-group row" id="data-id-group" v-if="cond.id !== 0"> <label for="data-id" class="col-sm-4 col-form-label">ID:</label> <div class="col-sm-8"> <input type="text" class="form-control" id="data-id" v-model="cond.id" readonly> </div> </div> <div class="form-group row"> <label for="name" class="col-sm-4 col-form-label">名前:</label> <div class="col-sm-8"> <input type="text" class="form-control" id="name" v-model="cond.name"> </div> </div> <div class="form-group row"> <label for="eMail" class="col-sm-4 col-form-label">メールアドレス:</label> <div class="col-sm-8"> <input type="email" class="form-control" id="eMail" v-model="cond.eMail"> </div> </div> <div class="form-group row"> <label for="address" class="col-sm-2 col-form-label">住所:</label> <div class="col-sm-10"> <input type="text" class="form-control" id="address" v-model="cond.address"> </div> </div> </form> </div> <div class="modal-footer"> <slot name="footer"> <div class="row"> <div class="col-sm"> <button type="button" class="btn btn-primary btn-block" @click="doAddData">{{ button_name }}</button> </div> <div class="col-sm" v-if="cond.id !== 0"> <button type="button" class="btn btn-danger btn-block" @click="doDelData">削 除</button> </div> <div class="col-sm"> <button type="button" class="btn btn-secondary btn-block" @click="$emit('close-modal')">閉じる</button> </div> </div> </slot> </div> </div> </div> </div> </transition> </script> 説明  ダイアログボックス自体(HTML部分、実際は<script>です。)は、modalScript.html で、このファイルのスタイルシートが modalStyle.html なります。  メインのファイル(Index.html)の Index.html <?!= HtmlService.createHtmlOutputFromFile('modalStyle').getContent(); ?> <?!= HtmlService.createHtmlOutputFromFile('modalScript').getContent(); ?> (上記)で読み込まれて、(下記<modal>タグ) Index.html <modal v-if="showModal" v-on:close-modal="doHideModal" v-on:add-data="doAddData" v-on:del-data="doDelData" v-bind:message_text="messageText" v-bind:button_name="buttonName" v-bind:cond="cond"> <h3 slot="header">custom header {{ headerText }}</h3> </modal> で参照されます。  ダイアログボックスの表示は、v-if="showModal"で制御されています。(変数showModalがtrueの時に表示)  ダイアログボックスは、子コンポーネント(Vuejs.html のVue.component('modal', {…});)として動作します。  親コンポーネント(Vuejs.html のvar app = new Vue({…});)と子コンポーネントのデータの関連付けは、Index.html のv-bind:message_text="messageText"(他も同様)で行われています。  slot="header"の指定により、直接ダイアログボックスへ文字列を連携させる事(<slot name="header">タグの要素に置き換えられます)も可能です。  子コンポーネントから親コンポーネントへのメソッド処理の関連付けは、Index.html のv-on:add-data="doAddData"(他も同様)で行われていて、以下の様に連携されます。 modalScript.html の@click="doAddDataにより、子コンポーネントのdoAddDataメソッドが実行されます。 続けて、this.$emit('add-data', this.cond)(下記参照)により、前述で関連付けられた親コンポーネントのdoAddDataメソッドが実行されます。 子コンポーネント(メソッド)→親コンポーネント(メソッド)へ引数でデータを渡す事も可能です。 Vuejs.html doAddData: function() { this.$emit('add-data', this.cond); }, 結果  下記の様に画面が表示されます。 一覧画面 〔追 加〕ボタン(@click="doShowModal(0)")で、データ追加用のダイアログボックスを表示します。 一覧の行を選択する事により、データ更新用のダイアログボックスを表示します。 Index.html の<tr>タグに@click="doShowModal(record.id)"を記述する事で実現しています。 追加画面 更新画面  各項目の値は、cond(子コンポーネントのprops)のプロパティが、v-modelディレクティブでバインディングされます。  更新画面を表示する場合、一覧で選択されたデータを親コンポーネントのcondのプロパティにセットしています。  前述のv-bindディレクティブで親コンポーネントのcondと子コンポーネントのcondバインディングされているので、選択行のデータがダイアログボックスに表示されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む