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

Vue.js を使うときに気を付けること。

Vue.js を使うときに気を付けること。

とりあえず、今の時点で痛い目にあったもの。

文字列の表示は .nodeValue を使っているので改行が無くなる。

<div>{{text}}</div>
のような場合に、this.text に改行が入っていても、その改行は無視されて一行で表示されてしまう。

.wrap { white-space:pre-wrap; word-wrap:break-word; }
のようなものを用意して
<div class="wrap">{{text}}</div>
とする。

VueRouter では同じハッシュ値だと一切何のアクションも発生しない。

各ページへのリンクを上部において、本体を下部に表示している場合に、
最新の情報にしたく更新のつもりで再度同じ <router-link> をクリックしても Vue は一切反応してくれない。
※ createdフックなどが呼ばれない、というか何も処理が動かない。

ただ、パラメータが違う場合は、beforeRouteUpdate フックが呼ばれるようなので、
面倒だがクリック毎に変化するダミーを用意してこのフックで捕捉して処理する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで連想配列を使おう!

?Vue.jsで連想配列を使おう!

はじめまして、s4na?と申します。
今回はじめてQiitaの記事を書きます。

今回書く内容は、少し前にVue.jsで実装していたら躓いた連想配列の利用方法です。
一時期は「計算量が増えるけど、連想配列を使わないで実装してしまおうかな・・・?」と思ったこともあるくらい大きく躓きました。

少しでも私と同じところで躓く人の力になればと思い、今回筆をとりました。?

RailsのViewからVue.jsに連想配列を渡す方法

まずはRailsのViewからVue.jsに連想配列を渡す方法について書いていきます。

この方法は、Rails側からVue.jsに初期値を渡す際に使います。
もちろん、APIを作ってそちらで渡してもいいのですが、それだとAPI分の通信量が増えてしまうので、私はViewから渡しています。

  • 補足:RailsのView = MVCのView

ControllerでJSON化しておく

  • 補足:Hashのまま渡すと、Vue.jsがParseできないです。フロントで無理にパースしてもいいのかもしれませんが、サーバーサイドでできることはサーバーサイドでしたほうが良さそうです
@foo = JSON(foo)

View

  • 補足:
    • RailsのTemplate Engineはslimで書いています
    • @foo, @baaみたいに値を複数渡す場合もあるので、#js-el-data-fooのように名前を区切ると便利です。
#js-el # ?Vue.jsがマウントするDOM
  #js-el-data-foo
    = @foo # ?渡したい値 その1
  #js-el-data-baa
    = @baa # ?渡したい値 その2

Vueインスタンス

Vue.js側では、ViewのDOMのinnerTextを取得し、JSON化してdataに入れ込みます。

<script>
export default {
  data: () => {
    return {
      reservations: {}
    }
  },
  created: function() {
    //                 ?JSONが連想配列になる                            ?JSONで受け取る
    this.memos = JSON.parse(document.querySelector('#js-el-data-foo').innerText);
  }
}
</script>

Vueインスタンスで連想配列をループ表示する方法

連想配列の表示は連想配列[key]とする必要があり、そのためにはkeyの集合であるkeysが必要になります。

  • 補足:テンプレートエンジンにはPugを使用しています
<template lang="pug">
  .foo(v-for="key in keys")
    | {{ foo[key] }}
</template>
<script>

export default {
  data: () => {
    return {
      reservations: {},
      keys: []
    }
  }
}
</script>

Vueインスタンス内で連想配列に情報を追加する方法

追加する際は$setメソッドを使いましょう。

これは書こうとした時、めっちゃハマりました。?
この記事を書こうと思った原点でもあります。

他の言語やフレームワークを利用していたりすると、「val['key']で値を代入できるのでは?」と考えてしまうこともあるかと思います。(私はそうでした。)

Vue.jsでは、JavaScriptの仕様で、連想配列に要素を追加した時に検知できません。
なので、変更検出が可能なメソッドを利用する必要があります。(公式ドキュメントを読めば気づけるけど、読んでなかった?)

Vue.js#オブジェクトの変更検出の注意

export default {
  data: () => {
    return {
      foo: {}
    }
  },
  methods: {
    addFoo: function(key, hoge) {
      this.$set(this.foo, `key`, hoge);
    }
  }

連想配列から要素を削除する方法

削除する際は$deleteメソッドを使いましょう。
JavaScriptを書いていると、deleteメソッドで削除してしまったりすることがあると思います。(私はそうでした)
それだと$setと同様に、Vue.jsが要素数の変化を検知できないので、$deleteを使う必要があります。

  methods: {
    deleteFoo: function(key) {
      this.$delete(this.foo, key);
    }
  }

Vueインスタンスで連想配列をAPIで取得する方法

  methods: {
    token () {
      const meta = document.querySelector('meta[name="csrf-token"]')
      return meta ? meta.getAttribute('content') : ''
    },
    getFoo: function() {
      fetch(`/api/hogehoge`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json; charset=utf-8',
          'X-Requested-With': 'XMLHttpRequest',
          'X-CSRF-Token': this.token()
        },
        credentials: 'same-origin',
        redirect: 'manual',
      })
        .then(response => {
          return response.json();
        })
        .then(json => {
          this.$set(this.foo, `key`, json); // ?受け取った値を連想配列`foo`に入れる
        })
        .catch(error => {
          console.warn('Failed to parsing', error);
        })
    },

まとめ

  • Vue.jsで連想配列を扱うのは、癖がわかれば結構簡単!
  • 時間がなくて厳しくても、触ったことのない技術を触るのであれば、何よりもまずは公式ドキュメントを全部読もう!!!
  • Vue.jsならこちらのガイドを読もう!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firestoreのデータを可視化・監視しアラートする、かんたんなWEBアプリ(Nuxt.js,GCP Firestore)

はじめに

Nuxt.jsの勉強がてら、Firestoreに格納されているデータをブラウザに表示させつつ、リアルタイムに変更があった際に、その更新が反映される簡易WEBアプリを作成しました。

アクションとしては、3つの仕組みを用意しました。
①トグルボタンでのFirestoreデータ更新(True/False)
②削除ボタンでのFirestoreデータ削除
③更新(追加、変更、削除)があった際、ブラウザ上アラートが上がる

作成したNuxt.jsプロジェクトは、Firebase Hostingにデプロイして動作確認し、
手動デプロイができたので、GCP CloudBuildを使って、CIの仕組みも少し試験しました。

見た目はこんなものです。(CSSについては特に今回は言及しません。)
このカラム名で、データがドキュメント毎に保存(図にあるのは二つ)されている形になります。

UI

技術要素

  • Firestore
  • vue.js / Nuxt.js
  • Firebase Hosting
  • GCP CloudBuild

説明

  • 1. create-nuxt-appでNuxt.jsプロジェクト作成
  • 2. Firestoreへ接続するコード準備
  • 3. Storeの設定
  • 4. 表示部分のページ作成
  • 5. ローカルで動作確認
  • 6. Firebase Hostingへデプロイ
  • 7. CloudBuildでCI環境構築

1. create-nuxt-appでNuxt.jsプロジェクト作成

まず、SEOなどは気にせず、今回はSPA(Single Page Application)でお手軽に作成したいので、SPAを選択しました。
参考サイトは、Nuxt.jsを使うときに、SPA・SSR・静的化のどれがいいか迷ったらです。

基本的にはデフォルトで、SPAを選択する箇所のみ変更。

npx create-nuxt-app nuxt-view-firestore-status

必要に応じて、npmパッケージはインストールしておきます。後々利用します。

npm install --save firebase
npm install --save vuexfire

2. Firestoreへ接続するコード準備

必須ではないですが、dotenvを使って環境変数にプロジェクトIDと後々使うFirestoreのコレクション名を格納しようと思います。nuxt.config.jsのmodulesに、にdotenvを設定。
そして、.envファイルは、作成されたフォルダ直下に新規作成。

nuxt.config.js
  modules: [
    '@nuxtjs/dotenv'
  ],
.env
FIREBASE_PROJECT_ID = '<your project id>'
FIRESTORE_COLLECTION_NAME = '<your firestore collection name>'

次に、Firestoreの初期コンフィグを定義する、Vueプラグインを作成します。pluginsフォルダ配下に、firebase.jsを作成します。

plugins/firebase.js
import firebase from 'firebase'

const config = {
    projectId: process.env.FIREBASE_PROJECT_ID
}

if (!firebase.apps.length){
    firebase.initializeApp(config)
}

export default firebase

3. Storeの設定

今回は、ストアの利用方法の「クラシックモード」と「モジュールモード」のうち、モジュールモードで作成します。構成は以下です。

Nuxt.jsのストアをモジュールモードで使用するときのTips

Nuxt.js + Firebase で vuexfire を使って Cloud Firestore と連携する(データバインド編)

store
├── index.js
└── view_status.js

index.jsでは、vuexfire の vuexfireMutations を定義するのみ。

store/index.js
import { vuexfireMutations } from 'vuexfire'

export const mutations ={
    ...vuexfireMutations
}

次に、view_status.jsに、Firestoreの読み込みと、ステート、アクション定義。

アクション名 説明
init Firestoreから取得するデータをバインド
remove 削除ボタンが押されたときに呼び出す。ドキュメント名を引数にFirestoreのドキュメント削除
toggle トグルボタンが押されたときに呼び出す。対象ドキュメントのkeyがdoneの値をTrue/Falseへ変更させる。(Todosサンプルアプリの流用なのであまり気にしないでください。)
popup Firestoreの対象Collection内のドキュメントが追加、削除、更新が発生した際に、アラートを上げる機能
store/view_status.js
import firebase from '~/plugins/firebase'
import { firestoreAction } from 'vuexfire'

const db = firebase.firestore()
const statusRef = db.collection(process.env.FIRESTORE_COLLECTION_NAME)

export const state = () => ({
  statuses: []
})

export const actions = {
  init: firestoreAction(({ bindFirestoreRef }) => {
    bindFirestoreRef('statuses', statusRef)
  }),
  remove: firestoreAction((context, id) => {
    statusRef.doc(id).delete()
  }),
  toggle: firestoreAction((context, status) => {
    statusRef.doc(status.id).update({
      done: !status.done
    })
  }),
  popup: firestoreAction((context) => {
    statusRef.onSnapshot(function(snapshot){
      snapshot.docChanges().forEach(function(change){
        if (change.type === "added"){
          alert("ドキュメントが追加されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "modified"){
          alert("ドキュメントが変更されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "removed"){
          alert("ドキュメントが削除されました。\r\nIDは、" + change.doc.id + "です。")
        }
      })
    })
  })
}

注意

FirestoreのCollectionのルール設定は、今回、開けてテストを実施しましたが、本番利用時はセキュリティ設計の考慮が必要です。FirebaseコンソールのDatabase -> 「ルール」でGUIで設定可能です。

Firebaseのサイトの安全でないルールを修正するを参照。

    match /<your collection name>/{document=**} {
      allow read, write: if true;
    }

4. 表示部分のページ作成

最後に、表示部分のページ作成するため、pagesフォルダに、閲覧用のVueファイルを作成します。status.vueとしました。

script      説明
created ページが開いた時の処理。Vue インスタンスが生成された直後に呼び出されます。今回は、上記で定義した、initとpopupアクションを呼び出しています。
methods vueテンプレートから呼び出すメソッドで、remove(id)とtoggle(status)を定義しました。上記で定義したremoveとtoggleアクションを引数設定しつつ呼び出します。
computed 値が変わると再評価される仕組みで、今回はstoreの値を見て変化があれば更新されるようにしています。
pages/status.vue(抜粋)
    created: function() {
      this.$store.dispatch('view_status/init')
      this.$store.dispatch('view_status/popup')
    },
    methods: {
      remove(id) {
        this.$store.dispatch('view_status/remove', id)
      },
      toggle(status) {
        this.$store.dispatch('view_status/toggle', status)
      },
    },
    computed: {
      view_status() {
        return this.$store.getters['view_status/orderedStatus']
      }
    },

テーブルで取得したFirestoreのデータを表示しています。

pages/status.vue(抜粋)
<template>
  <div>
    <table>
      <tr>
        <th>-</th>
        <th>done</th>
        <th>session_id</th>
        <th>url</th>
        <th>timestamp</th>
        <th>remove</th>
      </tr>
      <tr v-for="status in view_status" :key="status.id">
        <td>
          <input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">
        </td>

        <td>{{status.done}}</td>
        <td> {{status.session_id}}</td>
        <td> <a v-bind:href="status.url">{{status.url}}</a></td>
        <td> {{status.timestamp.toDate() | dateFilter}}</td>
        <td>
          <button v-on:click="remove(status.id)">remove</button>
        </td>

      </tr>
    </table>
  </div>
</template>

簡単にコメントすると、

pages/status.vue(抜粋)
<tr v-for="status in view_status" :key="status.id">

view_statusで取得した値(=ドキュメント一覧)をドキュメント毎に、v-forで繰り返し処理しています。

pages/status.vue(抜粋)
<input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">

UI上、一番左のカラムにチェックボックスを配置し、チェックON/OFFは、ドキュメント(status)のdoneの値をバインドしています。変更があった場合は、toggleメソッドを呼び出します。

pages/status.vue(抜粋)
<a v-bind:href="status.url">{{status.url}}</a>

ドキュメント(status)のurlの値にURLの文字列が格納されているのですが、最初はmustacheタグ{{status.url}}で表示させようとしましたが、それだとリンクがつかないので、aタグでリンクさせたい場合は、このようにバインドさせて表示させる必要があるようです。

pages/status.vue(template内)
<td> {{status.timestamp.toDate() | dateFilter}}</td>
pages/status.vue(script内)
  import moment from 'moment'
//(省略)
    filters: {
      dateFilter: function(date) {
        return moment(date).format('YYYY/MM/DD HH:mm:ss')
      }
    }

タイムスタンプの値を、指定の形式で表示させようとする場合は、フィルタ機能を使って実現させると良いようです。momentライブラリを使いました。

pages/status.vue(抜粋)
  <td>
     <button v-on:click="remove(status.id)">remove</button>
  </td>

timestampを降順にする

computedのところでは、storeのgettersを呼び出していますが、timestampの順序を変える仕組みを導入しました。日付でソートをかけるために、webpackプラグイン(lodash)をinstallしています。

webpack プラグインを追加するには?と、細かすぎるけど伝わって欲しいlodash.jsの話を参照。

nuxt.config.js
const webpack = require('webpack')

// (省略)

  build: {
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    },
    plugins: [
      new webpack.ProvidePlugin({
        '_': 'lodash'
      })
    ]
  }

そのうえで、gettersをview_status定義し、降順でtimestampが並ぶように処理をします。

store/view_status.js
export const getters = {
  orderedStatus: state => {
    return _.orderBy(state.statuses, 'timestamp', 'desc')
  }
}

5. ローカルで動作確認

ローカルでの検証は npm run devで動作させ、localhost:3000/status上で正常に動くことを確認しました。

再掲になりますが、以下のようなUIで、動作が問題なく動きました。
+ 一番左のチェックボックスをクリックすると、Doneの値がtrueへ更新
+ 再度クリックするとDoneの値がfalseに更新
+ 一番右のremoveボタンをクリックすると対象データが削除
+ 上記アクションを実施した際に、アラートが画面上上がること

UI

6. Firebase Hostingへのデプロイ

まず、Nuxtプロジェクトをビルドします。npm run build
Firebaseプロジェクトを firebase init コマンドで初期化します。

注意としては、hostingを選択することと、build後に生成されるフォルダはdist(not public)なので、distを指定することです。

上記を実施後、firebase deployコマンドでデプロイします。
問題がなければ、https://<your project>.firebaseapp.com/status/ などにアクセスして確認できます。

7. CloudBuildでCI環境構築

CloudBuildでCI環境構築構築し、githubにコードをデプロイすると、CloudBuildがトリガーされ、FirebaseHostingへ自動デプロイの仕組みも作りました。CloudBuild側の設定は、別途サイトを参考ください。
準備としては、Dockerfileを作成し、cloudbuild.yamlで設定を入れることで動作しました。
ちなみに、.gitignoreファイルに.envが記載されているので、CloudBuildで連携する場合は、.envもリポジトリに上げるようにする必要があります。

Dockerfile
# use latest Node 10
FROM node:10
# install Firebase CLI
RUN npm install -g firebase-tools

ENTRYPOINT ["/usr/local/bin/firebase"]
steps:
# Install
- name: 'gcr.io/cloud-builders/npm'
  args: ['install']
# Build
- name: 'gcr.io/cloud-builders/npm'
  args: ['run', 'build']
# Docker Image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/firebase', '.']
# Deploy
- name: 'gcr.io/$PROJECT_ID/firebase'
  args: ['deploy', '--project', '$PROJECT_ID']
# push built images to Container Registry
images: ['gcr.io/$PROJECT_ID/firebase']

おわりに

Vue.js / Nuxt.js のおかげで比較的簡単に、ブラウザ上でFirestoreデータを確認し、更新があったらアラートが上がる機能を作成できました。

もう少し応用的なものも作ってみたいと思うのと、環境面では、FirebaseHostingではなく、Google App Engineにもデプロイができるようなので、そちらも試してみたいと思います。

コードはgithubに公開しました。

参考コンテンツ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firestoreのデータを可視化・監視する、かんたんなWEBアプリ(Nuxt.js,GCP Firestore)

はじめに

Nuxt.jsの勉強がてら、Firestoreに格納されているデータをブラウザに表示させつつ、リアルタイムに変更があった際に、その更新が反映される簡易WEBアプリを作成しました。

アクションとしては、3つの仕組みを用意しました。
①トグルボタンでのFirestoreデータ更新(True/False)
②削除ボタンでのFirestoreデータ削除
③更新(追加、変更、削除)があった際、ブラウザ上アラートが上がる

作成したNuxt.jsプロジェクトは、Firebase Hostingにデプロイして動作確認し、
手動デプロイができたので、GCP CloudBuildを使って、CIの仕組みも少し試験しました。

見た目はこんなものです。(CSSについては特に今回は言及しません。)
このカラム名で、データがドキュメント毎に保存(図にあるのは二つ)されている形になります。

UI

技術要素

  • Firestore
  • vue.js / Nuxt.js
  • Firebase Hosting
  • GCP CloudBuild

説明

  • 1. create-nuxt-appでNuxt.jsプロジェクト作成
  • 2. Firestoreへ接続するコード準備
  • 3. Storeの設定
  • 4. 表示部分のページ作成
  • 5. ローカルで動作確認
  • 6. Firebase Hostingへデプロイ
  • 7. CloudBuildでCI環境構築

1. create-nuxt-appでNuxt.jsプロジェクト作成

まず、SEOなどは気にせず、今回はSPA(Single Page Application)でお手軽に作成したいので、SPAを選択しました。
参考サイトは、Nuxt.jsを使うときに、SPA・SSR・静的化のどれがいいか迷ったらです。

基本的にはデフォルトで、SPAを選択する箇所のみ変更。

npx create-nuxt-app nuxt-view-firestore-status

必要に応じて、npmパッケージはインストールしておきます。後々利用します。

npm install --save firebase
npm install --save vuexfire

2. Firestoreへ接続するコード準備

必須ではないですが、dotenvを使って環境変数にプロジェクトIDと後々使うFirestoreのコレクション名を格納しようと思います。nuxt.config.jsのmodulesに、にdotenvを設定。
そして、.envファイルは、作成されたフォルダ直下に新規作成。

nuxt.config.js
  modules: [
    '@nuxtjs/dotenv'
  ],
.env
FIREBASE_PROJECT_ID = '<your project id>'
FIRESTORE_COLLECTION_NAME = '<your firestore collection name>'

次に、Firestoreの初期コンフィグを定義する、Vueプラグインを作成します。pluginsフォルダ配下に、firebase.jsを作成します。

plugins/firebase.js
import firebase from 'firebase'

const config = {
    projectId: process.env.FIREBASE_PROJECT_ID
}

if (!firebase.apps.length){
    firebase.initializeApp(config)
}

export default firebase

3. Storeの設定

今回は、ストアの利用方法の「クラシックモード」と「モジュールモード」のうち、モジュールモードで作成します。構成は以下です。

Nuxt.jsのストアをモジュールモードで使用するときのTips

Nuxt.js + Firebase で vuexfire を使って Cloud Firestore と連携する(データバインド編)

store
├── index.js
└── view_status.js

index.jsでは、vuexfire の vuexfireMutations を定義するのみ。

store/index.js
import { vuexfireMutations } from 'vuexfire'

export const mutations ={
    ...vuexfireMutations
}

次に、view_status.jsに、Firestoreの読み込みと、ステート、アクション定義。

アクション名 説明
init Firestoreから取得するデータをバインド
remove 削除ボタンが押されたときに呼び出す。ドキュメント名を引数にFirestoreのドキュメント削除
toggle トグルボタンが押されたときに呼び出す。対象ドキュメントのkeyがdoneの値をTrue/Falseへ変更させる。(Todosサンプルアプリの流用なのであまり気にしないでください。)
popup Firestoreの対象Collection内のドキュメントが追加、削除、更新が発生した際に、アラートを上げる機能
store/view_status.js
import firebase from '~/plugins/firebase'
import { firestoreAction } from 'vuexfire'

const db = firebase.firestore()
const statusRef = db.collection(process.env.FIRESTORE_COLLECTION_NAME)

export const state = () => ({
  statuses: []
})

export const actions = {
  init: firestoreAction(({ bindFirestoreRef }) => {
    bindFirestoreRef('statuses', statusRef)
  }),
  remove: firestoreAction((context, id) => {
    statusRef.doc(id).delete()
  }),
  toggle: firestoreAction((context, status) => {
    statusRef.doc(status.id).update({
      done: !status.done
    })
  }),
  popup: firestoreAction((context) => {
    statusRef.onSnapshot(function(snapshot){
      snapshot.docChanges().forEach(function(change){
        if (change.type === "added"){
          alert("ドキュメントが追加されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "modified"){
          alert("ドキュメントが変更されました。\r\nIDは、" + change.doc.id + "です。")
        }
        if (change.type === "removed"){
          alert("ドキュメントが削除されました。\r\nIDは、" + change.doc.id + "です。")
        }
      })
    })
  })
}

注意

FirestoreのCollectionのルール設定は、今回、開けてテストを実施しましたが、本番利用時はセキュリティ設計の考慮が必要です。FirebaseコンソールのDatabase -> 「ルール」でGUIで設定可能です。

Firebaseのサイトの安全でないルールを修正するを参照。

    match /<your collection name>/{document=**} {
      allow read, write: if true;
    }

4. 表示部分のページ作成

最後に、表示部分のページ作成するため、pagesフォルダに、閲覧用のVueファイルを作成します。status.vueとしました。

script      説明
created ページが開いた時の処理。Vue インスタンスが生成された直後に呼び出されます。今回は、上記で定義した、initとpopupアクションを呼び出しています。
methods vueテンプレートから呼び出すメソッドで、remove(id)とtoggle(status)を定義しました。上記で定義したremoveとtoggleアクションを引数設定しつつ呼び出します。
computed 値が変わると再評価される仕組みで、今回はstoreの値を見て変化があれば更新されるようにしています。
pages/status.vue(抜粋)
    created: function() {
      this.$store.dispatch('view_status/init')
      this.$store.dispatch('view_status/popup')
    },
    methods: {
      remove(id) {
        this.$store.dispatch('view_status/remove', id)
      },
      toggle(status) {
        this.$store.dispatch('view_status/toggle', status)
      },
    },
    computed: {
      view_status() {
        return this.$store.getters['view_status/orderedStatus']
      }
    },

テーブルで取得したFirestoreのデータを表示しています。

pages/status.vue(抜粋)
<template>
  <div>
    <table>
      <tr>
        <th>-</th>
        <th>done</th>
        <th>session_id</th>
        <th>url</th>
        <th>timestamp</th>
        <th>remove</th>
      </tr>
      <tr v-for="status in view_status" :key="status.id">
        <td>
          <input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">
        </td>

        <td>{{status.done}}</td>
        <td> {{status.session_id}}</td>
        <td> <a v-bind:href="status.url">{{status.url}}</a></td>
        <td> {{status.timestamp.toDate() | dateFilter}}</td>
        <td>
          <button v-on:click="remove(status.id)">remove</button>
        </td>

      </tr>
    </table>
  </div>
</template>

簡単にコメントすると、

pages/status.vue(抜粋)
<tr v-for="status in view_status" :key="status.id">

view_statusで取得した値(=ドキュメント一覧)をドキュメント毎に、v-forで繰り返し処理しています。

pages/status.vue(抜粋)
<input type="checkbox" v-bind:checked="status.done" @change="toggle(status)">

UI上、一番左のカラムにチェックボックスを配置し、チェックON/OFFは、ドキュメント(status)のdoneの値をバインドしています。変更があった場合は、toggleメソッドを呼び出します。

pages/status.vue(抜粋)
<a v-bind:href="status.url">{{status.url}}</a>

ドキュメント(status)のurlの値にURLの文字列が格納されているのですが、最初はmustacheタグ{{status.url}}で表示させようとしましたが、それだとリンクがつかないので、aタグでリンクさせたい場合は、このようにバインドさせて表示させる必要があるようです。

pages/status.vue(template内)
<td> {{status.timestamp.toDate() | dateFilter}}</td>
pages/status.vue(script内)
  import moment from 'moment'
//(省略)
    filters: {
      dateFilter: function(date) {
        return moment(date).format('YYYY/MM/DD HH:mm:ss')
      }
    }

タイムスタンプの値を、指定の形式で表示させようとする場合は、フィルタ機能を使って実現させると良いようです。momentライブラリを使いました。

pages/status.vue(抜粋)
  <td>
     <button v-on:click="remove(status.id)">remove</button>
  </td>

timestampを降順にする

computedのところでは、storeのgettersを呼び出していますが、timestampの順序を変える仕組みを導入しました。日付でソートをかけるために、webpackプラグイン(lodash)をinstallしています。

webpack プラグインを追加するには?と、細かすぎるけど伝わって欲しいlodash.jsの話を参照。

nuxt.config.js
const webpack = require('webpack')

// (省略)

  build: {
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    },
    plugins: [
      new webpack.ProvidePlugin({
        '_': 'lodash'
      })
    ]
  }

そのうえで、gettersをview_status定義し、降順でtimestampが並ぶように処理をします。

store/view_status.js
export const getters = {
  orderedStatus: state => {
    return _.orderBy(state.statuses, 'timestamp', 'desc')
  }
}

5. ローカルで動作確認

ローカルでの検証は npm run devで動作させ、localhost:3000/status上で正常に動くことを確認しました。

再掲になりますが、以下のようなUIで、動作が問題なく動きました。
+ 一番左のチェックボックスをクリックすると、Doneの値がtrueへ更新
+ 再度クリックするとDoneの値がfalseに更新
+ 一番右のremoveボタンをクリックすると対象データが削除
+ 上記アクションを実施した際に、アラートが画面上上がること

UI

6. Firebase Hostingへのデプロイ

まず、Nuxtプロジェクトをビルドします。npm run build
Firebaseプロジェクトを firebase init コマンドで初期化します。

注意としては、hostingを選択することと、build後に生成されるフォルダはdist(not public)なので、distを指定することです。

上記を実施後、firebase deployコマンドでデプロイします。
問題がなければ、https://<your project>.firebaseapp.com/status/ などにアクセスして確認できます。

7. CloudBuildでCI環境構築

CloudBuildでCI環境構築構築し、githubにコードをデプロイすると、CloudBuildがトリガーされ、FirebaseHostingへ自動デプロイの仕組みも作りました。CloudBuild側の設定は、別途サイトを参考ください。
準備としては、Dockerfileを作成し、cloudbuild.yamlで設定を入れることで動作しました。
ちなみに、.gitignoreファイルに.envが記載されているので、CloudBuildで連携する場合は、.envもリポジトリに上げるようにする必要があります。

Dockerfile
# use latest Node 10
FROM node:10
# install Firebase CLI
RUN npm install -g firebase-tools

ENTRYPOINT ["/usr/local/bin/firebase"]
steps:
# Install
- name: 'gcr.io/cloud-builders/npm'
  args: ['install']
# Build
- name: 'gcr.io/cloud-builders/npm'
  args: ['run', 'build']
# Docker Image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/firebase', '.']
# Deploy
- name: 'gcr.io/$PROJECT_ID/firebase'
  args: ['deploy', '--project', '$PROJECT_ID']
# push built images to Container Registry
images: ['gcr.io/$PROJECT_ID/firebase']

おわりに

Vue.js / Nuxt.js のおかげで比較的簡単に、ブラウザ上でFirestoreデータを確認し、更新があったらアラートが上がる機能を作成できました。

もう少し応用的なものも作ってみたいと思うのと、環境面では、FirebaseHostingではなく、Google App Engineにもデプロイができるようなので、そちらも試してみたいと思います。

コードはgithubに公開しました。

参考コンテンツ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ant Design Vue の Message で複数行表示させる

AntDesign の MessageAPI

https://www.antdv.com/components/message/

スクリーンショット 2019-11-29 12.26.51.png

AntDesignを使っていれば this.$message.info('Hello') などで利用が可能となっています。

スクリーンショット 2019-11-29 12.31.45.png

素晴らしいですね。

複数行表示したい

ヒーリングっど♥プリキュアは2020年春からの放映なのでそれも伝える必要があります。
なので2行表示にしたかったためとりあえず改行コードを入れてみます。

スクリーンショット 2019-11-29 12.37.08.png
おもてたんとちがう…

もちろん <br> を入れてもそのまま表示されてしまいます。
このままでは来年春から始まる新しいプリキュアの素晴らしさがうまく伝わりません。

Stringでは無理

MessageAPIの info 関数は第一引数に string | VNode | (h) => VNode を取ります。
ということで string では無理そうなので他のものを試します。

VNodeって?

こちら で解説してくれていますが仮想ノードにあたります。
要するにこのあたりには直接仮想ノードを渡せますよーということみたいです。

VNodeの作り方

ドキュメント にもあるとおり createElement 関数で作成するのが一般的なようです。
しかし render 内ではないので該当の関数がありませんが h がそれにあたるようです。

createElement を h にエイリアスしていることは、 Vue のエコシステムの中でよく見かける慣習です。そして、それは実は JSX には必須です。Vue の Babel プラグインの バージョン 3.4.0 以降では、ES2015 のシンタックスで宣言された JSX を含むメソッドや getter(関数やアロー関数は対象外)に対しては、自動的に const h = this.$createElement が注入されるため、(h) パラメーターは省略できます。それ以前のバージョンでは、もし h がそのスコープ内で利用可能でない場合、アプリケーションはエラーを throw するでしょう。

ということだそうです。
ん? this.$createElement でもええんか…?

ということで

this.$message.info(h => {
  return h('span', [
    h('span', 'ヒーリングっど♥プリキュア'),
    h('br'),
    h('span', '2020年春からスタート!')
  ])
})

スクリーンショット 2019-11-29 13.01.16.png

ヒャッホウ!!
改行だけでなくハートマークを赤くもできますね。

動作確認したやつ置いときますね。

面倒くさくない?

JSX でもできるみたいです、使えるなら絶対そっちのがいい。
公式もそう言ってます。

次は @karur4n が何かしら書いてくれると思います。
面白い人なので記事もきっと面白いんだろうなあと思います。
ぜひよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuetifyjsのv-selectにてitemsのキーがtextとvalueじゃないときの対処法

はじめに

vuetifyjsのv-selectでは、デフォルトのitemsの仕様は以下となっています

{
  text: string | number | object
  value: string | number | object
}

itemsにtextとvalueがないときの設定に迷ったので、備忘録を兼ねてメモを残します

対処法

itemsとして、以下のオブジェクトを使用する場合のコード例です

{
  [
    { categoryName: "カテゴリ1", id: 1 },
    { categoryName: "カテゴリ2", id: 2 },
    { categoryName: "カテゴリ3", id: 3 },
    { categoryName: "カテゴリ4", id: 4 }
  ]
}

textにcategoryName、valueにidを使用する場合、v-selectのitem-valueとitems-textにitemsの対応するキーを指定します。ここでは以下のようにします

  • text : categoryName
  • value : id
<v-select
  v-model="id"
  label="*カテゴリ名"
  :items="categories"
  item-text="categoryName"
  item-value="id"
  required>
</v-select>

参考

Select component

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでHTMLのバリデーションが効かなくて詰まった時の話

inputタグに使用できる入力必須の機能

http://www.htmq.com/html5/input_required.shtml

入力してなかったら弾かれるという基礎的なバリデーションの機能をつけようとして、よく使用するhtmlの機能のrequired="true"で処理しようとした。が、バリデーションが効かず入力してなくても次のページへ遷移してしまう。。。

HTML、CSSに関しては個人アプリやスクールで利用していたため、requiredが効かない原因がVue.jsの何らかの機能のせいなのだろうということは何となくわかったのですが、知識がなくてどこでバリデーションが無効になっているのかがわからず...

原因

submitボタンでの処理が原因でした。

//template部分
@on-click="$emit('next'); submit()"

この一行。

//js部分
  methods: {
    submit() {
      this.$emit("update", {
        eventId: this.$route.params.id,
        Name: this.Name,
        mailAddress: this.mailAddress,
      });
    }
  }

クリックした時に関数のsubmitが発火。$emit('next')で次に遷移している。
この$emit('next')で、バリデーションに引っかかってようがいまいが、データを持って次に遷移してしまっている。
なので、バリデーションをかけるのはsubmitの関数の中。

  methods: {
    submit() {
      if(this.eventId && this.eventId && this.eventId){
        this.$emit("update", {
          eventId: this.$route.params.id,
          Name: this.Name,
          mailAddress: this.mailAddress,
        });
        this.$emit('next');
      }
    }
  }

こうすることで、入力していないとボタンが押せなくなりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsを使った初めてのフロント実装

Vue.jsとは?

https://lab.aratana.jp/entry/2017/12/09/000000
↑参考資料

jQueryは少し触ったことがありますが、動きをつけたい部分にちょこっと使用していただけのため、そこまでJavaScriptに詳しいわけでもなく....。

本当に漠然とした理解なのですが、動きをつけたい部分をそれぞれコンポーネントとして分けて記載し、それをhtmlで記載した部分の骨組みの中で呼び出して使用するという感じでしょうか。
今回はすべてVue.jsでの実装になるので混乱していますが、パーツ利用だともう少し理解しやすいのかなーと思ったり。
知識的にはほぼない状態のスタートなのではやく慣れていきたいです。

よく出てくるメソッドのメモ

  • v-bind 属性を反映する

下記(HTML側)は、この要素のclass属性を、VueインスタンスのisActiveプロパティによって更新して保存するという処理になっている。

//例) HTML側。この場合、isActiveが真偽値となっており、trueだとactiveクラスが生成される。
<div class="app">
  <div v-bind:class="{ active: isActive }"></div>
</div>

//例) js側
new Vue({
  el: 'app',
  data: {
    isActive: true
  }
});
  • v-for(htmlとjsがある) 配列の繰り返しを扱う
//例) HTML側。listsという配列の入ったメソッドを、listというメソッドに代入して繰り返し処理
<div id="app2">
  <li v-for="list in lists" :key="list.id">
    {{ list.name }}
  <li>
</div>

//例) js側
var vueApp= new Vue({
  el: '#app2',
  data: {
    lists: [
      { name: 'Vue.js' },
      { name: 'Angular.js' },
      { name: 'React' }
    ]
  }
});

vueApp.lists.push({ name: 'Nuxt.js'})
//この記載でvueAppの中の配列listsにname:'Nuxt.js'を追加できる。
  • v-model(htmlとjsに) 入力を反映する
//例) HTML側
<div id="app4">
  <p>{{ message }}</p>
  <input v-model="message">
</div>

//例) js側
new Vue({
  el: '#app4',
  data: {
    message: 'Hello Vue!'
  }
})

local-strageとは??

Cookieとかsessionみたいなもの。
検索機能つけるときに。
https://qiita.com/RunEagler/items/4496161d3b709659a897

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+Vueのユーザー登録実装でムカついたのでメモ!!!

やったこと

deviseなどのgemを使わずユーザー登録機能をrailsとvueで実装

ハマったこと

ユーザー登録フォームに入力してPOSTした時、:userの中にpasswordとpassword_confirmationの値が入らず(ネストせず)かなり苦戦。

具体的にはこんな感じ。

理想形

コンソール
Parameters: {"user"=>{"name"=>"hogehoge", "email"=>"fugafuga@test.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}

NG形

Parameters: {"name"=>"", "email"=>"", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "user"=>{"name"=>"", "email"=>""}}

passwordとpassword_confirmationだけuserの中に入ってこない!!

まあNG形の場合でもコントローラーのストロングパラメーターで

users_controller.rb
private
  def user_params
    params.permit(:name, :email, :password, :password_confirmation)
  end

とすれば大丈夫なんだけど、どうしてもカッコよくこう書きたい!

users_controller.rb
private
  def user_params
    params.fetch(:user, {}).permit(:name, :email, :password, :password_confirmation)
  end

vueで書くとhas_secure_password効かないのか〜?とか、formの書き方が悪いのか〜?とか、とにかく色々と原因を考えまくって時間を浪費したこと浪費したこと。

こういう時プログラミング嫌いになる:triumph:

そんな話はさておき、本題を続けます。

Vueの書き方

先にNGだった書き方から。(js部分のみ。かなり省略してます)

NGの書き方

NewForm.vue
import axios from 'axios';
export default {
  data: () => ({
    user: {
      name: '',
      email: '',
      password: '',
      password_confirmation: '',
    },
  }),
  methods: {
    createUser: function() {
      axios
        .post('/api/v1/users', this.user)
        .then(response => {
          var res = response.data;
          this.$router.push({ name: 'home' })
        })
        .catch(error => {
          if(error.response.data && error.response.data.errors) {
            this.errors = error.response.data.errors;
          }
        })
    },

これだとNG形になります。

理想形のパラメーターが返ってくる書き方

NewForm.vue
import axios from 'axios';
export default {
  data: () => ({
    name: '',
    email: '',
    password: '',
    password_confirmation: '',
  }),
  methods: {
    createUser: function() {
      axios
        .post('/api/v1/users', { user: { name: this.name, email: this.email, password: this.password, password_confirmation: this.password_confirmation }})
        // 以下、同じなので省略

POSTする時に、こうやって明示的に書けばOKだった!

どうしても納得いかないのが、NGの時もnameとemailはちゃんとネストされてたってこと。
passwordとpassword_confirmationは、テーブルのカラムに存在しないから取れなかったと思うんだけど、has_secure_passwordを書いててもダメなもんかい:sob:

初歩的な実装に、ものすごい時間がかかってしまったというお話でした。
ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む