20200405のvue.jsに関する記事は14件です。

VueRouter使い方

VueRouterのインストールから初期セットアップ

vue-routerをインストール

npm install vue-router

vue-routerを利用する各ファイル

main.js
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';

//router.jsファイル
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')
router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'Index',
      component: () => import('./components/Index.vue')
    },
    {
      path: '/form',
      name: 'Form',
      component: () => import('./components/Form.vue')
    },
  ]
})
App.vue
<template>
  <div id="app">  
    <v-app>
      <Header/>
      //Routerを利用
      <router-view/>
      <Footer />
    </v-app>
  </div>
</template>

router使用例

example.vue
<router-link to="/foo">Go to Foo</router-link>

//Vuetifyのv-btnとの組み合わせ
<router-link tag="v-btn" to="/edituser">登録情報を変更</router-link>

//Vuetifyのv-btnとパラメータをセット
<router-link tag="v-btn" :to="{ name: 'ToPage', params: { id: this.$route.query.fooid }}"


//画面表示時にStore(Vuex)からログイン状態を取得。ログインしていない場合にログインページへ遷移
created(){
      const loginStatus = this.$store.getters['loginStatus'];
      if (loginStatus == false && this.$router.currentRoute.path != "/login") {
        this.$router.push({name : "PageLogin"});
        return;
      }

参考

https://router.vuejs.org/ja/installation.html

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

さいきょうの配色 Web サービスが激遅だったので丁寧にパフォーマンスチューニングした

「久々に Lighthouse 確認してみるかな……」
start.png

「……!??」

「にじゅーご……?」
25.png

:innocent:

はじめにおことわり

この記事は Nuxt.js で個人開発した Web サービスがパフォーマスンス面でイマイチな状態だったので、それをチューニングした内容をまとめています。

計測は Lighthouse でおこない、その改善指針にしたがいチューニングしています。
パフォーマンスチューニングを幅広い視点で汎用的に行うための記事ではありませんので、あらかじめご了承くださいませ。
(逆に、これから個人開発をするような初学者の人には役に立つ情報があるかもしれません)

さいきょうの配色 Web サービスとは?

:link: ぼくのかんがえたさいきょうの配色Webサービスを公開した話

以前こちらの記事で紹介させていただきましたが、色に興味があることから開発した Web サービス『 yum-yum COLOR 』のことを指します。

構成は Nuxt.js × Netlify で、 PWA にも対応した SPA です。
フロントエンド初学者だった当時の僕にしては、まあまあがんばっていたんじゃないでしょうか。

記事の中でも紹介していますが、当時も Lighthouse で計測をおこなっており、そこそこのスコアを出していました。
Lighthouse の仕様は頻繁にアップデートされており、久々に計測したら散々だった……ということです。

Lighthouse について

冒頭にも紹介した Lighthouse は、 Google が開発したオープンソースのツールで、パフォーマンスをはじめとした Web アプリの品質向上に役立つツールです。

以前は Chrome 拡張により利用できましたが、最近では開発者ツール( Chrome から F12 で表示)にも標準で用意されています。
devtools.png

Chrome 拡張の方はモバイルの測定になりますが、開発者ツールではモバイル・デスクトップのそれぞれを測定することが可能です。
また、開発中のアプリ( URL が localhost:300 など)を測定できるのも開発者ツールのみです。
(スペックの低い PC だと正しく計測できないことがありましたので、どちらも併用するのがよいかもです)

Lighthouse が提案してくれる改善指針

Lighthouse はパフォーマンスなどを計測するだけでは終わりません。
「なにが原因なのか? どうすれば改善するか?」を改善指針として表示してくれます。
next-gen-formats.png

たとえばこれ。
「アプリが利用している画像のフォーマットが PNGJPEG だと重いよ!」といってくれています。
次世代フォーマット(って表現でいいのか?)である JPEG 2000JPEG XRWebP を使おう!
って感じで。

今回はこれらの指針にしたがいチューニングをしていきます。

さいきょうの配色 Web サービスのパフォーマンス

冒頭でキャプションを貼りましたが、チューニング前のパフォーマンスは次のような感じです。
base-performance.png

ひどいスコアですね :sweat:
ここから改善させていきます。

チューニング

1. ESLint

これはチューニング以前の問題なのですが、当時このアプリを開発していた頃は本当にフロントエンドの知識が少なく、 ESLint をビルド時に自動実行する設定にしていませんでした。
それどころか Lint を知らなかったわけで……(笑)

実際に Lint を流してみると微妙なところがたくさんあり、とりあえずエラーを解消させています。
それによるものかはわかりませんが、スコアが『 29 』になりました。
step1.png

2. 次世代フォーマットの画像への差し替え

next-gen-formats.png

これは上ですでに紹介しましたが「画像のフォーマットを新しいものに変えましょう」というものです。
個人的には WebP 推しなのですが、 Safari が対応しておらず、 iPhone ユーザーの多い日本では結構痛手です。

改善方法

Safari のみ JPEG ……というのも考えましたが、それはそれで JS 側が遅くなりそうなので、一番重い画像( 189KB のもの)を PNG から JPEG に変える対応のみ行いました。
(他の画像は比較的軽いですし、透過である必要があるので)
step2.png

うん、気持ちだけスコアアップ :sweat_smile:
ただ、 「Metrics」の First Contentful Paint の時間がかなり縮んでいますし、「 Opportunities 」の Serve images in next-gen formats が縮んでいます。

どちらも 赤字 から オレンジ に変わっていますね。
作ってる身としては、これだけでも気分が悪くないです!

3. Ensure text remains visible during webfont load

Ensure text remains visible during webfont load.png

これは Google フォントなどのいわゆる Web フォントを使用している場合に、一部のブラウザではフォントの読み込み中にテキストを非表示にするために、テキストが点滅しているように見えてしまうそうです。
「それに対して対策を打ってよ」とのこと。

どのフォントを利用しているのか調べてみると roboto というフォントでした。
使った覚えがなかったので node_modules に対して Grep すると、犯人は bulma でした。
CSS フレームワークである bulma の標準スタイルでこのフォントを使っていたようです。

改善方法

こちら に書いてありますが、 @font-face スタイルに font-display: swap; を指定することで改善するそうです。

@font-face {
  font-family: 'Pacifico';
  font-style: normal;
  font-weight: 400;
  src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
  font-display: swap;
}

また、 Google フォントであれば URL に &display=swap を付与するそうです。

<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">

ただ、 bulma のフォントにはどちらもうまく適用できませんでした……。

:link: Nuxt.jsにBulmaを導入して変数を使ったカスタマイズを行う

こちらの記事を参考に @nuxtjs/bulma の利用をやめたり試行錯誤しましたが解決方法がわかりません :head_bandage:
(わかる方いたら教えてほしいです)

4. Reduce the impact of third-party code

Reduce the impact of third-party code .png

これはわかりやすいですね、サードパーティのコードのインパクトが大きいよ! だそうです。

しかも一番パフォーマンスに影響を与えているのは……広告。
広告収入は個人開発をしている人にとって重要な収入源なので、これはこのままで(笑)

5. Minimize main-thread work

Minimize main-thread work .png

いかにもパフォーマンスという項目がきました。
メインスレッドのロードに要した時間の内訳です。

これを見ると分かるとおり、 Script Evaluation がもっともロードに時間がかかっています。
実はスクリプトのロード時間に関しては、次の項目で詳細がわかるようになっているのでそちらを見てみましょう。

6. Reduce JavaScript execution time

Reduce JavaScript execution time.png

JS の解析、コンパイル、および実行がかかっているということです。
デフォルトではサードパーティのコードも表示されているので、右上のチェックボックスを外すと自分のコードのみ表示することができます。

改善方法

まずは JS のコードを分析してみましょう。

nuxt.config.js
/*
 ** Build configuration
*/
build: {
  analyze: true,
},

nuxt.config.js にビルドオプションを追加し、アプリをビルドしましょう。

$ yarn run build

するとブラウザに次のような画面が表示されます。
yum-yum-color_.nuxt_stats_client.html.png

これはプロジェクトで利用されているコードの割合を示しています。
画面内で大きく表示されているものほど、その容量も大きくなります。

「……Font Awesome。おまえだったのか」

さいきょうの Web 配色サービスでは、ヘッダーに Twitter や GitHub 、それにブログ用のリンクを用意しており、そこで Font Awesome のアイコンを利用しています。

それなのに、利用の仕方は……こう。

import { fas } from '@fortawesome/free-solid-svg-icons'

面倒くさがり屋の自分を恨みたくなる設定です :sob:

これをちゃんと、

import { faBlog } from '@fortawesome/free-solid-svg-icons'

として利用しました。
yum-yum-color_.nuxt_stats_client.html (1).png

おー Font Awesome 小さくなりましたね。

スコアは……
step5.png

これはまあまあ改善されましたね!
やっと許容範囲といったところでしょうか。

その後も色々と試しましたが……

step5-2.png

逆に下がってしまったり……安定しません。

さいきょうの Web 配色サービスでは配色を扱うその特性上、インライン SVG を利用しているため一つのファイル容量が大きくなるなど、どうにもこうにもうまくいきませんでした。

ただし別の方法でパフォーマンスを担保している

このアプリが遅いのは SPA は初期の読み込みに時間がかかるという特性がダイレクトに響いています。

それを解消する一つの方法として PWA があります。
現にこのアプリも 2 回目以降の表示は高速(というか爆速)、 Lighthouse のスコアほど遅い印象は受けないと思います。
(自分で言う)

これから個人開発をはじめるかたは PWA を意識してみるのもいいかもしれません。

おわりに

この記事は企画とタイトルを先に思いつき、チューニングしながら記事を書き進めていきました。
チューニングがバッチリな結果であれば良かったのですが、実際はそうもうまくいかないもので…… :sweat_smile:

パフォーマンスチューニングの技術記事としては微妙なものですが、Lighthouse を使ったことがない人や、ソースコードの解析を知らない人にとって少しでも役に立てば幸いです :yum:

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

コロナウイルスの疾患者人数を可視化するサイトを作成しました

コロナウイルスの疾患人数を可視化できるサイトを作る

疾患者が増えていると断片的にニュースで聞くものの、私自身人数規模がいまいちピンときていませんでした。
適切な危機感を持つには定量的な情報を把握するのが大事だと思い、疾患者人数をグラフ化したサイトを作りたいと思っていました。

疾患者情報を提供するAPIがあると小耳に挟んだので週末で作ることにしました。

仕様要件

  • 世界及び日本の疾患者数の状況が一目で分かる
  • 国ごとのランキングが視覚的に分かる
  • スマホでスクロールせずに見れる

制約条件

  • 土日だけで完成させる(週を跨いだら多分完成しない。リリースするなら早い方がいい)
  • qiitaも土日中に書き終える(アウトプットとして残す)

出来上がったもの

https://github.com/growsic/covid-overview
COVID-19 Overview.jpeg

レポジトリURL

使った技術

  • RapidAPI
  • GitHub Pages
  • Vue.js
  • APEXCHARTS

を使いました。

データ取得:COVID-19のRapidAPI

RapidAPIでは有償/無償で多くのAPIが提供されています。
世界各国の疾患状況をリストで返してくれる無償のAPIがあったのでこちらを使いました。

今回使ったAPI:
https://rapidapi.com/api-sports/api/covid-193/details

サイトの公開: GitHub Pages

RapidAPIを使うことで静的サイトのみの公開で済むため、GitHub Pagesを利用しました。
GitHubでレポジトリを作成し、Settings > GitHub Pages > Sourceでmasterブランチを選択すれば、masterブランチにcommit/mergeするだけで自動的にGitHub Pagesに反映されるのでとても便利です。

JavaScript: Vue.js

ロジックや値の管理用です。復習がてら使いました。

グラフ描画: APEXCHARTS

グラフを描画するJavaScriptのライブラリとして、APEXCHARTSを使いました。
グラフの描画であればChart.jsの方が有名で使おうと思ったのですが、縦方向のグラフ
スクリーンショット 2020-04-05 19.24.09.png
を描画しようとした時に縦の長さの調節が上手くできず、各Q&Aサイトでも解決仕切れていなそうだったので代わりのライブラリを探しました。

「chartjs alternative」で検索した中で比較的人気が高く、見た目も良かったAPEXCHARTSを使いました。

大抵のグラフ描画系ライブラリは

{
    series: [
      {
        data: []←値(Y軸。今回で言えば人数)
      },
    ],
    xaxis: {
      categories: []←ラベル(X軸。国名や時間など)
    },
}

のような描画オプションの中にあるリストに、表示したい値の情報をpushして行けば簡単にグラフの表示が出来ます。
後は描画オプションをドキュメントにしたがってカスタマイズしていくことで希望通りの見た目に出来ます。

グラフ描画系ライブラリはライブラリの提供するオプション以外での見た目変更がやりにくいため、望み通りの描画が出来ない場合は他のライブラリに乗り換えるのもありです。

困ったこと

アメリカの疾患者が多過ぎてグラフが潰れる(30万人 vs 2000人)

アメリカの30万人規模のグラフと他国を同じスケール上で並べると

2000人以上いる日本がほとんどいないように見えてしまいました。

一部の飛び値に引っ張られて他が小さく埋もれるのは意図と異なるので、人数ごと10国ずつにページングする仕様にしました。
スクリーンショット 2020-04-05 22.41.08.pngスクリーンショット 2020-04-05 22.41.25.png

表が思い通りの見え方にならない

先述の通りChart.jsからAPEXCHARTSに変えたところ、使用例が少なく本家ドキュメント以外参考になるサイトがありありませんでした。
ひたすらドキュメントのsearchボックスに描画オプションのキーワードを入れ、ドキュメントと公式サンプルを見比べ試行錯誤しながら解決方法を探しました。
グラフ描画系ライブラリはライブラリが提供する以外の見た目変更が難しいため、根気強くドキュメントから探すのも大切です。

見た目がカッコ良くならない/レスポンシブ対応したい

普段webフロントを触らなく基本的なhtml/cssしか分からないが、せっかくなら見た目も良いものにしたい。
今回で言えばグラフはAPEXCHARTSを使ったためグラフの見た目は問題なく、世界/日本のサマリ情報の表を特になんとかしたいと思っていました。
週末で完成まで持っていきたかったので、html/cssのテンプレートを活用/改良することにしました。

世界/日本のサマリ情報の表はGitHubのプラン別料金表のような表を加工すれば出来そうだと考えたので
「html css price table template」
で調べて出てきた沢山のテンプレートの中からイメージに近いものを見つけ、加工して使いました。

ある程度レスポンシブ対応はされているものの細部は修正が必要だったため、要点を絞って検索して修正して行きました。

最後に

会社のとびっきり優秀なエンジニアの方に、どうやったら優秀なエンジニアになれるか聞いた時に教えてもらった一つが、
「やりたいことを決めたら、現在到達可能そうなものを作りきってみる」
ということでした。

「要素技術をある程度理解したら、出来るような気持ちになってしまう。
でもいざ作り切ろうとしたら見えていなかった難しさが沢山見えてくる。
作りきってみてようやく理解できる」

ということだったので、作りたいもの決めたら必ず作りきることを最近考えるようにしていました。
今回はVue.jsとChart.js組み合わせればさくっと作れるだろうと思っていたものの、いざ着手してみると細かいハマるポイントがいくつかあり、半日で作るはずが1日半以上かかっていました。

何とか見てもらえるクオリティに仕上がったものの、今後も作りきることに真摯に向き合わないとなーと思っています。

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

Vue.jsの双方向バインディング再入門

この記事の主なターゲットはReactから入った人や、仕組みが分からないと怖くて分からないと怖くて使えないという人と、フォーム部品のコントロールの記載方法をよく忘れる人向けの記事です。
また、上級者向けとしてカスタムコンポーネントへのv-modelの利用方法も書いてみました。

先日「Vueの双方向バインディングがよしなにやってくれすぎててよくわからない」という相談を受けました。

これはおそらくVue.jsから入門した人や、Vueって簡単じゃん、便利じゃんと飛びついた人にとってはよく分からない感覚かもしれませんが、言われてみればなるほどv-modelは多くのことをやっています。

間違ってることとか足りないこととかあったらコメントとか編集リクエストください

それでは処理の概要を見ていきましょう。

v-model の概要

v-model=hogev-bind:value="hoge"v-on:input="hoge = $event.target.value" のシンタックスシュガーです。
radio, select は束縛するイベントが input ではなく changeになります

言ってみたらいわばこれだけです。

Vue.jsのデータ管理機構 observer に関する補足

Vueのdataメソッドが返す各プロパティは、 observer によって(オブジェクトや配列の場合再起的に)ラップされており、これが中の値の変更をキャッチして v-bind やcomputed, watchedのトリガーとなって伝搬します。
このため、Objectにキー名を指定して新しいプロパティを生やしたり配列を添え字指定して配列長を延長しながら代入するとobserver にラップしてもらうことができずデータの変更がレンダリングに反映されないこととなります。
(補足しておくと、array.push, pop, shift などの基本的な配列操作は拡張されておりこれらはobserverでラップしたもの常に返すように扱っています。)

Console.logでオブジェクトを見ると生のオブジェクトではなく必ずObserverにラップされているのを確認できると思いますがその正体はこいつです。
スクリーンショット 2020-04-05 22.57.50.png

つまり、v-modelはobserverがdataの変更を感知してv-bind(v-model)しているプロパティを代入を行い、inputやchangeイベント(これらはブラウザJSの標準イベント)でフォームの値が変化を感知してdataに代入を行っています。

v-model の扱いは以上でしょうか。

textではないフォームのコントロール

さて、textの扱いは上記でよいでしょう。
ところでradio buttonは?checkboxは? selectboxはどうでしょう?

主な用途からサンプルコードを考えていきたいと思います。

ラジオボタン

hoge.html
<input type="radio" name="sample1" value="OK">OK<br>
<input type="radio" name="sample1" value="NG">NG<br>
<input type="radio" name="group2" value="3">3

この場合画面はこのようになります。

画面収録 2020-03-25 23.04.43.gif

また、ぽちぽち触るとこんな感じ。
nameの一致でグループ分けが行われるのはHTMLの範疇ですね。

これをvueでよしなにしましょう。
ループの中とかいいんじゃないですかね。
サンプルの都合でnameが1個だけ違うみたいな罠は特に仕込んでません。

radio.vue
<template>
  <div>
    <p>{{ itemValue }}</p>
    <div v-for="(item, index) in items" :key="index">
      <input type="radio" name="sample1" :value="item" v-model="itemValue" />
      {{ item }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      itemValue: "りんご",
      items: ["ぶどう", "りんご", "みかん"]
    };
  }
};
</script>

この場合どのように振る舞うでしょうか?
まず、itemsがループして がループの終端まで作成されます。
="valueは" ループの子要素である item, つまり ぶどう、りんご、みかんがそれぞれ入っています。
すべてに v-model="itemValue" がついているので、 dataのitemValueはチェックがついているものが採用されます。

また、双方向でバインドされているので itemValueに別のルートで値を代入した場合、自動的にブラウザ上のラジオボタンの選択状態も切り替えることができます。
ラジオボタンなのでitemValueは常に1つのパラメータしかもちません。
うっかりnameの統一されていないradioボタンを1つのv-modelでバインドしてしまうと、 画面上複数のラジオボタンにチェックをつけられてしまいますが、実際にitemValueが持つ状態は最後に選択された1つだけなので気をつけてください(要調査)
そんなことありませんでした。まあ気をつけておくにこしたことはないです。

チェックボックス

checkboxもありますよ。

checkbox.html
  <input type="checkbox" name="sample3" value="ぶどう">ぶどう<br>
  <input type="checkbox" name="sample3" value="りんご">りんご<br>
  <input type="checkbox" name="sample3" value="みかん">みかん

スクリーンショット 2020-04-05 21.55.31.png

この場合もループでVueにしてみまよう。

select.vue
<template>
<div id="app">
  <p>{{ itemValues }}</p>
  <div v-for="(item, index) in items" :key="index">
    <input type="checkbox" name="sample" :value="item" v-model="itemValues" />
    {{ item }}
  </div>
</div>
</template>

<script>
export default {
  data() {
    return {
      itemValues: ["りんご"],
      items: ["ぶどう", "りんご", "みかん"]
    };
  }
}
</script>

この場合、チェックボックスの選択要素は配列になります。
複数の要素が選択されることがあるわけですからね!

画面収録-2020-04-05-23.05.17.gif

補足:単体のCheckbox
nameを持たない単体のcheckboxの場合、v-modelでバインディングされた値は boolean値をとるようです
フォーム入力バインディング — Vue.js

select box

select.html
  <select>
    <option>初期選択肢</option>
    <option value="りんご">りんご</option>
    <option value="ぶどう">ぶどう</option>
    <option value="みかん">みかん</option>
  </select>

スクリーンショット 2020-04-06 0.12.22.png

Vueで書くときはselectの方に v-model, optionにvalueを設定してあげましょう。

select.vue
<template>
  <div id="app">
    <p>{{itemValue}}</p>
    <select v-model="itemValue">
      <option value="" disabled>選択してください</option>
      <option v-for="(item, i) in items" :key="i" :value="item">{{item}}</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      itemValue: "りんご",
      items: ["ぶどう", "りんご", "みかん"]
    };
  }
};
</script>

v-model の式の初期値がオプションのどれとも一致しない場合、 要素は “未選択” の状態で描画されます。iOS では、この場合 iOS が change イベントを発生させないため、最初のアイテムを選択できなくなります。したがって、上記の例に示すように、disabled な空の値のオプションを追加しておくことをおすすめします。

らしいです。

disabled, checked, selected

Vueのdisabled は booleanでコントロールできます。したがって、
<input :disabled=“isDisable”>
isDisableがtrueならその項目はdisableになります。

checked, selectedに関してはv-modelの状態に同期しているので基本的に気にしなくてよいはずです

値をNumberで受け取りたい

v-model.number="value" というように、 .number修飾子をつけてください。

修飾子には他に .lazy, .trimがあります
フォーム入力バインディング — Vue.js

上級者向け:カスタムコンポーネントにおけるv-model

カスタムコンポーネントでもv-modelを使うことができます。
この場合も概ねフォーム系タグへのシンタックスシュガーと同様で、 v-bind:value=hogev-on:input="hoge = $event.target.value"のシンタックスシュガーとなっています。

このため、子のコンポーネントは以下の2点が実装されていることを期待しています。
- Props down に value プロパティ
- Emit(Event) up に input イベントとvalue

つまり、以下のようなカスタムテキストフォームなんかをコンポーネントとして定義することができます。

customInput.vue
<template>
  <div>
    <label :for="item.id">{{ item.label }}</label>
    <input :id="item.id" v-model="editableValue" @input="emit" />
    <br />
    <p v-for="(errorMessage, i) in errorMessages" :key="i">{{ errorMessage }}</p>
  </div>
</template>

<script>
export default {
  props: ["value", "item"],
  data() {
    return {
      editableValue: this.value // propsを直接編集すると警告がでるのでコピーする
    };
  },
  methods: {
    emit() {
      this.$emit("input", this.editableValue);
    }
  },
  computed: {
    errorMessages() {
      const messages = [];
      if (this.editableValue === "") {
        return messages;
      }

      if (this.editableValue.trim().length < 5) {
        messages.push("5文字以上を設定してください");
      }
      if (this.editableValue.match(/[A-Z]/)) {
        messages.push("大文字は設定できません");
      }

      return messages;
    }
  }
};
</script>

呼び出し側はこんな感じでOK

Parent.vue
<template>
  <div>
    <CustomInput v-model="sampleValue" :item="sampleItem" />
    <p>{{ sampleValue }}</p>
  </div>
</template>

<script>
import customInput from "./hoge";

export default {
  components: { customInput },
  data() {
    return {
      sampleValue: "init",
      sampleItem: {
        id: 1,
        label: "ラベル",
      }
    };
  }
};
</script>

画面収録-2020-04-05-23.05.17.gif

これだとオブジェクトを渡すとラベルとバリデーション付きInputタグをコンポーネント化できました。
今回はバリデータをコンポーネントの中に埋め込んでしまいましたが、バリデータの定義もitem オブジェクトの中に入れてしまってcomputedの中に入れてしまうとかすると面白いかもしれません。

とかとか。色々使い道があるかもしれないですね!

Appendix

フォーム入力バインディング — Vue.js
Vue.jsのv-modelを正しく使う - Qiita

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

vueのフォームでenumを使う

今作っているアプリでは登録フォームにVueを使っています。
element-uiを使っているのですが、el-selectにenumで定義したものをどうやって持ってくるのかがわからず、試行錯誤を重ねた末なんとかできました!

class Api::V1::InitialisationsController < Api::V1::ApplicationController

  def show
    *省略
    @results = Trade.results.keys
                     *enumで定義したのを呼び出すときは複数形で書くらしい
    respond_to do |format|
      format.json {render :json =>
        {
          *省略
          results:          @results,
        }
      }
    end
  end

end

こういう感じでAPIコントローラーに定義し、

import Vue           from 'vue/dist/vue.esm.js';
import Notifications from 'vue-notification';
import ElementUI     from 'element-ui';
import               'element-ui/lib/theme-chalk/index.css';
import locale        from 'element-ui/lib/locale/lang/ja';
import superagent    from 'superagent';

Vue.use(ElementUI, { locale })
Vue.use(Notifications)
//see https://www.npmjs.com/package/vue-notification

const token = document.getElementsByName('csrf-token')[0].getAttribute('content')

window.onload = function(){
  var trade = new Vue({
    el: "#trade",
    data: {
      results: []
    },
    created: function() {
      superagent
      .get(`/api/v1/initialisations.json`)
      .set('X-CSRF-Token', token)
      .set('Accept', 'application/json')
      .end(function(error, data){
        trade.$data.results = data.body.results
      })
    }
  })
}

こうして

<el-select placeholder="result" v-model="trade.result">
  <el-option
   v-for="result in results"
   :key="result"
   :label="result"
   :value="result"
  >
</el-select>

こういう感じで書くとenumで定義したのが使えます!
登録した時もちゃんと意図したものが保存されました!

よかったよかった。

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

Vue.jsのSFC(単一ファイルコンポーネント)+PHPでWebアプリケーションを構築する - バックエンド環境編

前回の記事はこちら

本記事は、『フロントエンド環境編』の続きです。

バックエンド側の環境構築

タイトルにある通り、バックエンドはPHP言語を利用します。
また、著者はWindows10で環境構築をしています。

ソースのルートディレクトリについて

前回に引き続き、C:\hello-vuejsを前提に話を進めます。

インストールライブラリのまとめ

バックエンド側で必要なものは以下の通りです。

ソフトウェア バージョン 説明
Apache 2.4.* Webサーバー
PHP 7.4.* バックエンド言語
Slim Framework 3.1.* PHPフレームワーク
Slim PHP-View ^2.2 PHPフレームワーク
Monolog ^2.0 PHPロガー

Xamppのインストール

ApacheとPHPを使いたいので、XAMPPをインストールしました。本記事は、XAMPPベースで説明を進めます。
また、XAMPPの7.4系(しっかり確認していませんが、PHPの7.x系なら全般的に動作可能な気がします)をチョイスしました。

XAMPPのページ

HINT
XAMPPのインストールを進めると、コンポーネント選択画面が表示されます。
本記事で必要なものは、以下の通りです(最小限の指定)。

XAMPPインストールウィザード - Select Components
xampp_select_components.png

注意
著者の環境(OSはWindows10)では、インストール完了後に、XAMPPコントロールパネルを起動する際、"Error:Cannot create file "[XAMPPインストールパス]\xampp-control.ini". Access denied.と表示されました。
エラーを解消するには、XAMPPインストールパスのxampp-control.exeを管理者権限で起動(右クリックして管理者権限で起動)します。以後は通常の操作で起動できるようになりました。

Apacheの設定

ポート番号の変更

Apacheを起動すると、httpは80番、httpsは443番として、デフォルトのポート番号が割り当てられています。
デフォルトのままだと、他のアプリとポート番号が衝突する可能性があるので変更することにします。
本記事では、httpを5555番、httpsを5556番に変更しました。

[XAMPPインストールパス]\apache\conf\httpd.conf
# Listenの設定
Listen 80

Listen 5555

# ServerNameの設定
ServerName localhost:80

ServerName localhost:5555
[XAMPPインストールパス]\apache\conf\extra\httpd-ssl.conf
# Listenの設定
Listen 443

Listen 5556

# VirtualHostの設定
<VirtualHost _default_:443>

<VirtualHost _default_:5556>

# ServerNameの設定
ServerName www.example.com:443

ServerName www.example.com:5556

公開ディレクトリの設定

hello-vuejspublicディレクトリをApacheの公開ディレクトリとして指定します。
公開ディレクトリの指定には幾つかありますが、ここでは、alias_moduleを利用します。

[XAMPPインストールパス]\apache\conf\httpd.conf
...
<IfModule alias_module>
    ...

    Alias /hello-vuejs "C:\hello-vuejs\public"
    <Directory "C:\hello-vuejs\public">
        AllowOverride All
        Require all granted
    </Directory>

</IfModule>
...

HINT
alias_moduleを使用すると、URLが ”http://[ドメイン]/hello-vuejs” のように1段階層が深くなります。
virtual_hostを使用するというのも良い選択肢かと思います。

PHPの設定

PHPの挙動を設定するファイルは、[XAMPPインストールパス]\php\php.iniです。
今回は、デフォルト設定のままで問題ないので特に編集しません。

Apacheの起動

XAMPPのコントローラパネルからApacheを起動しましょう。下図のような画面になればOKです。

hello-vuejs2_xampp_control_panel.png

Composerで必要なパッケージを導入する

PHPの各種ライブラリを入手するために、Composerをインストールするところから始めます。
以下のページから最新版をダウンロードしてインストールしましょう。

Composerのページ

HINT
ComposerはPHPのパッケージ管理システムです。こちらを使って、PHPの各種ライブラリを導入します。

Composerの初期化

コマンドプロンプトを開いて、C:\hello-vuejscdしましょう。
そして、最初に以下を実行してください。
色々聞かれますが、大体の質問にEnterを押して、最後にyesを入力して行けばとりあえずOKです。

詳細が知りたい人は、ググってみてください。

コマンドプロンプト
composer init

...

# パッケージ名は、<vendor>/<name>形式の必要があるので、"ison/hello-vuejs"と入力
Package name (<vendor>/<name>) [***/***]: ison/hello-vuejs

... その他の質問は適当にEnterとyesを入力する

Composerのパッケージインストール

次に、以下を実行して行きます。

コマンドプロンプト
composer require slim/slim:3.*
composer require slim/php-view
composer require monolog/monolog

HINT
composer requireは、npmで言うところのnpm installと同じものです。
著者の環境では、コマンドを打ち込んでからしばらく反応が無く3分以上待たされてしまいました。気長に待ちましょう。

Autoloadの設定

次に、composer.jsonの"autoload"部分を追記します。

composer.json
{
    ...
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "config": {
        "optimize-autoloader": true
    }
}

ファイルの編集が終わったら、以下を実行します。

コマンドプロンプト
composer dumpautoload

autoloadは、PHPでモジュールファイルをロードする仕組みの事です。
簡単に説明すると、上の設定はsrcディレクトリに配置されるPHPのクラスファイルは、Appという名前空間で始まるという事を意味します。

また、最適化のために、"optimize-autoloader": trueというオプションも有効にしました。
詳細は、以下のページを確認してください。
オートローダーの最適化

HINT
クラスの追加や変更があった場合に、オートローダーの最適化を再適用するコマンドは以下の通りです。

コマンドプロンプト
composer dumpautoload           ... 最適化あり
composer dumpautoload --no-dev  ... 最適化ありで開発環境以外にリリース

バージョン管理システムからチェックアウトした直後などは、単にcomposer install [--no-dev]するだけでOKです。

composer.jsonに"optimize-autoloader": trueを記述しない場合は、composer dumpautoload -o [--no-dev]composer install --optimize-autoloader [--no-dev]とすればOKです。


全て実行し終えると、以下のようなファイルが出来上がっているはずです。

ファイル構成
c:\hello-vuejs
    + vendor
    + composer.json
    + composer.lock

vendorには、インストールしたライブラリファイルが格納されています。こちらは、バージョン管理が不要なディレクトリです。

composer.jsonには、composer initした際の各種設定と、何をインストールしたかの情報が記載されています。
composer.lockには、インストール時の厳密なライブラリのバージョン番号のスナップショットが記載されています。
こちらの2ファイルはコミットが必要です。

他の開発者は、こちらのcomposer.jsonまたはcomposer.lockから、composer installを実行して必要なライブラリをインストールすることになります。
したがって、本節で実施した、composer initとcomposer require [ライブラリ名]は、一度限りの実行になります。

他の開発者は、バージョン管理ツールからチェックアウトしたソースに対して、composer installを実行するだけで必要なライブラリを揃えることができます。

index.php、.htaccess、bootstrap.phpの作成

Webリクエストを受信した際にPHPを動作させるために必要な起動用のファイルを作成します。

ファイル構成
c:\hello-vuejs
    ...
    + public
        + index.php
        + .htaccess
    + src
        + bootstrap.php

Webリクエストを受信すると、以下の順序で処理が行われます。

  1. .htaccessの実行(リクエストされたファイルが存在しない場合に、mod_rewriteの書き換えルールが働きindex.phpが実行される)
  2. index.phpの実行(index.phpの先頭の処理でbootstrap.phpが読み込まれる。index.phpはURLの内容からコントローラークラスと実行すべきメソッドを特定して実行する)

.htaccessの内容は以下の通りです。
mod_rewriteというURL書き換えモジュールの適用と、PHP設定の初期化です。

.htacccess
<IfModule mod_rewrite.c>
  RewriteEngine On

  # Some hosts may require you to use the `RewriteBase` directive.
  # Determine the RewriteBase automatically and set it as environment variable.
  # If you are using Apache aliases to do mass virtual hosting or installed the
  # project in a subdirectory, the base path will be prepended to allow proper
  # resolution of the index.php file and to redirect to the correct URI. It will
  # work in environments without path prefix as well, providing a safe, one-size
  # fits all solution. But as you do not need it in this case, you can comment
  # the following 2 lines to eliminate the overhead.
  RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
  RewriteRule ^(.*) - [E=BASE:%1]

  # If the above doesn't work you might need to set the `RewriteBase` directive manually, it should be the
  # absolute physical path to the directory that contains this htaccess file.
  RewriteBase /hello-vuejs

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^ index.php [QSA,L]
</IfModule>

php_value "display_errors" "On"
php_value "error_reporting" "E_ALL"

php_value "max_execution_time" "0"

php_value "memory_limit" "1024M"
php_value "post_max_size" "1024M"
php_value "upload_max_filesize" "5G"
php_value "max_input_vars" "10000"
php_value "date.timezone" "Asia/Tokyo"

index.phpの内容は以下の通りです。
前述で作成したbootstrap.phpのロードと、HTTPリクエストのルーティング設定を行います。

例えば、http://[ドメイン]/hello-vuejs/helloを実行するとsrc\Func\Hello\Controller\HelloController.phpactionIndexが実行されます。

index.php
<?php

use Slim\Exception\NotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;

$app = require __DIR__ . '/../src/bootstrap.php';

$app->any('/[{func1}[/{func2}[/{func3}]]]', function (Request $request, Response $response, array $args) use($app) {

    $func1 = $args['func1'] ?? null;
    $func1 = ucfirst($func1);
    $func2 = $args['func2'] ?? null;
    $func2 = ucfirst($func2);
    $func3 = $args['func3'] ?? null;
    $func3 = ucfirst($func3);

    $class = '';
    $method = '';
    $pattern = null;

    if ($func1 && $func2 && $func3) {
        // 例)/login/auth/exec → Login\Controller\AuthController#actionExec
        $pattern = 1;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func2 . 'Controller';
        $method = 'action' . $func3;
    } else if ($func1 && $func2) {
        // 例)/login/exec → Login\Controller\ExecController#actionIndex
        $pattern = 2;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func2 . 'Controller';
        $method = 'actionIndex';

        if (!class_exists($class)) {
            // 例)/login/exec → Login\Controller\LoginController#actionExec
            $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func1 . 'Controller';
            $method = 'action' . $func2;
        }
    } else if ($func1) {
        // 例)/login → Login\Controller\LoginController#actionIndex
        $pattern = 3;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func1 . 'Controller';
        $method = 'actionIndex';
    }

    if (!class_exists($class)) {
        throw new NotFoundException($request, $response);
    }

    $controller = new $class($app);
    if (!method_exists($controller, $method)) {
        throw new NotFoundException($request, $response);
    }

    try {
        return $controller->$method();
    } catch (Throwable $exc) {
        $GLOBALS['unexpectedException'] = $exc;
        throw $exc;
    }
});

// Run app
$app->run();

bootstrap.phpの内容は以下の通りです。
PHPのフレームワークであるSlim関連のインスタンスを生成しています。

bootstrap.php
<?php

use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
use Slim\App;
use Slim\Views\PhpRenderer;

const PUBLIC_PATH = __DIR__ . '/../public/';
const SRC_PATH = __DIR__ . '/';

$app = null;

/*
 * Load the autoloader
 */
$autoloader = require __DIR__ . '/../vendor/autoload.php';

// Create Slim App instance
$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
]);

/*
 * Set up dependencies
 */
$container = $app->getContainer();

// View Renderer
$container['renderer'] = function ($c) {
    $templatePath = SRC_PATH;
    return new PhpRenderer($templatePath);
};

// monolog
$container['logger'] = function ($c) {
    $settings = $c->get('settings')['logger'];
    $logger = new Logger('app');
    $logger->pushProcessor(new UidProcessor());

    $handler = new RotatingFileHandler(__DIR__ . '/../log/app.log', 0, \Monolog\Logger::DEBUG, true, 0664);
    $handler->setFilenameFormat('{date}-{filename}', 'Y/m/d');
    $logger->pushHandler($handler);

    return $logger;
};

return $app;

Controllerクラスの作成とHelloコンポーネントの修正

前述で作成したindex.phpから実行されるコントローラクラスを作成します。
Helloコンポーネントも合わせて修正します。

ファイル構成
c:\hello-vuejs
    ...
    + src\Func
        + Base\Controller
            + BaseController.php     ... Hello・Goodbyeなどの各コントローラの基底クラス
        + Goodbye\Controller
            + GoodbyeController.php  ... Goodbyeのコントローラ
        + Hello
            + Controller
                + HelloController.php    ... Helloのコントローラ
            + Front\View                 ... Helloコンポーネント(htmlとjsのみ修正)
                + Hello.html
                + Hello.js

BaseController.phpの内容は以下の通りです。

特に重要なのは、renderメソッドです。
このメソッドで、src\View\Layout\Base.php内のHTMLテンプレートをレスポンスに出力しています。

BaseController.php
<?php

namespace App\Func\Base\Controller;

use App\Common\DB\DBObserver;
use App\Common\Exception\ServiceException;
use App\Common\Log\AppLogger;
use App\Common\Message\MessageManager;
use App\Common\ResponseCache\ResponseCache;
use App\Common\Session\SessionData;
use App\Common\Util\DBUtil;
use App\Constant\CommonConstant;
use App\Func\Login\Service\LoginService;
use SebastianBergmann\RecursionContext\Exception;
use Slim\App;
use Slim\Container;
use Slim\Exception\NotFoundException;
use Slim\Http\Response;
use Slim\Http\Stream;
use const SRC_PATH;

/**
 * 基本となるコントローラークラス。
 * コントローラクラスは、本クラスを継承すること。
 */
class BaseController {

    /**
     * @var App Appオブジェクト
     */
    protected $app;

    /**
     *
     * @var Container Containerオブジェクト
     */
    protected $container;

    /**
     * @var AppLogger ロガー
     */
    protected $logger;

    /**
     * @var string レイアウト名
     */
    protected $layoutName;

    /**
     * @var array リクエストパラメータ
     */
    protected $requestParams;

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {

        $this->app = $app;
        $this->container = $app->getContainer();
        $this->logger = $this->container['logger'];

        $this->layoutName = 'Base';
        $this->requestParams = null;

    }

    /**
     * リクエストパラメータを取得する。
     * @return array リクエストパラメータ
     */
    protected function getRequestParams(): array {

        if ($this->requestParams !== null) {
            return $this->requestParams;
        }

        $request = $this->container->request;

        $getParams = $request->getQueryParams() ?? [];
        $postPutParams = $request->getParsedBody() ?? [];

        $allParams = array_merge($getParams, $postPutParams);
        $this->requestParams = $allParams;

        return $allParams;
    }

    /**
     * 描画メソッド。
     * @param string $contentsViewPath コンテンツビューのパス … /srcディレクトリからのパスを指定する(例:/Func/Login/Front/View/Login)
     * @param array $contentsData データ
     * @param string $layoutName レイアウトページ名 … /src/View/Layout下のレイアウトページを指定する(例:Base.phpの場合は、"Base"と指定する)
     * @param array $importWidget インポートするWidget
     * @return type
     */
    protected function render(string $contentsViewPath, array $contentsData, string $layoutName = null) {

        if (!file_exists(SRC_PATH . $contentsViewPath . '.vue')) {
            // ファイルが存在しないため、例外を発行する(基本的には発生しないエラー)
            throw new \Exception("コンテンツビューファイル未存在エラー contentsViewPath={$contentsViewPath}");
        }

        $contentsViewName = basename($contentsViewPath);

        // レイアウトページの設定
        if ($layoutName === null) {
            $layoutName = $this->layoutName;
        }
        $layoutPhp = '/View/Layout/' . $layoutName . '.php';

        // コンテナから関連するオブジェクトを取得
        $container = $this->container;
        $request = $container->request;
        $response = $container->response;
        $renderer = $container->renderer;
        $baseUrl = $request->getUri()->getBasePath();

        // リクエストパラメーターを取得する
        $requestParams = $this->getRequestParams();

        // レイアウトページのデータオブジェクト
        $layoutDataObj = [
            '__baseUrl' => $baseUrl,
            '__requestParams' => $requestParams,
            '__contentsViewPath' => $contentsViewPath,
            '__contentsViewName' => $contentsViewName,
            '__contentsData' => $contentsData,
        ];

        return $renderer->render($response
                        , $layoutPhp
                        , $layoutDataObj);
    }

    /**
     * Json出力メソッド。
     * @param mixed $data The data
     * @param int $status The HTTP status code.
     * @param int $encodingOptions Json encoding options
     * @return type
     */
    protected function renderJson($data, int $status = null, int $encodingOptions = 0) {

        $response = $this->container->response;

        return $response->withJson($data, $status, $encodingOptions);
    }

}

HelloController.phpで重要なのは、actionIndexメソッドです。
内部では、BaseController#renderメソッドを呼び出しています。引数にHelloコンポーネントのパスを渡すことで、最終的にVue.jsのHelloコンポーネントを表示するように指示しています。

また、Helloコンポーネント上のボタン押下時のajax通信に対応するために、actionTalkメソッドを設けました。こちらは、BaseController#renderJsonメソッドでtalkMessageデータをレスポンスとして返却しています。

HelloController.php
<?php

namespace App\Func\Hello\Controller;

use App\Func\Base\Controller\BaseController;
use Slim\App;

/**
 * Helloコントローラー。
 */
class HelloController extends BaseController {

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {
        parent::__construct($app);
    }

    /**
     * 表示処理。
     */
    public function actionIndex() {

        return $this->render('/Func/Hello/Front/View/Hello', []);
    }

    /**
     * 話す処理。
     */
    public function actionTalk() {

        $params = $this->getRequestParams();

        $data = ['talkMessage'=>"Hi, I came from space."];

        return $this->renderJson($data);
    }
}

Helloコンポーネントのhtmlとjsファイルも修正します。
修正内容は、ボタンの追加・ボタン押下時の処理・返却メッセージの表示領域の追加です。

Hello.html
<div class="hello-component" v-cloak ref="view">

    <div>
        This is Hello Component.<br>
        {{message}}
    </div>

    <div>
        <button type="button" @click="talk">Hello?</button>
    </div>

    <div>
        <p>{{talkMessage}}</p>
    </div>

</div>
Hello.js
export default {
    data() {
        return {
            message: 'Hello Vue.js',
            talkMessage: ''
        };
    },
    methods: {
        talk() {

            $.ajax({
                url: AppContext.baseUrl + '/hello/talk'
            }).then((data)=> {
                this.talkMessage = data.talkMessage;
            });

        }
    }
}

GoodbyeController.phpは、HelloController.phpとほとんど同じ内容です(ただしactionTalkメソッドは設けていません)。

GoodbyeController.php
<?php

namespace App\Func\Goodbye\Controller;

use App\Func\Base\Controller\BaseController;
use Slim\App;

/**
 * Goodbyeコントローラー。
 */
class GoodbyeController extends BaseController {

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {
        parent::__construct($app);
    }

    /**
     * 表示処理。
     */
    public function actionIndex() {

        return $this->render('/Func/Goodbye/Front/View/Goodbye', []);
    }

}

レイアウトHTML(Base.php)の作成

Base.phpは、前回の記事で作成したindex.htmlに代わるHTMLのテンプレートです。
ついでに、C:\hello-vuejs\public\index.htmlを削除しましょう。

ファイル構成
c:\hello-vuejs
    ...
    + public
        + index.html ... !!! 削除する
    + src\View\Layout
        + Base.php

前回の記事で、index.htmlではPOSTパラメータにアクセスすることができないと言いました。
ですが、Base.phpで動的にHTMLを生成して、リクエストパラメータ(GETやPOST)やサーバー側で任意に設定した情報を、JavaScriptコードとして生成することで、JavaScript側でPOSTパラメータやその他サーバーで設定した情報に(AppContextというグローバル変数経由で)アクセスできるようになります。

Base.php
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-cache">
        <meta http-equiv="Expires" content="Mon, 26 Jul 1997 05:00:00 GMT">
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale = 1.0, maximum-scale = 1.0, user-scalable = no">

        <title>Hello Vue.js</title>

    </head>

    <body class="layout-base">

        <div id="app" v-cloak>
        </div>

        <?php /* グローバル関連の定義 */ ?>
        <script type="text/javascript">

            // アプリケーションコンテキストの定義(アプリケーション全体で使用したいデータ等を定義、イミュータブルなオブジェクトにする)
            var AppContext = Object.freeze({
                name: 'Hello Vue.js',
                baseUrl: '<?= $__baseUrl ?>',
                requestParams: <?= json_encode($__requestParams) ?>,
                contentsComponentPath: '<?= $__contentsViewPath ?>',
                contentsComponentId: '<?= $__contentsViewName ?>Component',
                contentsData: <?= json_encode($__contentsData) ?>
            });

        </script>

        <?php /* バンドルファイルの読み込み */ ?>
        <?php /* ※本ファイルの読み込み後に、main.jsで定義したグローバル変数などが読み込みできるようになる */ ?>
        <script src="<?= $__baseUrl . '/dist/bundle.js?' . filemtime(PUBLIC_PATH . 'dist/bundle.js') ?>" ></script>

        <?php /* Vue.js生成 */ ?>
        <script type="text/javascript">
            // Vue.js生成
            window.AppFuncs.createVuejs(AppContext.contentsComponentPath, AppContext.contentsComponentId);
        </script>

    </body>

</html>

main.jsの修正

各種ファイルの新規作成に伴い、main.jsの修正が必要になります。

ファイル構成
c:\hello-vuejs
    ...
    + src
        + main.js

その前に、jQueryをインストールしたいので、cd c:\hello-vuejsして以下を実行しましょう。
jQueryは、ajaxメソッドを利用するために導入しました。axiosでもOKです。

コマンドプロンプト
npm install -D jquery

main.jsを以下の内容で置き換えます。

大きく変更された箇所は、jQueryのインポート、Vue.jsインスタンスの生成メソッドをAppFuncsに移動したこと。また、最後の行でwindowオブジェクトにjQueryとAppfuncsを設定しています。

windowオブジェクトはグローバルオブジェクトなので、このようにしないと、bundle.js(main.jsが含まれている最終成果物ファイル)の外から、これらのオブジェクトを参照することができないのです。

main.js
// -----------------------------------------------------------------------------
// ライブラリのインポート
// -----------------------------------------------------------------------------
// JQuery
import $ from 'jquery';
// VueJs
import Vue from 'vue';

/**
 * アプリケーションの関数定義
 */
const AppFuncs = {

    /**
     * Vue.jsインスタンスを生成する。
     * @param {String} componentPath コンポーネントパス
     * @param {String} componentId コンポーネントID
     * @returns {Vue} Vue.jsインスタンス
     */
    createVuejs(componentPath, componentId) {

        // 動的インポートを実施
        //
        // componentPathを以下のルールで変換
        // 例)ログインの場合
        //    /Func/Login/Front/View/Login
        //     ↓
        //   ./Func/Login/Front/View/Login.vue
        const componentLoadPromise = import("./" + componentPath.replace(/^\//, "") + ".vue");

        componentLoadPromise.then(function (value) {
            // 動的インポート完了

            // VueのComponentのグローバル登録は、Vueインスタンス生成前に実施する必要あり
            Vue.component(componentId, value.default /* import関数の戻り値に default があるので、そちらを使用する(defaultエクスポート定義を読み込むという意味になる) */);
            // Vueインスタンスの生成
            const vm = new Vue({
                el: '#app',
                render: h => h(componentId)
            });

            return vm;
        });

    }

};

// -----------------------------------------------------------------------------
// グローバル変数の定義
// -----------------------------------------------------------------------------
window.$ = $; // jQueryオブジェクト
window.AppFuncs = AppFuncs;

HINT
ES6で定義されたデータや関数にES6未満のJSコードからアクセスする場合、ES6側で対象データをwindowオブジェクト(グローバルな領域に)に設定することで、アクセスが可能になります。

webpack.dev.jsの修正

前回はNode.jsをWebサーバーとして利用していましたが、PHPを使用するために今回からApacheをWebサーバーとして使用することになります。ただ、Apacheを使用するとHMR(Hot Module Replacement)が機能しなくなります。そのためプロキシ設定を行います。

プロキシ設定は、webpack.dev.jsで行います。

ファイル構成
c:\hello-vuejs
    ...
    + webpack.dev.js

修正内容は以下のdevServer部分です。devServer内の最終行にproxyを丸ごと貼り付けてください。
ついでに、devServer内のopenPageを変更(index.htmlを削除したので)しましょう。

HMR(Hot Module Replacement)を利用するためには、dist/bundle.jsファイルは今まで通りNode.jsサーバーに処理させたいので、dist以下のファイルはNode.jsで、それ以外の全てのリクエストはApache Webサーバーにリクエストさせるといった事をproxy部分に記述しています。

webpack.dev.js
...
module.exports = merge(common, {
    ...
    devServer: {
        ...
        // ブラウザの表示ページ
        openPage: prefixUri.substring(1) + '/hello',
        ...
        // プロキシ設定
        proxy: [
            {
                context: [
                    // すべてのリクエストを対象とする
                    '**',
                    // dist以下は対象外とする
                    '!' + prefixUri + '/dist/**'
                ],
                // Apache Webサーバーに転送する
                target: 'http://localhost:5555'
            }
        ]
    }
});

動かしてみる

処理イメージは下図の通りです。

hello-vuejs2_index_php_flow.png

C:\hello-vuejsで以下のコマンドを実行してください。すると、ブラウザが起動してページが表示されるはずです。

コマンドプロンプト
npm run start

hello-vuejs2_screen_hello.png

Helloコンポーネント上の『Hello?』ボタンを押してPHPと通信できるか試してみましょう。
すると、下図のように、『Hi, I came from space.』というメッセージが表示されましたね。

hello-vuejs2_screen_hello_after_pressed_button.png

HMRが有効かどうかも試しましょう。Hello.jsを開いて、dataのmessageを編集してみると…。

Hello.js
...
    data() {
        return {
            message: 'ニイハオ Vue.js',
            talkMessage: ''
        };
    },
...

hello-vuejs2_screen_hello_after_edit.png

Goodbyeコンポーネントの画面も表示しましょう。
http://localhost:8080/hello-vuejs/goodbye

hello-vuejs2_screen_goodbye.png

リリースビルドの実行

リリースビルドの実行については、フロントエンドで実行したnpm run buildと何ら変わりありません。
本番環境にリリースするファイルを作成するには、npm run buildしてbundle.jsを作成しましょう。

それと、デバッグ時はNode.jsのWebサーバーを経由してApacheにアクセスしていましたが、リリース時はApacheに直接接続するので、URLは以下のように(Apacheに直接接続)します。

http://localhost:5555/hello-vuejs/hello
http://localhost:5555/hello-vuejs/goodbye

最終的なファイル構成

ファイルの最終的な構成は以下の通りです。
フロントエンドとバックエンドのソースが混合された構成になっています。

ファイル構成
C:\hello-vuejs
    + node_modules               ... npmでインストールしたライブラリ(バージョン管理対象外とするディレクトリ)
    + public                     ... Webサーバーの公開ディレクトリ
        + .htaccess              ... Apache設定ファイル
        + index.php              ... フロントコントローラ
    + src
        + Func                   ... 機能別ディレクトリ
            + Base
                + Controller
                    + BaseController.php
            + Hello
                + Controller
                    + HelloController.php ... Helloコントローラー
                + Front\View              ... Helloコンポーネント
                    + Hello.vue
                    + Hello.html
                    + Hello.css
                    + Hello.js
            + Goodbye
                + Controller
                    + GoodbyeController.php ... Goodbyeコントローラー
                + Front\View                ... Goodbyeコンポーネント
                    + Goodbye.vue
                    + Goodbye.html
                    + Goodbye.css
                    + Goodbye.js
        + View
            + Layout
                + Base.php       ... HTMLテンプレート
        + bootstrap.php          ... PHP起動用共通処理
        + main.js                ... webpackエントリポイントファイル
    + vendor                     ... Composerでインストールしたライブラリ(バージョン管理対象外とするディレクトリ)
    + composer.json              ... Composerに関する設定ファイル
    + composer.lock              ... composer requireのスナップショットファイル
    + package.json               ... npmに関する設定ファイル
    + package-lock.json          ... npm installのスナップショットファイル
    + webpack.common.js          ... webpackの共通設定ファイル
    + webpack.dev.js             ... webpackの開発用設定ファイル
    + webpack.prod.js            ... webpackのリリース用設定ファイル

バックエンドのまとめ

以上で、本記事が目標としていたWebアプリケーションのひな型が完成しました。
SPA(シングルページアプリケーション)でもない、従来のページ遷移方式で、さらにはVue.jsのSFCを用いることもできました。

webpackのビルドも、設定さえ正しくできれば開発効率を妨げる要因はなく、むしろ開発効率が向上することが実感できました。
JavaScriptのES6も慣れてくると非常に書きやすいです。本記事を応用すればTypeScriptを導入することもできます。

ただ、webpackの流儀に従うのであれば、依存するライブラリが増えた場合には、main.jsにimportを追記する必要が有ります。
画像ファイルもbundle.jsにマージしたいこともあるでしょうし、Webフォントも一緒に取り扱うという事であればwebpackの設定と深く付き合っていく必要があります。これらは、公式リファレンスを見たり、色々な記事を見て勉強していくしかありませんね。

今回作成したソースはGitHubにアップしておきました。以下のページにアクセスして確認してみてください。
hello-vuejsソース(PHP統合版)

:grinning:

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

[vue]テーブル内のチェックボックスをまとめてチェックする

codepenを使ってみたかったので備忘がてら投稿

See the Pen マトリクス内のチェックボックスをまとめてチェックする by shotanekoneko (@shota-yamane) on CodePen.

tdタグ内のチェックボックスがチェックされたときに
thタグ内のON/OFFに対応してないので
ごまかしでボタンに置き換えるか or そこの処理は新しく実装が必要・・・

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

$data, $propsに型をつけるプラグインを作る

始めに

Vue.jsで昔以下の記事を書いたことがあり、$data, $propsを書く習慣をつけていました。
しかしTypeScriptで型推論をするとこれらはanyと解釈されてしまうため、非常に使いづらくなってしまいました。

そんな時に、以下のようなVue.jsの実装をそのまま流用して型だけ当てるライブラリを見かけたので、同様なことができないかを試しました。

この記事ではTypedVueと言うライブラリを自作して、これの紹介と実装方法についてまとめました。

https://www.npmjs.com/package/@wintyo/typed-vue

ライブラリの機能

やっていることは非常にシンプルで、以下のことだけしています。

  • $data, $propsに型をつける
  • this直下にdataやpropsの型を含めない

呼び出し方はVue.extendと一緒の書き方で、完全推論版と、data, propsの型を伝えた書き方それぞれについて書きます。(比較のために通常の書き方とライブラリを使った書き方の両方を載せます)

完全型推論版

全ての型を推論させる方法で、それぞれ以下のように書きます。

通常
import Vue from 'vue';

export default Vue.extend({
  props: {
    str: { type: String },
  },
  data() {
    return {
      value: 10,
      localStr: this.str,
      // $propsからのアクセスは可能だが、anyになってしまう
      // localStr: this.$props.str,
    };
  },
  computed: {
    _double(): number {
      return 2 * this.value;
      // $dataからのアクセスは可能だが、anyになってしまう
      // return 2 * this.$data.value;
    }
  },
});
TypedVue
import { TypedVue } from '@wintyo/typed-vue';

export default TypedVue.typedExtend({
  props: {
    str: { type: String },
  },
  data() {
    return {
      value: 10,
      localStr: this.$props.str,
      // this直下をアクセスするとエラーになる
      // localStr: this.str,
    };
  },
  computed: {
    _double(): number {
      return 2 * this.$data.value;
      // this直下をアクセスするとエラーになる
      // return 2 * this.value;
    },
  },
});

props, data型の定義版

上記の方法でもある程度型はつけられますが、特にpropsは推論が難しく、Dateを渡すとなぜかstringと推論されてしまったりする問題があります。
そこでprops, dataは先に定義し、その定義に合わせて設定コードを書くようにする方法は以下のようになります。
慣れないとめんどくさいかもしれませんが、Reactとかみると先に型を定義してますし、そもそも推論でどうにかしようとする方が無理があるように感じました。

props,data型の定義版(通常)
import Vue from 'vue';
import { RecordPropsDefinition } from 'vue/types/options';

interface IProps {
  str: string;
  date: Date;
}

interface IData {
  value: number;
  localStr: string;
}

export default Vue.extend({
  props: {
    str: { type: String },
    date: { type: Date },
  } as RecordPropsDefinition<IProps>,
  data(): IData {
    return {
      value: 10,
      localStr: this.str,
    };
  },
  created() {
    this.date;  // ちゃんとDate型と認識される
  },
});
props,data型の定義版(TypedVue)
import { TypedVue, RecordPropsDefinition } from '@wintyo/typed-vue';
// 以下と同じ
// import { RecordPropsDefinition } from 'vue/types/options';

interface IProps {
  str: string;
  date: Date;
}

interface IData {
  value: number;
  localStr: string;
}

export default TypedVue.typedExtend({
  props: {
    str: { type: String },
    date: { type: Date },
  } as RecordPropsDefinition<IProps>,
  data(): IData {
    return {
      value: 10,
      localStr: this.$props.str,
    };
  },
  created() {
    this.$props.date;  // ちゃんとDate型と認識される
  }
});

実装方法

ここからはライブラリの実装の中身になります。
簡単に言えばanyになってしまうところに上手く型が当たるように追加する感じになります。
ただ事前に型が乗っているせいで、設定は非常にやりづらいです。

$data, $propsのany定義を止める

まずVue.jsはデフォルトでRecord<string, any>になっているせいで、そこに型がついてもこちらが優先されて型エラーになることができません。
そこで空オブジェクトにオーバーライドさせて、それを使うようにします。少し厄介なのが、Vueは実はVueConstructor<V>の型であるため、それに合わせて上手くキャストさせます。

デフォルト
export interface Vue {
  readonly $data: Record<string, any>;
  readonly $props: Record<string, any>;
}
拡張
import Vue from 'vue';
import { Vue as IVue, VueConstructor as IVueConstructor } from 'vue/types/vue';

export interface ITypedVue extends IVue {
  readonly $props: {};
  readonly $data: {};
}

export const TypedVue = Vue as IVueConstructor<ITypedVue>;

data, propsの型を$data, $propsに当てる

詳細は以下の記事に譲りますが、CombinedVueInstanceDataPropsの部分が{ $data: Data }, { $props: Props }のように入れば型がつきます。ついでにthis直下に型がつくのもなくせますね。

ThisTypeの拡張
// デフォルト
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Props>>

// 拡張
ThisType<CombinedVueInstance<V, { $data: Data }, Methods, Computed, { $props: Readonly<Props> }>>

このような変更を一つずつ行いますが、既存の型を変更するわけにはいかないので、新しく型を作って上記のような微妙な変更を行っています。

https://github.com/wintyo/typed-vue/blob/v0.0.2/lib/index.ts#L12-L15

新しく定義した名前で動作するようにする

typedExtendと言う名前にしたが、このメソッドでも動くようにプラグインを設定しました。

プラグインの設定
const Plugin = {
  install(Vue) {
    const typedExtend = function (this: any, options: any) {
      const instance = this.extend(options);
      instance.typedExtend = typedExtend;
      return instance;
    }
    Vue.typedExtend = typedExtend;
  },
} as PluginObject<never>;

export default Plugin;

終わりに

通常では$data, $propsはanyになっているので、そこに型を付けるライブラリの紹介と実装方法について書きました。割と強引な実装方法なので少しでも型定義が変わると動かなくなりますが、Vue 3.0になりますしOptions APIはこれ以上変わらないだろうなと思っています。
Vue 3.0はComposition APIになりますが、書き方がカオスにならないかとか、大幅な変更でライブラリ周りの連携とか心配ですし、もう少し様子見なのかなと思いました。Class形式だったらすぐにでも乗り移るんですけどね。。。

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

Vuetifyの基本的な使い方とよく使いそうなところのドキュメントのメモ

Vuetifyとは

公式ページ

Vue.jsのマテリアルデザイン用のフレームワークです。React.jsのMATERIAL-UIのVueバージョンと言ったところです。

Vuetifyを使用する最大のメリットはCSSを書かなくても、整ったUIを作成することができるということです。

Vuetifyの導入

VueCLIはインストール済を想定してます。

//プロジェクトの作成
$ vue create my-app //「my-app」は何でもいいここの名前でプロジェクトが作成される
//vuetifyの追加
$ vue add vuetify

Nuxt.jsなどで導入するときはこちらを参考

サンプル

画面

スクリーンショット 2020-04-05 14.55.04.png

コード

Home.vue
<template>
  <div class="home">
    <h1>HomePage</h1>
    //v-btnでボタンが作れる
    //classにpinkをあてることでピンク色に、white--textをあてることでテキストが白に
    <v-btn class="pink white--text">click me</v-btn>
    //textをつけるで背景色と影がなくなる colorで色を指定
    <v-btn text color="pink">click me</v-btn>
    // depressedをつけることで影がなくなる
    <v-btn depressed class="pink white--text">
      //v-iconでアイコンをつける mdi-emailでemaiのアイコン
      <v-icon left>mdi-email</v-icon>
      <span>EMAIL ME</span>
    </v-btn>
    //fabをつけることで丸くできる
    <v-btn fab depressed small dark color="purple">
      //v-iconでアイコンをつける mdi-heartでハートのアイコン
      <v-icon>mdi-heart</v-icon>
    </v-btn>
  </div>
</template>

<script>

export default {
}
</script>

勉強方法

チュートリアルをやるのが手っ取り早いです。

英語ですが、画面に映っている通りにやっていけば大体のことは理解できます。
また、画面通りにやって上手くいかない時には、コメントを見れば、解決できることが多いです。

よく使うそうな部分のドキュメントメモ

下記は私がよく使いそうだと感じる部分のドキュメントのリンクのメモです。
大体は下記のドキュメント、サンプルコードを読むことで大体理解できるかなぁと思います。
Codepenへのリンクにとんで実際にコードをイジってみるのも面白いです。


https://vuetifyjs.com/en/styles/colors/#colors

文字サイズ
https://vuetifyjs.com/en/styles/typography/#font-sizes

ボタンとicon
https://vuetifyjs.com/en/components/buttons/#buttons

フォーム
https://vuetifyjs.com/en/components/forms/#forms

ファイルを使うフォーム
https://vuetifyjs.com/en/components/file-inputs/#file-inputs

使い勝手良さげ

自分の手でCSSを書かなくていい分、慣れれば工数を削減できる可能性高いなと思います。
かなり凝った表現が必要、とかでない限りはこういったフレームワークを使うのはアリなんじゃないかなぁと思います。

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

Vue初心者が2日でポートフォリオサイトを作る

2日でポートフォリオサイトを作ったのでまとめておこうと思います。
田川裕隆のポートフォリオサイト

環境/技術

  • macOS
  • Vue / Vue Router
  • Vuetify
  • (Typescript)
  • Figma
  • Netlify
  • GitHub

1日目

サイトの構想を練る

使う技術、内容、デザインを考えます。
技術に関しては、Vuetifyを試してみたかったのでVue.jsを使うことにしました。
TypescriptをかじっていたのでVue.tsで環境構築はしましたが、ロジックをほとんど書かずVuetifyに全てを委ねたのでTypescriptはおろかJavascriptを書いたという意識は全くありません。
せっかくなのでこれからアレンジを加えていきたいと思います。
内容、デザインに関しては、Figmaというデザインツールを使って構想をねりました
4色を使い分けたシンプルなデザインと、ページのトップに、押したくなるようなボタンを設置したのがこだわりです。
デプロイは、github.ioを使おうと思っていたのですが、少し調べたらNetlifyがヒットしたので使ってみました。
NetlifyとGithubを連携させ、サイトのアップロード作業を自動化する方法という記事を参考にしたらすんなりデプロイできました。Netlify最高です。

環境構築

まずはVue CLIをインストールし、プロジェクトの作成をします。

npm  install -g @vue/cli
vue create vue-typescript

? Please pick a preset: では、 Manually select featuresを選択し、? Check the features needed for your project: では、後々使うかもしれないので全てを選択しておくとよいでしょう。(スペースキーで選択できます)
他の質問にはYesで答え、他の項目では、自分の好みのものを選択するとよいでしょう。

プロジェクトが作成されたら、ローカルサーバーを立てます。

cd vue-typescript
npm run serve

localhostで Welcome to Your Vue.js App の画面が出ていれば成功です。

次に、Vuetifyの導入をしていきます。

vue add vuetify

再びnpm run serveをすると以下のようなエラーが出てしまいます。
スクリーンショット 2020-04-05 10.49.07.png

このエラーは、ts.config.jsoncompilerOptions.types"vuetify"を追加するだけでなおりました。(参考: Vuetify と TypeScript を使用した環境を構築してサンプルプロジェクトを立ち上げるまでの手順)
localhost で Welcome to Vuetify の画面が出ていれば成功です。

次に、フォルダを整理してhomeのページとaboutのページにルーティングできるようにしていきます。

まずは、HelloWorld.vueをルーティングの起点にするため、Base.vueに改名して既存のコードを書き換えます。

Base.vue
<template>
  <v-container>
    <p><router-link to="/">home</router-link></p>
    <p><router-link to="/about">about</router-link></p>
    <router-view></router-view>
  </v-container>
</template>

<script lang="ts">
  import Vue from 'vue'

  export default Vue.extend({
    name: 'Base'
  })
</script>

ルーティングはあらかじめsrc/router/index.tsで定義されています。

Base.vueへの変更に伴ってApp.vue,Home.vueも変更します。

App.vue
<template>
  <v-app>
    <v-content>
      <Base/>
    </v-content>
  </v-app>
</template>

<script lang="ts">
import Vue from 'vue';
import Base from './components/Base.vue';

export default Vue.extend({
  name: 'App',

  components: {
    Base,
  },

  data: () => ({
    //
  }),
});
</script>
Home.vue
<template>
    <h1>This is a home page</h1>
</template>

localhost で以下のような画面になっており、aboutを押して画面遷移ができていれば成功です。
スクリーンショット 2020-04-05 11.35.08.png

これで、サイトを作る土台ができました。
あとはsrc/viewsにファイルを、src/routes/index.tsにルーティングを追加し、実装していくだけです!
ここまでのコード

2日目

Vuetifyで実装

使ったコンポーネントなどを紹介していきます。

Cards

全てのページで使いました。
HOME画面ではボタンとしての機能を持たせ、それ以外の画面では、背景として用いました。

スクリーンショット 2020-04-05 11.58.23.png
スクリーンショット 2020-04-05 11.58.48.png
これを用いて画面を整理することで、見やすくなるだけでなくレスポンシブにも対応しやすくなりました。

Expansion panels

自己紹介の一覧に使用しました。
スクリーンショット 2020-04-05 11.51.21.png
初めはhタグで直接書いていたのですが、デザインやレスポンシブの面であまりよくありませんでした。
これを導入することで伝えたい項目が明確になり、クリックすると情報が得られるという構図ができて画面がスッキリしました。

Timelines

自分の経験を時間軸で表示するために使いました。
スクリーンショット 2020-04-05 12.01.34.png

denseで左側に寄せ、Cardsと併用することでそこでの経験を説明するのに十分なスペースが確保できました。

以上です。
とりあえずポートフォリオを作りたいっていう方はVuetifyを使うことをおすすめします。
Bootstrapを使った経験のある方は学習コストがほとんどかからず、レスポンシブ対応のスッキリしたポートフォリオサイトが2日で作れます。
デプロイに関しては、masterにpushすればNetlifyが自動デプロイしてくれます。
個人的には、Typescriptの練習をしたいので今後はもっとサイトに動きをつけていこうと思っています。

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

Vue.jsでSPA - [16] へー、FirebaseでWebアプリのログインってこうやるのか

はじめに

だいぶ今更感があるのですが、Vue.js で Firebaseの認証をやってみました

つくってみたのはこんな感じ1

モバイルサイズですがWebアプリです。コードは下の方に貼っておきます

out12.gif

やってみたこと

以下のような手順で、調べながらやりました

Firebase SDK を Web アプリに読み込んでおく

公式のとおりに事前設定。要はこんなやつを html に貼り付けるアレ

var firebaseConfig = {
  apiKey: "api-key",
  authDomain: "project-id.firebaseapp.com",
  databaseURL: "https://project-id.firebaseio.com",
  projectId: "project-id",
  storageBucket: "project-id.appspot.com",
  messagingSenderId: "sender-id",
  appID: "app-id",
};

Firebase で Authentication を有効にしておく

今回は一番シンプルそうな「メール / パスワード」での認証を有効化2

image.png

公式のサンプルコードで動作確認

このログインのサンプルコードをありがたく使わせてもらいました。公式のここから辿ることができます。見ての通り、ログインにまつわる一通りの動作が実装されています。パスワードリセットなんかも。使ってみると、ボタンの下の null のところにバックエンドとのやりとりが表示されるのでわかりやすくてよい

image.png

試しにこのサンプルコードで SIGN UP したユーザはこんな感じでFirebaseコンソールでみることができる

image.png

サンプルをみながら自分のコードで認証できるようにする

サンプルをみながら Vue.js で書いてみました。簡単なんでペタッと貼っておきます。

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Basic Page Needs -->
  <meta charset="utf-8">
  <title>KUSA</title>
  <meta name="description" content="">
  <meta name="author" content="">
  <!-- Mobile Specific Metas -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- FONT -->
  <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
  <!-- CSS -->
  <link rel="stylesheet" href="css/normalize.css">
  <link rel="stylesheet" href="css/skeleton.css">
  <!-- Favicon -->
  <link rel="icon" type="image/png" href="img/kusa-h40.png">
  <!-- Application configuration -->
  <script type="text/javascript" src=".app_config.js"></script>
  <!-- Fontawsome -->
  <script src="https://kit.fontawesome.com/a1763a7d6f.js" crossorigin="anonymous"></script>
  <!-- Vue.js -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>

<div id="app">

<!-- Menu -->
<div class="container" style="margin-top: 20%">
  <p style="text-align: center;"><img src="img/kusa-h60.png"></img></p>
  <input v-model="email" class="u-full-width font-awesome" type="email" placeholder="Email">
  <input v-model="password" class="u-full-width font-awesome" type="password" placeholder="Password">
  <input @click="signIn" class="u-full-width button-primary" type="submit" value="login">
  <input @click="signUp" class="u-full-width" type="submit" value="create account">

  <a href="#" style="text-decoration:none" @click="signOut">Logout</a>
  <a href="#" style="text-decoration:none" class="u-pull-right">Forgot password</a>

  <label style="margin-top: 20px">Error :</label>
  <pre style="margin-top: 0px; margin-bottom: 5px;"><code>{{ message }}</code></pre>
  <label>Current user :</label>
  <pre style="margin-top: 0px; margin-bottom: 5px;"><code>{{ currentUser }}</code></pre>
</div>

</div>

<!-- Insert this script at the bottom of the HTML, but before you use any Firebase services -->
<!-- Add the entire Firebase JavaScript SDK. Replace when go for production -->
<script src="https://www.gstatic.com/firebasejs/6.2.0/firebase.js"></script>

<script>

  firebase.initializeApp(conf.fbc);
  //firebase.analytics();

  // Vue
  var app = new Vue({
    el: '#app',
    data () {
      return {
        email: '',
        password: '',
        message: '',
        currentUser: ''
      }
    },
    methods: {
      signIn: function () {
        app.message = ''; console.log('sign-in pressed');

        firebase.auth().signInWithEmailAndPassword(app.email, app.password).catch(function(error) {
          var errorCode = error.code;
          var errorMessage = error.message;

          app.message = error.message;
          console.log(error.message);

        }).then(function() {
          app.currentUser = firebase.auth().currentUser;
        });
      },
      signUp: function () {
        app.message = ''; console.log('sign-up pressed');
        firebase.auth().createUserWithEmailAndPassword(this.email, this.password).catch(function(error) {
          var errorCode = error.code;
          var errorMessage = error.message;

          app.message = error.message;
          console.log(error);
        }).then(function() {
          app.currentUser = firebase.auth().currentUser;
        });
      },
      signOut: function () {
        firebase.auth().signOut().then(function() {
          app.currentUser = firebase.auth().currentUser;
        });
      },
      resetPass: function () {
     // あとで書く
      }
    }
  })
</script>

</body>
</html>

  • firebaseConfig は直書きしたくなかったので外部ファイル .app_config.js から呼ぶようにして、.app_config.jsgitignore しています3
  • ログインしているかどうか、誰でログインしているかは firebase.auth().currentUser に聞けばわかるので便利

これから

Firebase SDK はよくできていて、かなり短時間で認証周りが作れることがわかりました。あとで OAuth 系も触ってみます。そのあと、引き続き、この KUSA アプリ完成までやっていきます!

シリーズ


  1. ffmpeg で mov を gif にしたけど、なんか青っぽい。なんとかならんか 

  2. 有効になってないと、サンプルコードにこんな風に怒られる → The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section. 

  3. 直書きしてもセキュリティ上問題ないし、本番に出すときに Firebase Hosting つかうと、どうせバレるところに設定ファイルがあるようですが、やはり開発中はデータベースなどのエンドポイント知られるのはなんか気になるし、デバッグでルールをフルオープンしたりもあるので念のためこうしてます 

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

<router-view>のタグがそのまま表示される

概要

devツールを見た時にがそのまま表示されて、内容が出てこない

解決策

  • router.jsに使用していないroutesが入っている
  • router.jsでimportしていない とこの現象が起きる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js 画像URLから幅と高さを取得しようとしてはまった

こちらの記事どうり簡単に取得できるものだと思っていたけど、

var img = new Image();
img.src = 'http://image.src.com/image.jpg';

var width  = img.width;  // 幅
var height = img.height; // 高さ

vue.jsでやってみた時にはimg.widthimg.heightが0だった。

調べているとstackoverflowなどでreturnが0だ!なんで!みたいな記事を見つけたがそもそもjavascriptの知識が乏しいのとjQueryを使ってどうにかしようとしている解決法をvue.jsでどうかくかわからなかった

知人に聞きこちらの記事を発見
https://www.raymondcamden.com/2019/06/13/reading-image-sizes-and-dimensions-with-vuejs

img.onloadという関数を使ってvar img = new Image();でできたimgを読み込まなきゃ画像の幅、高さを取得できないそうだ。

かつ、スコープの関係でonload内で使った変数を他で使うことはできないため、vue.jsのdataにプロパティを設定して、そこにセットする形になるそう。

vue.js
  export default {
    data () {
      return {
        new_image: []
      }
    },
    methods: {
      getImageSize (obj) {
        var img = new Image();
        img.onload = () => {
          this.new_image.push({width: img.width, height: img.height});
        }
        img.src = obj;
      }
    }
  }

// objには画像urlが入ります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あつ森の魚・虫リストCSVをVueでテーブル表示し、現在時刻でソートしました。

できたもの

こちら

途中だけどとりあえず公開しちゃいます。
あとで綺麗にまとめます。

あとでつくるリスト

  • 時間プルダウンに「絞り込まない」追加
  • 項目並び替え実装(売値の昇順、降順)
  • Cookieを使用して南半球などのチェック設定を保持
  • Cookieを使用して捕まえたかどうかのチェック項目を追加
  • レスポンシブ化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む